完成序列化

This commit is contained in:
eson
2023-06-16 18:52:24 +08:00
parent 169c167d4a
commit 8679ba1ba3
164 changed files with 2253 additions and 663 deletions

39
generator/gen_test.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import (
"fmt"
"regexp"
"testing"
)
func TestA(t *testing.T) {
main3()
}
func main3() {
ddl := `CREATE TABLE fs_guest (
guest_id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
auth_key varchar(512) NOT NULL DEFAULT '' COMMENT 'jwt token',
status tinyint(3) unsigned DEFAULT '1' COMMENT '1正常 0不正常',
is_del tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 1删除',
created_at int(11) NOT NULL DEFAULT '0' COMMENT '添加时间',
updated_at int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
is_open_render tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否打开个性化渲染1开启0关闭',
is_thousand_face tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已经存在千人千面1存在0不存在',
is_low_rendering tinyint(1) unsigned zerofill NOT NULL DEFAULT '0' COMMENT '是否开启低渲染模型渲染',
is_remove_bg tinyint(1) NOT NULL DEFAULT '1' COMMENT '用户上传logo是否去除背景',
guid decimal(10,2) NOT NULL,
PRIMARY KEY (guest_id) USING BTREE,
UNIQUE KEY fs_guest_guid_IDX (guid) USING BTREE,
KEY fs_guest_is_del_IDX (is_del) USING BTREE,
KEY fs_guest_status_IDX (status) USING BTREE,
KEY fs_guest_created_at_IDX (created_at) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='游客表'`
// 正则表达式匹配
r, _ := regexp.Compile(`CREATE TABLE (\S+) \..*COMMENT='(.*)'`)
matches := r.FindStringSubmatch(ddl)
// 输出结果
fmt.Println("表名:", matches[1])
fmt.Println("注释:", matches[2])
}

View File

@@ -0,0 +1,12 @@
package main
import (
"testing"
_ "github.com/go-sql-driver/mysql"
)
func TestXMain(t *testing.T) {
// Now you can use the generated GORM model to interact with the database
}

View File

@@ -1,23 +1,24 @@
package main
import (
"database/sql"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/zeromicro/ddl-parser/parser"
"golang.org/x/text/cases"
"golang.org/x/text/language"
_ "github.com/go-sql-driver/mysql"
)
var ddlDir = "ddl"
var genDir = "model/gmodel_gen"
var testName = "fs_auth_item"
var testGenDir = "model/gmodel_gen"
func toPascalCase(s string) string {
words := strings.Split(s, "_")
@@ -27,257 +28,371 @@ func toPascalCase(s string) string {
return strings.Join(words, "")
}
func main() {
var name string // 需要序列化的单独文件名
var gdir string // 需要修改的序列化路径 model
var ddir string // 需要修改的序列化路径 ddl
flag.StringVar(&name, "name", "", "输入需要序列化的ddl文件名, 不需要后缀.ddl")
flag.StringVar(&gdir, "mdir", "", "输入需要生成model的Go文件所在目录")
flag.StringVar(&ddir, "ddir", "", "输入需要生成ddl的Go文件所在目录")
flag.Parse()
func GetAllTableNames(uri string) []string {
db, err := sql.Open("mysql", uri)
if err != nil {
panic(err)
}
defer db.Close()
if gdir != "" {
genDir = gdir
rows, err := db.Query("SHOW TABLES")
if err != nil {
panic(err)
}
if ddir != "" {
ddlDir = ddir
}
if name != "" {
name = fmt.Sprintf("%s/%s.sql", ddlDir, name)
GenFromPath(name)
} else {
matches, err := filepath.Glob(fmt.Sprintf("%s/*.sql", ddlDir))
if err != nil {
var tableNames []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err != nil {
panic(err)
}
for _, pth := range matches {
GenFromPath(pth)
}
tableNames = append(tableNames, tableName)
}
return tableNames
}
func GenFromPath(pth string) {
p, err := filepath.Abs(pth)
// "fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest"
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)
}
ddlfilestr, err := ioutil.ReadFile(pth)
return ParserDDL(ddl)
}
func main() {
var mysqluri string
var name string // 需要序列化的单独文件名
var mdir string // 需要修改的序列化路径 model
flag.StringVar(&mysqluri, "uri", "fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest", "输入需要序列化的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)
}
// PRIMARY KEY (`guest_id`) USING BTREE
re := regexp.MustCompile("PRIMARY\\s+KEY\\s+\\(\\s*`([^`]+)`\\s*\\)|`([^`]+)` [^\n]+PRIMARY\\s+KEY\\s+")
defer db.Close()
matches := re.FindStringSubmatch(string(ddlfilestr))
PrimaryStr := ""
if len(matches) > 0 {
PrimaryStr = matches[1]
if name == "-" {
tablenames := GetAllTableNames(mysqluri)
for _, testName := range tablenames {
cols, tname, tcomment := GetColsFromTable(testName, db)
GenFromPath(testGenDir, cols, tname, tcomment)
}
} else {
if name != "" {
testName = name
}
// log.Println(testName)
cols, tname, tcomment := GetColsFromTable(testName, db)
GenFromPath(testGenDir, cols, tname, tcomment)
}
// tablenames := GetAllTableNames(mysqluri)
// log.Println(tablenames)
// name
}
func GenFromPath(mdir string, cols []Column, tableName string, tableComment string) {
var importstr = "import (\"gorm.io/gorm\"\n"
// 匹配到主键定义
parser.NewParser()
result, err := parser.NewParser().From(p)
fcontent := "package model\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 {
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:
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.DefaultValue == "" {
// log.Panic(col, "需要默认值")
// }
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}`, pTableName)
fcontent += modelstr
fcontent += "\n"
newfuncstr := fmt.Sprintf(`func New%sModel(db *gorm.DB) *%sModel {return &%sModel{db}}`, pTableName, pTableName, pTableName)
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)
}
fcontent := "package model\n"
for _, table := range result {
structstr := "type %s struct {%s\n}\n"
tableName := toPascalCase(table.Name)
fieldstr := ""
for _, col := range table.Columns {
fieldName := toPascalCase(col.Name)
typeName := SQLTypeToGoTypeMap[col.DataType.Type()]
if typeName == "*time.Time" {
importstr += "\"time\"\n"
}
tagstr := "`gorm:"
if col.Name == PrimaryStr {
tagstr += "\"primary_key\""
typeName = typeName[1:]
} else {
tagstr += "\"-\""
}
tagstr += fmt.Sprintf(" json:\"%s\"`", col.Name)
fieldColStr := fmt.Sprintf("\n%s %s %s// %s", fieldName, typeName, tagstr, col.Constraint.Comment)
fieldstr += fieldColStr
}
fcontent += importstr + ")\n"
fcontent += fmt.Sprintf(structstr, tableName, fieldstr)
modelstr := fmt.Sprintf(`type %sModel struct {db *gorm.DB}`, tableName)
fcontent += modelstr
fcontent += "\n"
newfuncstr := fmt.Sprintf(`func New%sModel(db *gorm.DB) *%sModel {return &%sModel{db}}`, tableName, tableName, tableName)
fcontent += newfuncstr
fcontent += "\n"
genGoFileName := fmt.Sprintf("%s/%s_gen.go", genDir, table.Name)
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 model\n// TODO: 使用model的属性做你想做的"
genGoLogicFileName := fmt.Sprintf("%s/%s_logic.go", genDir, table.Name)
// 使用 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")
}
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 model\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")
}
}
const (
_ int = iota
LongVarBinary
LongVarChar
GeometryCollection
GeomCollection
LineString
MultiLineString
MultiPoint
MultiPolygon
Point
Polygon
Json
Geometry
Enum
Set
Bit
Time
Timestamp
DateTime
Binary
VarBinary
Blob
Year
Decimal
Dec
Fixed
Numeric
Float
Float4
Float8
Double
Real
TinyInt
SmallInt
MediumInt
Int
Integer
BigInt
MiddleInt
Int1
Int2
Int3
Int4
Int8
Date
TinyBlob
MediumBlob
LongBlob
Bool
Boolean
Serial
NVarChar
NChar
Char
Character
VarChar
TinyText
Text
MediumText
LongText
)
type Column struct {
Name string
Type string
DefaultValue *string
Length int
Decimal int
Unsigned bool
NotNull bool
AutoIncrement bool
Comment string
var SQLTypeToGoTypeMap = map[int]string{
LongVarBinary: "[]byte",
Binary: "[]byte",
VarBinary: "[]byte",
Blob: "[]byte",
TinyBlob: "[]byte",
MediumBlob: "[]byte",
LongBlob: "[]byte",
LongVarChar: "*string",
NVarChar: "*string",
NChar: "*string",
Char: "*string",
Character: "*string",
VarChar: "*string",
TinyText: "*string",
Text: "*string",
MediumText: "*string",
LongText: "*string",
Time: "*time.Time",
Timestamp: "*time.Time",
DateTime: "*time.Time",
Date: "*time.Time",
Year: "*int64",
TinyInt: "*int64",
SmallInt: "*int64",
MediumInt: "*int64",
Int: "*int64",
Integer: "*int64",
BigInt: "*int64",
MiddleInt: "*int64",
Int1: "*int64",
Int2: "*int64",
Int3: "*int64",
Int4: "*int64",
Int8: "*int64",
Serial: "*int64",
Decimal: "*float64",
Dec: "*float64",
Fixed: "*float64",
Numeric: "*float64",
Float: "*float64",
Float4: "*float64",
Float8: "*float64",
Double: "*float64",
Real: "*float64",
Bool: "*bool",
Boolean: "*bool",
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": "*uint64",
"integer unsigned": "*uint64",
"tinyint unsigned": "*uint64",
"smallint unsigned": "*uint64",
"mediumint unsigned": "*uint64",
"bigint unsigned": "*uint64",
"bit": "*uint64",
// 布尔类型
"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]),
}
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
}

View File

@@ -6,8 +6,8 @@ import (
func TestMain(t *testing.T) {
// args := []string{"-name", "fs_guest"}
ddlDir = "../" + ddlDir
genDir = "../" + genDir
testGenDir = "../" + testGenDir
// os.Args = []string{"cmd", "-name=fs_guest"}
main()