gendao.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package gendao
  7. import (
  8. "context"
  9. "fmt"
  10. "golang.org/x/mod/modfile"
  11. "strings"
  12. "github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
  13. "github.com/gogf/gf/v2/container/garray"
  14. "github.com/gogf/gf/v2/database/gdb"
  15. "github.com/gogf/gf/v2/frame/g"
  16. "github.com/gogf/gf/v2/os/gfile"
  17. "github.com/gogf/gf/v2/os/gproc"
  18. "github.com/gogf/gf/v2/os/gtime"
  19. "github.com/gogf/gf/v2/text/gstr"
  20. "github.com/gogf/gf/v2/util/gtag"
  21. "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
  22. )
  23. const (
  24. CGenDaoConfig = `gfcli.gen.dao`
  25. CGenDaoUsage = `gf gen dao [OPTION]`
  26. CGenDaoBrief = `automatically generate go files for dao/do/entity`
  27. CGenDaoEg = `
  28. gf gen dao
  29. gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
  30. gf gen dao -p ./model -g user-center -t user,user_detail,user_login
  31. gf gen dao -r user_
  32. `
  33. CGenDaoAd = `
  34. CONFIGURATION SUPPORT
  35. Options are also supported by configuration file.
  36. It's suggested using configuration file instead of command line arguments making producing.
  37. The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml):
  38. gfcli:
  39. gen:
  40. dao:
  41. - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
  42. tables: "order,products"
  43. jsonCase: "CamelLower"
  44. - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
  45. path: "./my-app"
  46. prefix: "primary_"
  47. tables: "user, userDetail"
  48. typeMapping:
  49. decimal:
  50. type: decimal.Decimal
  51. import: github.com/shopspring/decimal
  52. numeric:
  53. type: string
  54. `
  55. CGenDaoBriefPath = `directory path for generated files`
  56. CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
  57. CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','`
  58. CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','`
  59. CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables`
  60. CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
  61. CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables`
  62. CGenDaoBriefWithTime = `add created time for auto produced go files`
  63. CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
  64. CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
  65. CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
  66. CGenDaoBriefDoPath = `directory path for storing generated do files under path`
  67. CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
  68. CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
  69. CGenDaoBriefModelFile = `custom file name for storing generated model content`
  70. CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default`
  71. CGenDaoBriefDescriptionTag = `add comment to description tag for each field`
  72. CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
  73. CGenDaoBriefNoModelComment = `no model comment will be added for each field`
  74. CGenDaoBriefClear = `delete all generated go files that do not exist in database`
  75. CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
  76. CGenDaoBriefGroup = `
  77. specifying the configuration group name of database for generated ORM instance,
  78. it's not necessary and the default value is "default"
  79. `
  80. CGenDaoBriefJsonCase = `
  81. generated json tag case for model struct, cases are as follows:
  82. | Case | Example |
  83. |---------------- |--------------------|
  84. | Camel | AnyKindOfString |
  85. | CamelLower | anyKindOfString | default
  86. | Snake | any_kind_of_string |
  87. | SnakeScreaming | ANY_KIND_OF_STRING |
  88. | SnakeFirstUpper | rgb_code_md5 |
  89. | Kebab | any-kind-of-string |
  90. | KebabScreaming | ANY-KIND-OF-STRING |
  91. `
  92. CGenDaoBriefTplDaoIndexPath = `template file path for dao index file`
  93. CGenDaoBriefTplDaoInternalPath = `template file path for dao internal file`
  94. CGenDaoBriefTplDaoDoPathPath = `template file path for dao do file`
  95. CGenDaoBriefTplDaoEntityPath = `template file path for dao entity file`
  96. tplVarTableName = `{TplTableName}`
  97. tplVarTableNameCamelCase = `{TplTableNameCamelCase}`
  98. tplVarTableNameCamelLowerCase = `{TplTableNameCamelLowerCase}`
  99. tplVarPackageImports = `{TplPackageImports}`
  100. tplVarImportPrefix = `{TplImportPrefix}`
  101. tplVarStructDefine = `{TplStructDefine}`
  102. tplVarColumnDefine = `{TplColumnDefine}`
  103. tplVarColumnNames = `{TplColumnNames}`
  104. tplVarGroupName = `{TplGroupName}`
  105. tplVarDatetimeStr = `{TplDatetimeStr}`
  106. tplVarCreatedAtDatetimeStr = `{TplCreatedAtDatetimeStr}`
  107. )
  108. var (
  109. createdAt = gtime.Now()
  110. defaultTypeMapping = map[string]TypeMapping{
  111. "decimal": {
  112. Type: "float64",
  113. },
  114. "money": {
  115. Type: "float64",
  116. },
  117. "numeric": {
  118. Type: "float64",
  119. },
  120. "smallmoney": {
  121. Type: "float64",
  122. },
  123. "tinyint": {
  124. Type: "int64",
  125. },
  126. "smallint": {
  127. Type: "int64",
  128. },
  129. "mediumint": {
  130. Type: "int64",
  131. },
  132. "int": {
  133. Type: "int64",
  134. },
  135. "bigint": {
  136. Type: "int64",
  137. },
  138. }
  139. )
  140. func init() {
  141. gtag.Sets(g.MapStrStr{
  142. `CGenDaoConfig`: CGenDaoConfig,
  143. `CGenDaoUsage`: CGenDaoUsage,
  144. `CGenDaoBrief`: CGenDaoBrief,
  145. `CGenDaoEg`: CGenDaoEg,
  146. `CGenDaoAd`: CGenDaoAd,
  147. `CGenDaoBriefPath`: CGenDaoBriefPath,
  148. `CGenDaoBriefLink`: CGenDaoBriefLink,
  149. `CGenDaoBriefTables`: CGenDaoBriefTables,
  150. `CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
  151. `CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
  152. `CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
  153. `CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
  154. `CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
  155. `CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
  156. `CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
  157. `CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
  158. `CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
  159. `CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix,
  160. `CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao,
  161. `CGenDaoBriefModelFile`: CGenDaoBriefModelFile,
  162. `CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao,
  163. `CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag,
  164. `CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
  165. `CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
  166. `CGenDaoBriefClear`: CGenDaoBriefClear,
  167. `CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping,
  168. `CGenDaoBriefGroup`: CGenDaoBriefGroup,
  169. `CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
  170. `CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
  171. `CGenDaoBriefTplDaoInternalPath`: CGenDaoBriefTplDaoInternalPath,
  172. `CGenDaoBriefTplDaoDoPathPath`: CGenDaoBriefTplDaoDoPathPath,
  173. `CGenDaoBriefTplDaoEntityPath`: CGenDaoBriefTplDaoEntityPath,
  174. })
  175. }
  176. type (
  177. CGenDao struct{}
  178. CGenDaoInput struct {
  179. g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
  180. Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
  181. Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
  182. Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
  183. TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
  184. Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
  185. Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
  186. RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
  187. JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
  188. ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
  189. DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
  190. DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
  191. EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
  192. TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
  193. TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
  194. TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
  195. TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
  196. StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
  197. WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
  198. GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
  199. OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
  200. DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
  201. NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
  202. NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
  203. Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
  204. TypeMapping map[string]TypeMapping `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
  205. }
  206. CGenDaoOutput struct{}
  207. CGenDaoInternalInput struct {
  208. CGenDaoInput
  209. DB gdb.DB
  210. TableNames []string
  211. NewTableNames []string
  212. }
  213. TypeMapping struct {
  214. Type string `brief:"custom attribute type name"`
  215. Import string `brief:"custom import for this type"`
  216. }
  217. )
  218. func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
  219. if g.Cfg().Available(ctx) {
  220. v := g.Cfg().MustGet(ctx, CGenDaoConfig)
  221. if v.IsSlice() {
  222. for i := 0; i < len(v.Interfaces()); i++ {
  223. doGenDaoForArray(ctx, i, in)
  224. }
  225. } else {
  226. doGenDaoForArray(ctx, -1, in)
  227. }
  228. } else {
  229. doGenDaoForArray(ctx, -1, in)
  230. }
  231. mlog.Print("done!")
  232. return
  233. }
  234. // doGenDaoForArray implements the "gen dao" command for configuration array.
  235. func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
  236. var (
  237. err error
  238. db gdb.DB
  239. )
  240. if index >= 0 {
  241. err = g.Cfg().MustGet(
  242. ctx,
  243. fmt.Sprintf(`%s.%d`, CGenDaoConfig, index),
  244. ).Scan(&in)
  245. if err != nil {
  246. mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenDaoConfig, err)
  247. }
  248. }
  249. if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" {
  250. mlog.Fatalf(`path "%s" does not exist`, in.Path)
  251. }
  252. removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
  253. // It uses user passed database configuration.
  254. if in.Link != "" {
  255. var tempGroup = gtime.TimestampNanoStr()
  256. gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
  257. Link: in.Link,
  258. })
  259. if db, err = gdb.Instance(tempGroup); err != nil {
  260. mlog.Fatalf(`database initialization failed: %+v`, err)
  261. }
  262. } else {
  263. db = g.DB(in.Group)
  264. }
  265. if db == nil {
  266. mlog.Fatal(`database initialization failed, may be invalid database configuration`)
  267. }
  268. var tableNames []string
  269. if in.Tables != "" {
  270. tableNames = gstr.SplitAndTrim(in.Tables, ",")
  271. } else {
  272. tableNames, err = db.Tables(context.TODO())
  273. if err != nil {
  274. mlog.Fatalf("fetching tables failed: %+v", err)
  275. }
  276. }
  277. // Table excluding.
  278. if in.TablesEx != "" {
  279. array := garray.NewStrArrayFrom(tableNames)
  280. for _, v := range gstr.SplitAndTrim(in.TablesEx, ",") {
  281. array.RemoveValue(v)
  282. }
  283. tableNames = array.Slice()
  284. }
  285. // merge default typeMapping to input typeMapping.
  286. if in.TypeMapping == nil {
  287. in.TypeMapping = defaultTypeMapping
  288. } else {
  289. for key, typeMapping := range defaultTypeMapping {
  290. if _, ok := in.TypeMapping[key]; !ok {
  291. in.TypeMapping[key] = typeMapping
  292. }
  293. }
  294. }
  295. // Generating dao & model go files one by one according to given table name.
  296. newTableNames := make([]string, len(tableNames))
  297. for i, tableName := range tableNames {
  298. newTableName := tableName
  299. for _, v := range removePrefixArray {
  300. newTableName = gstr.TrimLeftStr(newTableName, v, 1)
  301. }
  302. newTableName = in.Prefix + newTableName
  303. newTableNames[i] = newTableName
  304. }
  305. // Dao: index and internal.
  306. generateDao(ctx, CGenDaoInternalInput{
  307. CGenDaoInput: in,
  308. DB: db,
  309. TableNames: tableNames,
  310. NewTableNames: newTableNames,
  311. })
  312. // Do.
  313. generateDo(ctx, CGenDaoInternalInput{
  314. CGenDaoInput: in,
  315. DB: db,
  316. TableNames: tableNames,
  317. NewTableNames: newTableNames,
  318. })
  319. // Entity.
  320. generateEntity(ctx, CGenDaoInternalInput{
  321. CGenDaoInput: in,
  322. DB: db,
  323. TableNames: tableNames,
  324. NewTableNames: newTableNames,
  325. })
  326. }
  327. func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
  328. var packageImportsArray = garray.NewStrArray()
  329. if isDo {
  330. packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
  331. }
  332. // Time package recognition.
  333. if strings.Contains(source, "gtime.Time") {
  334. packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`)
  335. } else if strings.Contains(source, "time.Time") {
  336. packageImportsArray.Append(`"time"`)
  337. }
  338. // Json type.
  339. if strings.Contains(source, "gjson.Json") {
  340. packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
  341. }
  342. // Check and update imports in go.mod
  343. if appendImports != nil && len(appendImports) > 0 {
  344. goModPath := utils.GetModPath()
  345. if goModPath == "" {
  346. mlog.Fatal("go.mod not found in current project")
  347. }
  348. mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil)
  349. if err != nil {
  350. mlog.Fatalf("parse go.mod failed: %+v", err)
  351. }
  352. for _, appendImport := range appendImports {
  353. found := false
  354. for _, require := range mod.Require {
  355. if gstr.Contains(appendImport, require.Mod.Path) {
  356. found = true
  357. break
  358. }
  359. }
  360. if !found {
  361. err = gproc.ShellRun(ctx, `go get `+appendImport)
  362. mlog.Fatalf(`%+v`, err)
  363. }
  364. packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport))
  365. }
  366. }
  367. // Generate and write content to golang file.
  368. packageImportsStr := ""
  369. if packageImportsArray.Len() > 0 {
  370. packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n"))
  371. }
  372. return packageImportsStr
  373. }
  374. func replaceDefaultVar(in CGenDaoInternalInput, origin string) string {
  375. var tplCreatedAtDatetimeStr string
  376. var tplDatetimeStr string = createdAt.String()
  377. if in.WithTime {
  378. tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr)
  379. }
  380. return gstr.ReplaceByMap(origin, g.MapStrStr{
  381. tplVarDatetimeStr: tplDatetimeStr,
  382. tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr,
  383. })
  384. }
  385. func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string {
  386. names := make(map[int]string)
  387. for _, field := range fieldMap {
  388. names[field.Index] = field.Name
  389. }
  390. var (
  391. i = 0
  392. j = 0
  393. result = make([]string, len(names))
  394. )
  395. for {
  396. if len(names) == 0 {
  397. break
  398. }
  399. if val, ok := names[i]; ok {
  400. result[j] = val
  401. j++
  402. delete(names, i)
  403. }
  404. i++
  405. }
  406. return result
  407. }
  408. func getTemplateFromPathOrDefault(filePath string, def string) string {
  409. if filePath != "" {
  410. if contents := gfile.GetContents(filePath); contents != "" {
  411. return contents
  412. }
  413. }
  414. return def
  415. }