关于go:golang-sql-转-struct-插件实现

30次阅读

共计 10776 个字符,预计需要花费 27 分钟才能阅读完成。

写 go curd 的时候,常常须要 sql 转 struct, 比拟麻烦, 写个主动转换的代码:
main.go

package main

import (
    "convert/convert"
    "flag"
    "fmt"
    "log"
    "os"
    "path"
)

const VERSION = "1.0.0"
const VersionText = "Convert of mysql schema to golang struct"

var saveFilePath string

func init() {pa, err := os.Getwd()
    if err != nil {fmt.Println("获取运行文件门路获取失败")
        return
    }
    saveFilePath = path.Join(pa, "models")
    if isexists, _ := PathExists(saveFilePath); !isexists {os.Mkdir(saveFilePath, os.ModePerm)
    }
}

func main() {connStr :="root:[email protected](127.0.0.1:3306)/test?charset=utf8mb4&parseTime=true"
    dsn := flag.String("dsn", connStr, "连贯数据库字符串")
    file := flag.String("file", saveFilePath, "保留门路")
    table := flag.String("table", ""," 要生成的表名 ") // 不填默认所有表
    realNameMethod := flag.String("realNameMethod", "TableName", "构造体对应的表名")
    packageName := flag.String("packageName", ""," 生成 struct 的包名 ( 默认为空的话, 则取名为: package models)")
    tagKey := flag.String("tagKey", "gorm", "字段 tag 的 key")
    prefix := flag.String("prefix", ""," 表前缀 ")
    version := flag.Bool("version", false, "版本号")
    v := flag.Bool("v", false, "版本号")
    enableJsonTag := flag.Bool("enableJsonTag", true, "是否增加 json 的 tag, 默认 false")
    enableFormTag := flag.Bool("enableFormTag", false, "是否增加 form 的 tag, 默认 false")

    // 版本号
    if *version || *v {fmt.Println(fmt.Sprintf("\n version: %s\n %s\n using -h param for more help \n", VERSION, VersionText))
        return
    }

    // 初始化
    t2t := convert.NewTable2Struct()
    // 个性化配置
    t2t.Config(&convert.T2tConfig{// json tag 是否转为驼峰 ( 大驼峰式),默认为 false,不转换
        JsonTagToHump:     true, //false=>json tag:  request_method  true=> json tag  RequestMethod
        JsonTagToFirstLow: true, // json  tag 首字母是否转换小写, 默认 true  => requestMethod
        // 构造体名称是否转为驼峰式,默认为 false
        StructNameToHump: true,
        // 如果字段首字母原本就是大写, 就不增加 tag, 默认 false 增加, true 不增加
        RmTagIfUcFirsted: false,
        // tag 的字段名字首字母是否转换为小写, 如果自身有大写字母的话, 默认 false 不转
        TagToLower: false,
        // 字段首字母大写的同时, 是否要把其余字母转换为小写, 默认 false 不转换
        UcFirstOnly: false,
        // 每个 struct 放入独自的文件, 默认 false, 放入同一个文件
        SeperatFile: false,
    })
    // 开始迁徙转换
    err := t2t.
        // 指定某个表, 如果不指定, 则默认全副表都迁徙
        Table(*table).
        // 表前缀
        Prefix(*prefix).
        // 是否增加 json tag
        EnableJsonTag(*enableJsonTag).
        EnableFormTag(*enableFormTag).
        // 生成 struct 的包名 (默认为空的话, 则取名为: package model)
        PackageName(*packageName).
        // tag 字段的 key 值, 默认是 gorm
        TagKey(*tagKey).
        // 是否增加构造体办法获取表名
        RealNameMethod(*realNameMethod).
        // 生成的构造体保留门路
        SavePath(*file).
        // 数据库 dsn
        Dsn(*dsn).
        // 执行
        Run()
    if err != nil {log.Println(err.Error())
    }
}

// PathExists 判断所给门路文件 / 文件夹是否存在
func PathExists(path string) (bool, error) {_, err := os.Stat(path)
    if err == nil {return true, nil}
    //isnotexist 来判断,是不是不存在的谬误
    if os.IsNotExist(err) {// 如果返回的谬误类型应用 os.isNotExist() 判断为 true,阐明文件或者文件夹不存在
        return false, nil
    }
    return false, err // 如果有谬误了,然而不是不存在的谬误,所以把这个谬误一成不变的返回
}

tableTostruct.go 代码:

package convert

import (
    "database/sql"
    "errors"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "log"
    "os"
    "os/exec"
    "path"
    "strings"
)

//map for converting mysql type to golang types
var typeForMysqlToGo = map[string]string{
    "int":                "int64",
    "integer":            "int64",
    "tinyint":            "int64",
    "smallint":           "int64",
    "mediumint":          "int64",
    "bigint":             "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",
    "blob":               "string",
    "tinyblob":           "string",
    "mediumblob":         "string",
    "longblob":           "string",
    "date":               "time.Time", // time.Time or string
    "datetime":           "time.Time", // time.Time or string
    "timestamp":          "time.Time", // time.Time or string
    "time":               "time.Time", // time.Time or string
    "float":              "float64",
    "double":             "float64",
    "decimal":            "float64",
    "binary":             "string",
    "varbinary":          "string",
}

type TableToStruct struct {
    dsn            string
    savePath       string
    db             *sql.DB
    table          string
    prefix         string
    config         *T2tConfig
    err            error
    realNameMethod string
    enableJsonTag  bool   // 是否增加 json 的 tag, 默认不增加
    enableFormTag  bool   // 是否增加 form 的 tag, 默认不增加
    packageName    string // 生成 struct 的包名 (默认为空的话, 则取名为: package model)
    tagKey         string // tag 字段的 key 值, 默认是 orm
    dateToTime     bool   // 是否将 date 相干字段转换为 time.Time, 默认否
}

type T2tConfig struct {
    StructNameToHump  bool // 构造体名称是否转为驼峰式,默认为 false
    RmTagIfUcFirsted  bool // 如果字段首字母原本就是大写, 就不增加 tag, 默认 false 增加, true 不增加
    TagToLower        bool // tag 的字段名字是否转换为小写, 如果自身有大写字母的话, 默认 false 不转
    JsonTagToHump     bool // json tag 是否转为驼峰 (大驼峰式),默认为 false,不转换
    JsonTagToFirstLow bool // json tag 首字母是否转换小写
    UcFirstOnly       bool // 字段首字母大写的同时, 是否要把其余字母转换为小写, 默认 false 不转换
    SeperatFile       bool // 每个 struct 放入独自的文件, 默认 false, 放入同一个文件
}

func NewTable2Struct() *TableToStruct {return &TableToStruct{}
}

func (t *TableToStruct) Dsn(d string) *TableToStruct {
    t.dsn = d
    return t
}

func (t *TableToStruct) TagKey(r string) *TableToStruct {
    t.tagKey = r
    return t
}

func (t *TableToStruct) PackageName(r string) *TableToStruct {
    t.packageName = r
    return t
}

func (t *TableToStruct) RealNameMethod(r string) *TableToStruct {
    t.realNameMethod = r
    return t
}

func (t *TableToStruct) SavePath(p string) *TableToStruct {
    t.savePath = p
    return t
}

func (t *TableToStruct) DB(d *sql.DB) *TableToStruct {
    t.db = d
    return t
}

func (t *TableToStruct) Table(tab string) *TableToStruct {
    t.table = tab
    return t
}

func (t *TableToStruct) Prefix(p string) *TableToStruct {
    t.prefix = p
    return t
}

func (t *TableToStruct) EnableJsonTag(p bool) *TableToStruct {
    t.enableJsonTag = p
    return t
}

func (t *TableToStruct) EnableFormTag(p bool) *TableToStruct {
    t.enableFormTag = p
    return t
}

func (t *TableToStruct) DateToTime(d bool) *TableToStruct {
    t.dateToTime = d
    return t
}

func (t *TableToStruct) Config(c *T2tConfig) *TableToStruct {
    t.config = c
    return t
}

// Run 生成逻辑
func (t *TableToStruct) Run() error {
    if t.config == nil {t.config = new(T2tConfig)
    }
    // 链接 mysql, 获取 db 对象
    t.dialMysql()
    if t.err != nil {return t.err}

    // 获取表和字段的 shcema
    tableColumns, err := t.getColumns()
    if err != nil {return err}

    // 包名
    var packageName string
    if t.packageName == "" {packageName = "package models\n\n"} else {packageName = fmt.Sprintf("package %s\n\n", t.packageName)
    }

    // 组装 struct
    var structContent string
    for tableRealName, item := range tableColumns {
        // 去除前缀
        if t.prefix != "" {tableRealName = tableRealName[len(t.prefix):]
        }
        tableName := tableRealName
        structName := tableName
        if t.config.StructNameToHump {structName = t.camelCase(structName)
        }

        switch len(tableName) {
        case 0:
        case 1:
            tableName = strings.ToUpper(tableName[0:1])
        default:
            // 字符长度大于 1 时
            tableName = strings.ToUpper(tableName[0:1]) + tableName[1:]
        }
        depth := 1
        structContent += "type" + structName + "struct {\n"
        for _, v := range item {//structContent += tab(depth) + v.ColumnName + "" + v.Type +" "+ v.Json +"\n"
            // 字段正文
            var clumnComment string
            if v.ColumnComment != "" {clumnComment = fmt.Sprintf("// %s", v.ColumnComment)
            }
            structContent += fmt.Sprintf("%s%s %s %s%s\n",
                tab(depth), v.ColumnName, v.Type, v.Tag, clumnComment)
        }
        structContent += tab(depth-1) + "}\n\n"

        // 增加 method 获取实在表名
        if t.realNameMethod != "" {structContent += fmt.Sprintf("func (*%s) %s() string {\n",
                structName, t.realNameMethod)
            structContent += fmt.Sprintf("%sreturn \"%s\"\n",
                tab(depth), tableRealName)
            structContent += "}\n\n"
        }

        // 如果为 true, 每个表依照 struct 离开寄存一个文件
        if t.config.SeperatFile {
            // 如果有引入 time.Time, 则须要引入 time 包
            var importContent string
            if strings.Contains(structContent, "time.Time") {importContent = "import \"time\"\n\n"}

            // 写入文件 struct
            var savePath = t.savePath
            savePath = path.Join(savePath, tableRealName+".go")

            filePath := fmt.Sprintf("%s", savePath)
            f, err := os.Create(filePath)
            if err != nil {log.Println("Can not write file")
                return err
            }
            defer f.Close()

            f.WriteString(packageName + importContent + structContent)

            cmd := exec.Command("gofmt", "-w", filePath)
            cmd.Run()
            structContent = ""
        }
    }
    // false, 多个表放入同一个文件
    if t.config.SeperatFile == false {
        // 如果有引入 time.Time, 则须要引入 time 包
        var importContent string
        if strings.Contains(structContent, "time.Time") {importContent = "import \"time\"\n\n"}

        // 写入文件 struct
        var savePath = t.savePath
        if t.table != "" {savePath = path.Join(savePath, t.table+".go")
        } else {savePath = path.Join(savePath, "models.go")
        }
        filePath := fmt.Sprintf("%s", savePath)
        f, err := os.Create(filePath)
        if err != nil {log.Println("Can not write file")
            return err
        }
        defer f.Close()

        f.WriteString(packageName + importContent + structContent)

        cmd := exec.Command("gofmt", "-w", filePath)
        cmd.Run()}
    log.Println("gen model finish!!!")

    return nil
}

func (t *TableToStruct) dialMysql() {
    if t.db == nil {
        if t.dsn == "" {t.err = errors.New("dsn 数据库配置缺失")
            return
        }
        t.db, t.err = sql.Open("mysql", t.dsn)
    }
    return
}

type column struct {
    ColumnName    string
    Type          string
    Nullable      string
    TableName     string
    ColumnComment string
    Tag           string
}

// Function for fetching schema definition of passed table
func (t *TableToStruct) getColumns(table ...string) (tableColumns map[string][]column, err error) {
    // 依据设置, 判断是否要把 date 相干字段替换为 string
    if t.dateToTime == false {typeForMysqlToGo["date"] = "string"
        typeForMysqlToGo["datetime"] = "string"
        typeForMysqlToGo["timestamp"] = "string"
        typeForMysqlToGo["time"] = "string"
    }
    tableColumns = make(map[string][]column)
    // sql
    var sqlStr = `SELECT COLUMN_NAME,DATA_TYPE,IS_NULLABLE,TABLE_NAME,COLUMN_COMMENT
        FROM information_schema.COLUMNS 
        WHERE table_schema = DATABASE()`
    // 是否指定了具体的 table
    if t.table != "" {sqlStr += fmt.Sprintf("AND TABLE_NAME ='%s'", t.prefix+t.table)
    }
    // sql 排序
    sqlStr += "order by TABLE_NAME asc, ORDINAL_POSITION asc"

    rows, err := t.db.Query(sqlStr)
    if err != nil {log.Println("Error reading table information:", err.Error())
        return
    }

    defer rows.Close()

    for rows.Next() {col := column{}
        err = rows.Scan(&col.ColumnName, &col.Type, &col.Nullable, &col.TableName, &col.ColumnComment)

        if err != nil {log.Println(err.Error())
            return
        }

        //col.Json = strings.ToLower(col.ColumnName)
        col.Tag = col.ColumnName
        col.ColumnName = t.camelCase(col.ColumnName)
        col.Type = typeForMysqlToGo[col.Type]
        jsonTag := col.Tag
        // 字段首字母自身大写, 是否须要删除 tag
        if t.config.RmTagIfUcFirsted &&
            col.ColumnName[0:1] == strings.ToUpper(col.ColumnName[0:1]) {col.Tag = "-"} else {
            // 是否须要将 tag 转换成小写
            if t.config.TagToLower {col.Tag = strings.ToLower(col.Tag)
                jsonTag = col.Tag
            }

            if t.config.JsonTagToHump {jsonTag = t.camelCase(jsonTag)
            }
            if t.config.JsonTagToFirstLow {jsonTag = FirstLower(jsonTag)
            }

            //if col.Nullable == "YES" {//    col.Json = fmt.Sprintf("`json:\"%s,omitempty\"`", col.Json)
            //} else {//}
        }
        if t.tagKey == "" {t.tagKey = "orm"} else if t.tagKey == "gorm" {col.Tag = "column:" + col.Tag}
        if t.enableJsonTag {//col.Json = fmt.Sprintf("`json:\"%s\"%s:\"%s\"`", col.Json, t.config.TagKey, col.Json)
            if t.enableFormTag {col.Tag = fmt.Sprintf("`%s:\"%s\"json:\"%s\"form:\"%s\"`", t.tagKey, col.Tag, jsonTag, jsonTag)
            } else {col.Tag = fmt.Sprintf("`%s:\"%s\"json:\"%s\"`", t.tagKey, col.Tag, jsonTag)
            }
        } else {col.Tag = fmt.Sprintf("`%s:\"%s\"`", t.tagKey, col.Tag)
        }
        //columns = append(columns, col)
        if _, ok := tableColumns[col.TableName]; !ok {tableColumns[col.TableName] = []column{}
        }
        tableColumns[col.TableName] = append(tableColumns[col.TableName], col)
    }
    return
}

func (t *TableToStruct) camelCase(str string) string {
    // 是否有表前缀, 设置了就先去除表前缀
    if t.prefix != "" {str = strings.Replace(str, t.prefix, "", 1)
    }
    var text string
    //for _, p := range strings.Split(name, "_") {for _, p := range strings.Split(str, "_") {
        // 字段首字母大写的同时, 是否要把其余字母转换为小写
        switch len(p) {
        case 0:
        case 1:
            text += strings.ToUpper(p[0:1])
        default:
            // 字符长度大于 1 时
            if t.config.UcFirstOnly == true {text += strings.ToUpper(p[0:1]) + strings.ToLower(p[1:])
            } else {text += strings.ToUpper(p[0:1]) + p[1:]
            }
        }
    }
    return text
}
func tab(depth int) string {return strings.Repeat("\t", depth)
}

// FirstUpper 字符串首字母大写
func FirstUpper(s string) string {
    if s == "" {return ""}
    return strings.ToUpper(s[:1]) + s[1:]
}

// FirstLower 字符串首字母小写
func FirstLower(s string) string {
    if s == "" {return ""}
    return strings.ToLower(s[:1]) + s[1:]
}

目录构造截图:

转换的 struct:

package models

type Region struct {
    Id         int64  `gorm:"column:id" json:"id"`                  // 主键
    RegionName string `gorm:"column:region_name" json:"regionName"` // 区域
    Timezone   string `gorm:"column:timezone" json:"timezone"`      // 时区
    CreateTime string `gorm:"column:create_time" json:"createTime"`
    UpdateTime string `gorm:"column:update_time" json:"updateTime"`
}

正文完
 0