2
0

auto_code_template.go 14 KB

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