mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-11 03:33:53 +08:00
414 lines
13 KiB
Go
414 lines
13 KiB
Go
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gendao
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/olekukonko/tablewriter"
|
|
"github.com/olekukonko/tablewriter/renderer"
|
|
"github.com/olekukonko/tablewriter/tw"
|
|
"golang.org/x/mod/modfile"
|
|
|
|
"github.com/gogf/gf/v2/container/garray"
|
|
"github.com/gogf/gf/v2/container/gset"
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gfile"
|
|
"github.com/gogf/gf/v2/os/gproc"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"github.com/gogf/gf/v2/os/gview"
|
|
"github.com/gogf/gf/v2/text/gregex"
|
|
"github.com/gogf/gf/v2/text/gstr"
|
|
|
|
"hotgo/internal/library/hggen/internal/utility/mlog"
|
|
"hotgo/internal/library/hggen/internal/utility/utils"
|
|
)
|
|
|
|
type (
|
|
CGenDao struct{}
|
|
CGenDaoInput struct {
|
|
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
|
|
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
|
|
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
|
|
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
|
|
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
|
|
ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"`
|
|
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
|
|
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
|
|
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
|
|
RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenDaoBriefRemoveFieldPrefix}"`
|
|
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
|
|
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
|
|
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
|
|
TablePath string `name:"tablePath" short:"tp" brief:"{CGenDaoBriefTablePath}" d:"table"`
|
|
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
|
|
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
|
|
TplDaoTablePath string `name:"tplDaoTablePath" short:"t0" brief:"{CGenDaoBriefTplDaoTablePath}"`
|
|
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
|
|
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
|
|
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
|
|
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
|
|
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
|
|
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
|
|
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
|
|
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
|
|
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
|
|
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
|
|
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
|
|
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
|
|
GenTable bool `name:"genTable" short:"gt" brief:"{CGenDaoBriefGenTable}" orphan:"true"`
|
|
|
|
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
|
|
FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"`
|
|
|
|
// internal usage purpose.
|
|
genItems *CGenDaoInternalGenItems
|
|
}
|
|
CGenDaoOutput struct{}
|
|
|
|
CGenDaoInternalInput struct {
|
|
CGenDaoInput
|
|
DB gdb.DB
|
|
TableNames []string
|
|
NewTableNames []string
|
|
ShardingTableSet *gset.StrSet
|
|
}
|
|
DBTableFieldName = string
|
|
DBFieldTypeName = string
|
|
CustomAttributeType struct {
|
|
Type string `brief:"custom attribute type name"`
|
|
Import string `brief:"custom import for this type"`
|
|
}
|
|
)
|
|
|
|
var (
|
|
createdAt = gtime.Now()
|
|
tplView = gview.New()
|
|
defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{
|
|
"decimal": {
|
|
Type: "float64",
|
|
},
|
|
"money": {
|
|
Type: "float64",
|
|
},
|
|
"numeric": {
|
|
Type: "float64",
|
|
},
|
|
"smallmoney": {
|
|
Type: "float64",
|
|
},
|
|
}
|
|
|
|
// tablewriter Options
|
|
twRenderer = tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
|
|
Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.Off, Right: tw.Off},
|
|
Settings: tw.Settings{
|
|
Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off},
|
|
},
|
|
Symbols: tw.NewSymbols(tw.StyleASCII),
|
|
}))
|
|
twConfig = tablewriter.WithConfig(tablewriter.Config{
|
|
Row: tw.CellConfig{
|
|
Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone},
|
|
},
|
|
})
|
|
)
|
|
|
|
func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
|
|
in.genItems = newCGenDaoInternalGenItems()
|
|
if in.Link != "" {
|
|
doGenDaoForArray(ctx, -1, in)
|
|
} else if g.Cfg().Available(ctx) {
|
|
v := g.Cfg().MustGet(ctx, CGenDaoConfig)
|
|
if v.IsSlice() {
|
|
for i := 0; i < len(v.Interfaces()); i++ {
|
|
doGenDaoForArray(ctx, i, in)
|
|
}
|
|
} else {
|
|
doGenDaoForArray(ctx, -1, in)
|
|
}
|
|
} else {
|
|
doGenDaoForArray(ctx, -1, in)
|
|
}
|
|
doClear(in.genItems)
|
|
mlog.Print("done!")
|
|
return
|
|
}
|
|
|
|
// doGenDaoForArray implements the "gen dao" command for configuration array.
|
|
func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
|
|
if in.genItems == nil {
|
|
in.genItems = newCGenDaoInternalGenItems()
|
|
}
|
|
|
|
var (
|
|
err error
|
|
db gdb.DB
|
|
)
|
|
if index >= 0 {
|
|
err = g.Cfg().MustGet(
|
|
ctx,
|
|
fmt.Sprintf(`%s.%d`, CGenDaoConfig, index),
|
|
).Scan(&in)
|
|
if err != nil {
|
|
mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenDaoConfig, err)
|
|
}
|
|
}
|
|
if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" {
|
|
mlog.Fatalf(`path "%s" does not exist`, in.Path)
|
|
}
|
|
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
|
|
|
|
// It uses user passed database configuration.
|
|
if in.Link != "" {
|
|
var tempGroup = gtime.TimestampNanoStr()
|
|
err = gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
|
|
Link: in.Link,
|
|
})
|
|
if err != nil {
|
|
mlog.Fatalf(`database configuration failed: %+v`, err)
|
|
}
|
|
if db, err = gdb.Instance(tempGroup); err != nil {
|
|
mlog.Fatalf(`database initialization failed: %+v`, err)
|
|
}
|
|
} else {
|
|
db = g.DB(in.Group)
|
|
}
|
|
if db == nil {
|
|
mlog.Fatal(`database initialization failed, may be invalid database configuration`)
|
|
}
|
|
|
|
var tableNames []string
|
|
if in.Tables != "" {
|
|
tableNames = gstr.SplitAndTrim(in.Tables, ",")
|
|
} else {
|
|
tableNames, err = db.Tables(context.TODO())
|
|
if err != nil {
|
|
mlog.Fatalf("fetching tables failed: %+v", err)
|
|
}
|
|
}
|
|
// Table excluding.
|
|
if in.TablesEx != "" {
|
|
array := garray.NewStrArrayFrom(tableNames)
|
|
for _, p := range gstr.SplitAndTrim(in.TablesEx, ",") {
|
|
if gstr.Contains(p, "*") || gstr.Contains(p, "?") {
|
|
p = gstr.ReplaceByMap(p, map[string]string{
|
|
"\r": "",
|
|
"\n": "",
|
|
})
|
|
p = gstr.ReplaceByMap(p, map[string]string{
|
|
"*": "\r",
|
|
"?": "\n",
|
|
})
|
|
p = gregex.Quote(p)
|
|
p = gstr.ReplaceByMap(p, map[string]string{
|
|
"\r": ".*",
|
|
"\n": ".",
|
|
})
|
|
for _, v := range array.Clone().Slice() {
|
|
if gregex.IsMatchString(p, v) {
|
|
array.RemoveValue(v)
|
|
}
|
|
}
|
|
} else {
|
|
array.RemoveValue(p)
|
|
}
|
|
}
|
|
tableNames = array.Slice()
|
|
}
|
|
|
|
// merge default typeMapping to input typeMapping.
|
|
if in.TypeMapping == nil {
|
|
in.TypeMapping = defaultTypeMapping
|
|
} else {
|
|
for key, typeMapping := range defaultTypeMapping {
|
|
if _, ok := in.TypeMapping[key]; !ok {
|
|
in.TypeMapping[key] = typeMapping
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generating dao & model go files one by one according to given table name.
|
|
var (
|
|
newTableNames = make([]string, len(tableNames))
|
|
shardingNewTableSet = gset.NewStrSet()
|
|
)
|
|
for i, tableName := range tableNames {
|
|
newTableName := tableName
|
|
for _, v := range removePrefixArray {
|
|
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
|
|
}
|
|
if len(in.ShardingPattern) > 0 {
|
|
for _, pattern := range in.ShardingPattern {
|
|
var (
|
|
match []string
|
|
regPattern = gstr.Replace(pattern, "?", `(.+)`)
|
|
)
|
|
match, err = gregex.MatchString(regPattern, newTableName)
|
|
if err != nil {
|
|
mlog.Fatalf(`invalid sharding pattern "%s": %+v`, pattern, err)
|
|
}
|
|
if len(match) < 2 {
|
|
continue
|
|
}
|
|
newTableName = gstr.Replace(pattern, "?", "")
|
|
newTableName = gstr.Trim(newTableName, `_.-`)
|
|
if shardingNewTableSet.Contains(newTableName) {
|
|
tableNames[i] = ""
|
|
continue
|
|
}
|
|
// Add prefix to sharding table name, if not, the isSharding check would not match.
|
|
shardingNewTableSet.Add(in.Prefix + newTableName)
|
|
}
|
|
}
|
|
newTableName = in.Prefix + newTableName
|
|
if tableNames[i] != "" {
|
|
// If shardingNewTableSet contains newTableName (tableName is empty), it should not be added to tableNames, make it empty and filter later.
|
|
newTableNames[i] = newTableName
|
|
}
|
|
}
|
|
tableNames = garray.NewStrArrayFrom(tableNames).FilterEmpty().Slice()
|
|
newTableNames = garray.NewStrArrayFrom(newTableNames).FilterEmpty().Slice() // Filter empty table names. make sure that newTableNames and tableNames have the same length.
|
|
in.genItems.Scale()
|
|
|
|
// Dao: index and internal.
|
|
generateDao(ctx, CGenDaoInternalInput{
|
|
CGenDaoInput: in,
|
|
DB: db,
|
|
TableNames: tableNames,
|
|
NewTableNames: newTableNames,
|
|
ShardingTableSet: shardingNewTableSet,
|
|
})
|
|
// Table: table fields.
|
|
generateTable(ctx, CGenDaoInternalInput{
|
|
CGenDaoInput: in,
|
|
DB: db,
|
|
TableNames: tableNames,
|
|
NewTableNames: newTableNames,
|
|
ShardingTableSet: shardingNewTableSet,
|
|
})
|
|
// Do.
|
|
generateDo(ctx, CGenDaoInternalInput{
|
|
CGenDaoInput: in,
|
|
DB: db,
|
|
TableNames: tableNames,
|
|
NewTableNames: newTableNames,
|
|
})
|
|
// Entity.
|
|
generateEntity(ctx, CGenDaoInternalInput{
|
|
CGenDaoInput: in,
|
|
DB: db,
|
|
TableNames: tableNames,
|
|
NewTableNames: newTableNames,
|
|
})
|
|
|
|
in.genItems.SetClear(in.Clear)
|
|
}
|
|
|
|
func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
|
|
var packageImportsArray = garray.NewStrArray()
|
|
if isDo {
|
|
packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
|
|
}
|
|
|
|
// Time package recognition.
|
|
if strings.Contains(source, "gtime.Time") {
|
|
packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`)
|
|
} else if strings.Contains(source, "time.Time") {
|
|
packageImportsArray.Append(`"time"`)
|
|
}
|
|
|
|
// Json type.
|
|
if strings.Contains(source, "gjson.Json") {
|
|
packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
|
|
}
|
|
|
|
// Check and update imports in go.mod
|
|
if len(appendImports) > 0 {
|
|
goModPath := utils.GetModPath()
|
|
if goModPath == "" {
|
|
mlog.Fatal("go.mod not found in current project")
|
|
}
|
|
mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil)
|
|
if err != nil {
|
|
mlog.Fatalf("parse go.mod failed: %+v", err)
|
|
}
|
|
for _, appendImport := range appendImports {
|
|
found := false
|
|
for _, require := range mod.Require {
|
|
if gstr.Contains(appendImport, require.Mod.Path) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
if err = gproc.ShellRun(ctx, `go get `+appendImport); err != nil {
|
|
mlog.Fatalf(`%+v`, err)
|
|
}
|
|
}
|
|
packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport))
|
|
}
|
|
}
|
|
|
|
// Generate and write content to golang file.
|
|
packageImportsStr := ""
|
|
if packageImportsArray.Len() > 0 {
|
|
packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n"))
|
|
}
|
|
return packageImportsStr
|
|
}
|
|
|
|
func assignDefaultVar(view *gview.View, in CGenDaoInternalInput) {
|
|
var (
|
|
tplCreatedAtDatetimeStr string
|
|
tplDatetimeStr = createdAt.String()
|
|
)
|
|
if in.WithTime {
|
|
tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr)
|
|
}
|
|
view.Assigns(g.Map{
|
|
tplVarDatetimeStr: tplDatetimeStr,
|
|
tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr,
|
|
})
|
|
}
|
|
|
|
func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string {
|
|
names := make(map[int]string)
|
|
for _, field := range fieldMap {
|
|
names[field.Index] = field.Name
|
|
}
|
|
var (
|
|
i = 0
|
|
j = 0
|
|
result = make([]string, len(names))
|
|
)
|
|
for {
|
|
if len(names) == 0 {
|
|
break
|
|
}
|
|
if val, ok := names[i]; ok {
|
|
result[j] = val
|
|
j++
|
|
delete(names, i)
|
|
}
|
|
i++
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getTemplateFromPathOrDefault(filePath string, def string) string {
|
|
if filePath != "" {
|
|
if contents := gfile.GetContents(filePath); contents != "" {
|
|
return contents
|
|
}
|
|
}
|
|
return def
|
|
}
|