fusenapi/generator/main.go
2023-08-24 11:47:22 +08:00

572 lines
13 KiB
Go

package main
import (
"database/sql"
"flag"
"fmt"
"log"
"os"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
_ "github.com/go-sql-driver/mysql"
)
var testName = "fs_auth_item"
var testGenDir = "model/gmodel"
func toPascalCase(s string) string {
words := strings.Split(s, "_")
for i, word := range words {
words[i] = cases.Title(language.English).String(strings.ToLower(word))
}
return strings.Join(words, "")
}
func GetAllTableNames(uri string) []string {
db, err := sql.Open("mysql", uri)
if err != nil {
panic(err)
}
defer db.Close()
rows, err := db.Query("SHOW TABLES")
if err != nil {
panic(err)
}
var tableNames []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err != nil {
panic(err)
}
tableNames = append(tableNames, tableName)
}
return tableNames
}
// "fsreaderwriter:XErSYmLELKMnf3Dh@tcp(fusen.cdmigcvz3rle.us-east-2.rds.amazonaws.com:3306)/fusen"
func GetColsFromTable(tname string, db *sql.DB) (result []Column, tableName, tableComment string) {
var a, ddl string
err := db.QueryRow("SHOW CREATE TABLE "+tname).Scan(&a, &ddl)
// log.Println(ddl)
if err != nil {
panic(err)
}
return ParserDDL(ddl)
}
var gmodelVarStr = `
package gmodel
import "gorm.io/gorm"
// AllModelsGen 所有Model集合,修改单行,只要不改字段名,不会根据新的内容修改,需要修改的话手动删除
type AllModelsGen struct {
}
func NewAllModels(gdb *gorm.DB) *AllModelsGen {
models := &AllModelsGen{
}
return models
}
`
var gmodelVarStrFormat = `
package gmodel
import "gorm.io/gorm"
// AllModelsGen 所有Model集合,修改单行,只要不改字段名,不会根据新的内容修改,需要修改的话手动删除
type AllModelsGen struct {
%s
}
func NewAllModels(gdb *gorm.DB) *AllModelsGen {
models := &AllModelsGen{
%s
}
return models
}
`
type TableNameComment struct {
Name string
GoName string
Comment string
}
type TMCS []TableNameComment
func (u TMCS) Len() int {
return len(u)
}
func (u TMCS) Less(i, j int) bool {
return u[i].Name < u[j].Name
}
func (u TMCS) Swap(i, j int) {
u[i], u[j] = u[j], u[i]
}
func GenAllModels(filedir string, tmcs ...TableNameComment) {
fileName := filedir + "/var_gen.go"
var dupMap map[string]TableNameComment = make(map[string]TableNameComment)
for _, tmc := range tmcs {
dupMap[tmc.Name] = tmc
}
if _, err := os.Stat(fileName); err == nil {
log.Printf("%s exists!", fileName)
data, err := os.ReadFile(fileName)
if err != nil {
panic(err)
}
filestr := string(data)
filelines := strings.Split(filestr, "\n")
re := regexp.MustCompile(`([A-Za-z0-9_]+) [^/]+ // ([^ ]+) (.+)$`)
for _, line := range filelines {
result := re.FindStringSubmatch(line)
if len(result) > 0 {
// key := result[0]
if len(result) != 4 {
log.Println(result)
}
log.Println(result)
tmc := TableNameComment{
Name: result[2],
GoName: result[1],
Comment: result[3],
}
if newTmc, ok := dupMap[tmc.Name]; ok {
log.Printf("not change: (old)%v -> (new)%v", tmc, newTmc)
}
dupMap[tmc.Name] = tmc
}
}
tmcs = nil
for _, tmc := range dupMap {
tmcs = append(tmcs, tmc)
}
sort.Sort(TMCS(tmcs))
structStr := ""
newModelsStr := ""
for _, tmc := range tmcs {
fsline := fmt.Sprintf("%s *%sModel // %s %s\n", tmc.GoName, tmc.GoName, tmc.Name, tmc.Comment)
structStr += fsline
nmline := fmt.Sprintf("%s: New%sModel(gdb),\n", tmc.GoName, tmc.GoName)
newModelsStr += nmline
}
content := fmt.Sprintf(gmodelVarStrFormat, structStr, newModelsStr)
f, err := os.OpenFile(fileName, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
_, err = f.WriteString(content)
if err != nil {
panic(err)
}
} else if os.IsExist(err) {
f, err := os.Create(fileName)
if err != nil {
panic(err)
}
_, err = f.WriteString(gmodelVarStr)
if err != nil {
panic(err)
}
} else {
panic(err)
}
err := exec.Command("gofmt", "-w", fileName).Run()
if err != nil {
panic(err)
}
}
func main() {
var mysqluri string
var name string // 需要序列化的单独文件名
var mdir string // 需要修改的序列化路径 model
flag.StringVar(&mysqluri, "uri", "fsreaderwriter:XErSYmLELKMnf3Dh@tcp(fusen.cdmigcvz3rle.us-east-2.rds.amazonaws.com:3306)/fusen", "输入需要序列化的ddl文件名, 不需要后缀.ddl")
flag.StringVar(&name, "name", "", "输入需要序列化的ddl文件名, 不需要后缀.ddl")
flag.StringVar(&mdir, "mdir", "", "输入需要生成model的Go文件所在目录")
flag.Parse()
if mdir != "" {
testGenDir = mdir
}
db, err := sql.Open("mysql", mysqluri)
if err != nil {
panic(err)
}
defer db.Close()
var tmcs []TableNameComment
if name == "-" {
tablenames := GetAllTableNames(mysqluri)
for _, testName := range tablenames {
// if testName != "fs_user_info" {
// continue
// }
cols, tname, tcomment := GetColsFromTable(testName, db)
GenFromPath(testGenDir, cols, tname, tcomment)
tmcs = append(tmcs, TableNameComment{
Name: tname,
GoName: toPascalCase(tname),
Comment: tcomment,
})
}
} else {
if name != "" {
testName = name
}
// log.Println(testName)
cols, tname, tcomment := GetColsFromTable(testName, db)
GenFromPath(testGenDir, cols, tname, tcomment)
tmcs = append(tmcs, TableNameComment{
Name: tname,
GoName: toPascalCase(tname),
Comment: tcomment,
})
}
GenAllModels(testGenDir, tmcs...)
// tablenames := GetAllTableNames(mysqluri)
// log.Println(tablenames)
// name
}
func GenFromPath(mdir string, cols []Column, tableName string, tableComment string) {
var importstr = "import (\"gorm.io/gorm\"\n"
// 匹配到主键定义
fcontent := "package gmodel\n"
structstr := "// %s %s\ntype %s struct {%s\n}\n"
pTableName := toPascalCase(tableName)
fieldstr := ""
for _, col := range cols {
fieldName := toPascalCase(col.Name)
typeName := typeForMysqlToGo[col.GetType()]
var defaultString string
if col.DefaultValue != nil {
switch typeName {
case "*int64", "*uint64", "*float64", "*bool":
defaultString = "default:" + strings.Trim(*col.DefaultValue, "'") + ";"
default:
defaultString = "default:" + *col.DefaultValue + ";"
}
} else {
switch typeName {
case "*string":
defaultString = "default:'';"
case "*time.Time":
defaultString = "default:'0000-00-00 00:00:00';"
case "*[]byte":
defaultString = "default:'';"
case "*int64", "*uint64":
defaultString = "default:0;"
case "*float64":
defaultString = "default: 0.0;"
case "*bool":
defaultString = "default:0;"
default:
// if fieldName == "Metadata" {
// log.Println()
// }
fieldName = "// " + fieldName + " " + col.Type
}
}
if typeName == "*time.Time" {
importstr += "\"time\"\n"
}
if col.IndexType == "primary_key" {
typeName = typeName[1:]
}
tagstr := "`gorm:"
gormTag := ""
if col.IndexType != "" {
gormTag += col.IndexType + ";"
}
gormTag += defaultString
if col.AutoIncrement {
gormTag += "auto_increment;"
}
tagstr += fmt.Sprintf("\"%s\"", gormTag)
tagstr += fmt.Sprintf(" json:\"%s\"`", col.Name)
fieldColStr := fmt.Sprintf("\n%s %s %s// %s", fieldName, typeName, tagstr, col.Comment)
fieldstr += fieldColStr
}
fcontent += importstr + ")\n"
fcontent += fmt.Sprintf(structstr, tableName, tableComment, pTableName, fieldstr)
modelstr := fmt.Sprintf(`type %sModel struct {db *gorm.DB
name string}`, pTableName)
fcontent += modelstr
fcontent += "\n"
newfuncstr := fmt.Sprintf(`func New%sModel(db *gorm.DB) *%sModel {return &%sModel{db:db,name:"%s"}}`, pTableName, pTableName, pTableName, tableName)
fcontent += newfuncstr
fcontent += "\n"
genGoFileName := fmt.Sprintf("%s/%s_gen.go", mdir, tableName)
f, err := os.OpenFile(genGoFileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
f.WriteString(fcontent)
err = f.Close()
if err != nil {
panic(err)
}
err = exec.Command("gofmt", "-w", genGoFileName).Run()
if err != nil {
panic(err)
}
fcontent = "package gmodel\n// TODO: 使用model的属性做你想做的"
genGoLogicFileName := fmt.Sprintf("%s/%s_logic.go", mdir, tableName)
// 使用 os.Stat 函数获取文件信息
_, err = os.Stat(genGoLogicFileName)
// 判断文件是否存在并输出结果
if os.IsNotExist(err) {
f2, err := os.OpenFile(genGoLogicFileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
f2.WriteString(fcontent)
err = f2.Close()
if err != nil {
panic(err)
}
fmt.Println(genGoLogicFileName, "create!")
} else {
fmt.Println(genGoLogicFileName, "exists")
}
}
type Column struct {
Name string
Type string
DefaultValue *string
Length int
Decimal int
Unsigned bool
NotNull bool
AutoIncrement bool
Comment string
IndexType string
}
func (col *Column) GetType() string {
content := col.Type
if col.Unsigned {
return content + " unsigned"
}
return content
}
var typeForMysqlToGo = map[string]string{
// 整数
"int": "*int64",
"integer": "*int64",
"tinyint": "*int64",
"smallint": "*int64",
"mediumint": "*int64",
"bigint": "*int64",
"year": "*int64",
"int unsigned": "*int64",
"integer unsigned": "*int64",
"tinyint unsigned": "*int64",
"smallint unsigned": "*int64",
"mediumint unsigned": "*int64",
"bigint unsigned": "*int64",
"bit": "*int64",
// 布尔类型
"bool": "*bool",
// 字符串
"enum": "*string",
"set": "*string",
"varchar": "*string",
"char": "*string",
"tinytext": "*string",
"mediumtext": "*string",
"text": "*string",
"longtext": "*string",
// 二进制
"binary": "*[]byte",
"varbinary": "*[]byte",
"blob": "*[]byte",
"tinyblob": "*[]byte",
"mediumblob": "*[]byte",
"longblob": "*[]byte",
// 日期时间
"date": "*time.Time",
"datetime": "*time.Time",
"timestamp": "*time.Time",
"time": "*time.Time",
// 浮点数
"float": "*float64",
"double": "*float64",
"decimal": "*float64",
}
func ParserDDL(ddl string) (result []Column, tableName, tableComment string) {
reTable := regexp.MustCompile(`CREATE TABLE +([^ ]+) +\(`)
reTableComment := regexp.MustCompile(`.+COMMENT='(.+)'$`)
reField := regexp.MustCompile("`([^`]+)` +([^ \n\\(\\,]+)(?:\\(([^)]+)\\))?( +unsigned| +UNSIGNED)?( +not +null| +NOT +NULL)?( +default +\\'[^\\']*'| +DEFAULT +\\'[^\\']*')?( +auto_increment| +AUTO_INCREMENT)?( comment '[^']*'| COMMENT '[^']*')?(,)?")
reIndex := regexp.MustCompile(`(?i)(PRIMARY|UNIQUE)?\s*(INDEX|KEY)\s*(` + "`([^`]*)`" + `)?\s*\(([^)]+)\)`)
reValue := regexp.MustCompile(` '(.+)'$`)
reDefaultValue := regexp.MustCompile(` ('.+')$`)
var fieldmap map[string]string = make(map[string]string)
indexMatches := reIndex.FindAllStringSubmatch(ddl, -1)
for _, m := range indexMatches {
idxAttr := strings.Trim(m[5], "`")
PrefixName := strings.ToUpper(m[1])
if PrefixName == "PRIMARY" {
fieldmap[idxAttr] = "primary_key"
} else if PrefixName == "UNIQUE" {
fieldmap[idxAttr] = "unique_key"
} else if PrefixName == "" {
fieldmap[idxAttr] = "index"
} else {
log.Fatal(PrefixName)
}
}
tableMatches := reTable.FindStringSubmatch(ddl)
tableName = strings.Trim(tableMatches[1], "`")
tableCommentMatches := reTableComment.FindStringSubmatch(ddl)
if len(tableCommentMatches) > 0 {
tableComment = strings.Trim(tableCommentMatches[1], "`")
}
// log.Println(tableName, tableComment)
fieldMatches := reField.FindAllStringSubmatch(ddl, -1)
for _, m := range fieldMatches {
if m[0] == "" {
continue
}
col := Column{
Name: m[1],
Type: strings.ToLower(m[2]),
}
if col.Type == "json" {
col.Type = "blob"
}
col.IndexType = fieldmap[col.Name]
if m[3] != "" {
maylen := strings.Split(m[3], ",")
if len(maylen) >= 1 {
clen, err := strconv.ParseInt(maylen[0], 10, 64)
if err != nil {
panic(err)
}
col.Length = int(clen)
}
if len(maylen) >= 2 {
clen, err := strconv.ParseInt(maylen[1], 10, 64)
if err != nil {
panic(err)
}
col.Decimal = int(clen)
}
}
if len(m[4]) > 0 {
col.Unsigned = true
}
if len(m[5]) > 0 {
col.NotNull = true
}
if len(m[6]) > 0 {
v := reDefaultValue.FindStringSubmatch(m[6])
if len(v) > 0 {
dv := string(v[1])
col.DefaultValue = &dv
}
}
if len(m[7]) > 0 {
col.AutoIncrement = true
}
if len(m[8]) > 0 {
v := reValue.FindStringSubmatch(m[8])
if len(v) > 0 {
col.Comment = v[1]
}
}
result = append(result, col)
// fmt.Println(col)
}
return result, tableName, tableComment
}