auto_code_template.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package system
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/flipped-aurora/gin-vue-admin/server/utils/autocode"
  7. "go/ast"
  8. "go/format"
  9. "go/parser"
  10. "go/token"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "text/template"
  15. "github.com/flipped-aurora/gin-vue-admin/server/global"
  16. model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
  17. "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
  18. utilsAst "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
  19. "github.com/pkg/errors"
  20. "gorm.io/gorm"
  21. )
  22. var AutoCodeTemplate = new(autoCodeTemplate)
  23. type autoCodeTemplate struct{}
  24. func (s *autoCodeTemplate) checkPackage(Pkg string, template string) (err error) {
  25. switch template {
  26. case "package":
  27. apiEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", Pkg, "enter.go")
  28. _, err = os.Stat(apiEnter)
  29. if err != nil {
  30. return fmt.Errorf("package结构异常,缺少api/v1/%s/enter.go", Pkg)
  31. }
  32. serviceEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", Pkg, "enter.go")
  33. _, err = os.Stat(serviceEnter)
  34. if err != nil {
  35. return fmt.Errorf("package结构异常,缺少service/%s/enter.go", Pkg)
  36. }
  37. routerEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", Pkg, "enter.go")
  38. _, err = os.Stat(routerEnter)
  39. if err != nil {
  40. return fmt.Errorf("package结构异常,缺少router/%s/enter.go", Pkg)
  41. }
  42. case "plugin":
  43. pluginEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", Pkg, "plugin.go")
  44. _, err = os.Stat(pluginEnter)
  45. if err != nil {
  46. return fmt.Errorf("plugin结构异常,缺少plugin/%s/plugin.go", Pkg)
  47. }
  48. }
  49. return nil
  50. }
  51. // Create 创建生成自动化代码
  52. func (s *autoCodeTemplate) Create(ctx context.Context, info request.AutoCode) error {
  53. history := info.History()
  54. var autoPkg model.SysAutoCodePackage
  55. err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&autoPkg).Error
  56. if err != nil {
  57. return errors.Wrap(err, "查询包失败!")
  58. }
  59. err = s.checkPackage(info.Package, autoPkg.Template)
  60. if err != nil {
  61. return err
  62. }
  63. // 增加判断: 重复创建struct 或者重复的简称
  64. if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) {
  65. return errors.New("已经创建过此数据结构,请勿重复创建!")
  66. }
  67. generate, templates, injections, err := s.generate(ctx, info, autoPkg)
  68. if err != nil {
  69. return err
  70. }
  71. for key, builder := range generate {
  72. err = os.MkdirAll(filepath.Dir(key), os.ModePerm)
  73. if err != nil {
  74. return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", key)
  75. }
  76. err = os.WriteFile(key, []byte(builder.String()), 0666)
  77. if err != nil {
  78. return errors.Wrapf(err, "[filepath:%s]写入文件失败!", key)
  79. }
  80. }
  81. // 自动创建api
  82. if info.AutoCreateApiToSql && !info.OnlyTemplate {
  83. apis := info.Apis()
  84. err := global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  85. for _, v := range apis {
  86. var api model.SysApi
  87. var id uint
  88. err := tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error
  89. if errors.Is(err, gorm.ErrRecordNotFound) {
  90. if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
  91. return err
  92. }
  93. id = v.ID
  94. } else {
  95. id = api.ID
  96. }
  97. history.ApiIDs = append(history.ApiIDs, id)
  98. }
  99. return nil
  100. })
  101. if err != nil {
  102. return err
  103. }
  104. }
  105. // 自动创建menu
  106. if info.AutoCreateMenuToSql {
  107. var entity model.SysBaseMenu
  108. var id uint
  109. err := global.GVA_DB.WithContext(ctx).First(&entity, "name = ?", info.Abbreviation).Error
  110. if err == nil {
  111. id = entity.ID
  112. } else {
  113. entity = info.Menu(autoPkg.Template)
  114. if info.AutoCreateBtnAuth && !info.OnlyTemplate {
  115. entity.MenuBtn = []model.SysBaseMenuBtn{
  116. {SysBaseMenuID: entity.ID, Name: "add", Desc: "新增"},
  117. {SysBaseMenuID: entity.ID, Name: "batchDelete", Desc: "批量删除"},
  118. {SysBaseMenuID: entity.ID, Name: "delete", Desc: "删除"},
  119. {SysBaseMenuID: entity.ID, Name: "edit", Desc: "编辑"},
  120. {SysBaseMenuID: entity.ID, Name: "info", Desc: "详情"},
  121. }
  122. if info.HasExcel {
  123. excelBtn := []model.SysBaseMenuBtn{
  124. {SysBaseMenuID: entity.ID, Name: "exportTemplate", Desc: "导出模板"},
  125. {SysBaseMenuID: entity.ID, Name: "exportExcel", Desc: "导出Excel"},
  126. {SysBaseMenuID: entity.ID, Name: "importExcel", Desc: "导入Excel"},
  127. }
  128. entity.MenuBtn = append(entity.MenuBtn, excelBtn...)
  129. }
  130. }
  131. err = global.GVA_DB.WithContext(ctx).Create(&entity).Error
  132. id = entity.ID
  133. if err != nil {
  134. return errors.Wrap(err, "创建菜单失败!")
  135. }
  136. }
  137. history.MenuID = id
  138. }
  139. if info.HasExcel {
  140. dbName := info.BusinessDB
  141. name := info.Package + "_" + info.StructName
  142. tableName := info.TableName
  143. fieldsMap := make(map[string]string, len(info.Fields))
  144. for _, field := range info.Fields {
  145. if field.Excel {
  146. fieldsMap[field.ColumnName] = field.FieldDesc
  147. }
  148. }
  149. templateInfo, _ := json.Marshal(fieldsMap)
  150. sysExportTemplate := model.SysExportTemplate{
  151. DBName: dbName,
  152. Name: name,
  153. TableName: tableName,
  154. TemplateID: name,
  155. TemplateInfo: string(templateInfo),
  156. }
  157. err = SysExportTemplateServiceApp.CreateSysExportTemplate(&sysExportTemplate)
  158. if err != nil {
  159. return err
  160. }
  161. history.ExportTemplateID = sysExportTemplate.ID
  162. }
  163. // 创建历史记录
  164. history.Templates = templates
  165. history.Injections = make(map[string]string, len(injections))
  166. for key, value := range injections {
  167. bytes, _ := json.Marshal(value)
  168. history.Injections[key] = string(bytes)
  169. }
  170. err = AutocodeHistory.Create(ctx, history)
  171. if err != nil {
  172. return err
  173. }
  174. return nil
  175. }
  176. // Preview 预览自动化代码
  177. func (s *autoCodeTemplate) Preview(ctx context.Context, info request.AutoCode) (map[string]string, error) {
  178. var entity model.SysAutoCodePackage
  179. err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&entity).Error
  180. if err != nil {
  181. return nil, errors.Wrap(err, "查询包失败!")
  182. }
  183. // 增加判断: 重复创建struct 或者重复的简称
  184. if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) && !info.IsAdd {
  185. return nil, errors.New("已经创建过此数据结构或重复简称,请勿重复创建!")
  186. }
  187. preview := make(map[string]string)
  188. codes, _, _, err := s.generate(ctx, info, entity)
  189. if err != nil {
  190. return nil, err
  191. }
  192. for key, writer := range codes {
  193. if len(key) > len(global.GVA_CONFIG.AutoCode.Root) {
  194. key, _ = filepath.Rel(global.GVA_CONFIG.AutoCode.Root, key)
  195. }
  196. // 获取key的后缀 取消.
  197. suffix := filepath.Ext(key)[1:]
  198. var builder strings.Builder
  199. builder.WriteString("```" + suffix + "\n\n")
  200. builder.WriteString(writer.String())
  201. builder.WriteString("\n\n```")
  202. preview[key] = builder.String()
  203. }
  204. return preview, nil
  205. }
  206. func (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode, entity model.SysAutoCodePackage) (map[string]strings.Builder, map[string]string, map[string]utilsAst.Ast, error) {
  207. templates, asts, _, err := AutoCodePackage.templates(ctx, entity, info, false)
  208. if err != nil {
  209. return nil, nil, nil, err
  210. }
  211. code := make(map[string]strings.Builder)
  212. for key, create := range templates {
  213. var files *template.Template
  214. files, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)
  215. if err != nil {
  216. return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]读取模版文件失败!", key)
  217. }
  218. var builder strings.Builder
  219. err = files.Execute(&builder, info)
  220. if err != nil {
  221. return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]生成文件失败!", create)
  222. }
  223. code[create] = builder
  224. } // 生成文件
  225. injections := make(map[string]utilsAst.Ast, len(asts))
  226. for key, value := range asts {
  227. keys := strings.Split(key, "=>")
  228. if len(keys) == 2 {
  229. if keys[1] == utilsAst.TypePluginInitializeV2 {
  230. continue
  231. }
  232. if info.OnlyTemplate {
  233. if keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm {
  234. continue
  235. }
  236. }
  237. if !info.AutoMigrate {
  238. if keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm {
  239. continue
  240. }
  241. }
  242. var builder strings.Builder
  243. parse, _ := value.Parse("", &builder)
  244. if parse != nil {
  245. _ = value.Injection(parse)
  246. err = value.Format("", &builder, parse)
  247. if err != nil {
  248. return nil, nil, nil, err
  249. }
  250. code[keys[0]] = builder
  251. injections[keys[1]] = value
  252. fmt.Println(keys[0], "注入成功!")
  253. }
  254. }
  255. }
  256. // 注入代码
  257. return code, templates, injections, nil
  258. }
  259. func (s *autoCodeTemplate) AddFunc(info request.AutoFunc) error {
  260. autoPkg := model.SysAutoCodePackage{}
  261. err := global.GVA_DB.First(&autoPkg, "package_name = ?", info.Package).Error
  262. if err != nil {
  263. return err
  264. }
  265. if autoPkg.Template != "package" {
  266. info.IsPlugin = true
  267. }
  268. err = s.addTemplateToFile("api.go", info)
  269. if err != nil {
  270. return err
  271. }
  272. err = s.addTemplateToFile("server.go", info)
  273. if err != nil {
  274. return err
  275. }
  276. err = s.addTemplateToFile("api.js", info)
  277. if err != nil {
  278. return err
  279. }
  280. return s.addTemplateToAst("router", info)
  281. }
  282. func (s *autoCodeTemplate) GetApiAndServer(info request.AutoFunc) (map[string]string, error) {
  283. autoPkg := model.SysAutoCodePackage{}
  284. err := global.GVA_DB.First(&autoPkg, "package_name = ?", info.Package).Error
  285. if err != nil {
  286. return nil, err
  287. }
  288. if autoPkg.Template != "package" {
  289. info.IsPlugin = true
  290. }
  291. apiStr, err := s.getTemplateStr("api.go", info)
  292. if err != nil {
  293. return nil, err
  294. }
  295. serverStr, err := s.getTemplateStr("server.go", info)
  296. if err != nil {
  297. return nil, err
  298. }
  299. jsStr, err := s.getTemplateStr("api.js", info)
  300. if err != nil {
  301. return nil, err
  302. }
  303. return map[string]string{"api": apiStr, "server": serverStr, "js": jsStr}, nil
  304. }
  305. func (s *autoCodeTemplate) getTemplateStr(t string, info request.AutoFunc) (string, error) {
  306. tempPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", "function", t+".tpl")
  307. files, err := template.New(filepath.Base(tempPath)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(tempPath)
  308. if err != nil {
  309. return "", errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", tempPath)
  310. }
  311. var builder strings.Builder
  312. err = files.Execute(&builder, info)
  313. if err != nil {
  314. fmt.Println(err.Error())
  315. return "", errors.Wrapf(err, "[filpath:%s]生成文件失败!", tempPath)
  316. }
  317. return builder.String(), nil
  318. }
  319. func (s *autoCodeTemplate) addTemplateToAst(t string, info request.AutoFunc) error {
  320. tPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", info.Package, info.HumpPackageName+".go")
  321. funcName := fmt.Sprintf("Init%sRouter", info.StructName)
  322. routerStr := "RouterWithoutAuth"
  323. if info.IsAuth {
  324. routerStr = "Router"
  325. }
  326. stmtStr := fmt.Sprintf("%s%s.%s(\"%s\", %sApi.%s)", info.Abbreviation, routerStr, info.Method, info.Router, info.Abbreviation, info.FuncName)
  327. if info.IsPlugin {
  328. tPath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "router", info.HumpPackageName+".go")
  329. stmtStr = fmt.Sprintf("group.%s(\"%s\", api%s.%s)", info.Method, info.Router, info.StructName, info.FuncName)
  330. funcName = "Init"
  331. }
  332. src, err := os.ReadFile(tPath)
  333. if err != nil {
  334. return err
  335. }
  336. fileSet := token.NewFileSet()
  337. astFile, err := parser.ParseFile(fileSet, "", src, 0)
  338. if err != nil {
  339. return err
  340. }
  341. funcDecl := utilsAst.FindFunction(astFile, funcName)
  342. stmtNode := utilsAst.CreateStmt(stmtStr)
  343. if info.IsAuth {
  344. for i := 0; i < len(funcDecl.Body.List); i++ {
  345. st := funcDecl.Body.List[i]
  346. // 使用类型断言来检查stmt是否是一个块语句
  347. if blockStmt, ok := st.(*ast.BlockStmt); ok {
  348. // 如果是,插入代码 跳出
  349. blockStmt.List = append(blockStmt.List, stmtNode)
  350. break
  351. }
  352. }
  353. } else {
  354. for i := len(funcDecl.Body.List) - 1; i >= 0; i-- {
  355. st := funcDecl.Body.List[i]
  356. // 使用类型断言来检查stmt是否是一个块语句
  357. if blockStmt, ok := st.(*ast.BlockStmt); ok {
  358. // 如果是,插入代码 跳出
  359. blockStmt.List = append(blockStmt.List, stmtNode)
  360. break
  361. }
  362. }
  363. }
  364. // 创建一个新的文件
  365. f, err := os.Create(tPath)
  366. if err != nil {
  367. return err
  368. }
  369. defer f.Close()
  370. if err := format.Node(f, fileSet, astFile); err != nil {
  371. return err
  372. }
  373. return err
  374. }
  375. func (s *autoCodeTemplate) addTemplateToFile(t string, info request.AutoFunc) error {
  376. getTemplateStr, err := s.getTemplateStr(t, info)
  377. if err != nil {
  378. return err
  379. }
  380. var target string
  381. switch t {
  382. case "api.go":
  383. if info.IsAi && info.ApiFunc != "" {
  384. getTemplateStr = info.ApiFunc
  385. }
  386. target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", info.Package, info.HumpPackageName+".go")
  387. case "server.go":
  388. if info.IsAi && info.ServerFunc != "" {
  389. getTemplateStr = info.ServerFunc
  390. }
  391. target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", info.Package, info.HumpPackageName+".go")
  392. case "api.js":
  393. if info.IsAi && info.JsFunc != "" {
  394. getTemplateStr = info.JsFunc
  395. }
  396. target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "api", info.Package, info.PackageName+".js")
  397. }
  398. if info.IsPlugin {
  399. switch t {
  400. case "api.go":
  401. target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "api", info.HumpPackageName+".go")
  402. case "server.go":
  403. target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "service", info.HumpPackageName+".go")
  404. case "api.js":
  405. target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", info.Package, "api", info.PackageName+".js")
  406. }
  407. }
  408. // 打开文件,如果不存在则返回错误
  409. file, err := os.OpenFile(target, os.O_WRONLY|os.O_APPEND, 0644)
  410. if err != nil {
  411. return err
  412. }
  413. defer file.Close()
  414. // 写入内容
  415. _, err = fmt.Fprintln(file, getTemplateStr)
  416. if err != nil {
  417. fmt.Printf("写入文件失败: %s\n", err.Error())
  418. return err
  419. }
  420. return nil
  421. }