《Go语言编程之旅》学习04--SQL转GoStruct
我们继续使用学习单词格式转换 的项目目录
这篇文章是跟着《Go编程之旅》第1.4节 ,编写一个从已存在的数据库表中,将其列与类型直接转换为go的结构体的工具,但是连接数据库时改用了GORM ,默认使用MySQL数据库
需要转换的数据结构
如果你的数据库里已经有自己创建的表,那么可以用自己的表
如果没有的话,也可使用如下SQL创建一个新表
1 2 3 4 5 6 7 8 9 10 11 12
| create TABLE `blog_tag`( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT '' COMMENT '标签名称', `creare_on` int(10) unsigned DEFAULT '0' COMMENT'创建时间', `created_by` varchar(100) DEFAULT '' COMMENT '创建人', `modified_on` int(10) unsigned DEFAULT '0' COMMENT'修改时间', `modified_by` varchar(100) DEFAULT '' COMMENT '修改人', `deleted_on` int(10) unsigned DEFAULT'0' COMMENT'删除时间', `is_del` tinyint(3) unsigned DEFAULT '0' COMMENT '是否删除,0为未删除、1为已删除', `state` tinyint(3) unsigned DEFAULT '1' COMMENT'状态,0为禁用、1为启用', PRIMARY KEY (`id`) )ENGINE=InnODB DEFAULT CHARSET=utf8mb4 COMMENT='标签管理';
|
如何生成结构体
我们先来确定一下整体的思路,再来写代码
首先是获取到指定数据库中指定表中列的所有信息,然后将信息通过操作转换成结构体
获取数据肯定没问题,但重点是将数据转换为结构体,这里我们使用了text/template,文档:https://studygolang.com/pkgdoc 左侧往下翻,找到text/template
获取指定表所有列的信息
在我们的MySQL中,有information_schema
这样一个数据库
information_schema
是 MySQL 数据库的一个系统数据库,用于存储关于 MySQL 数据库和表的元数据信息。information_schema
数据库包含了一系列的表,用于存储不同层次和角度的元数据信息,这些信息包括:
- 数据库层次的信息:如数据库名、数据库表名、列名、列数据类型、列长度、列默认值等。
- 存储引擎层次的信息:如存储引擎名、存储引擎版本、表行数、表大小、索引等。
- 用户权限层次的信息:如用户、用户密码、用户权限等。
下面是 information_schema
数据库中一些常用的表:
SCHEMATA
:存储所有数据库的信息,包括数据库名、默认字符集、默认排序规则等。TABLES
:存储所有表的信息,包括表名、数据库名、表类型、表引擎、表行数等。COLUMNS
:存储所有列的信息,包括列名、列数据类型、列长度、列默认值等。STATISTICS
:存储所有索引的信息,包括索引名、索引类型、索引长度等。USER_PRIVILEGES
:存储用户权限的信息,包括用户、主机名、权限等。
通过查询 information_schema
数据库中的COLUMNS
表,我们便可以获取到列的各项信息
正式开始
请确定已经创建好表,并确保数据可以正常连接
连接数据库
在internal文件夹下新建文件夹sql2struct
,并在sql2struct
文件夹下新建文件mysql.go
我们使用的是GORM
来连接数据库,因此需要安装一下
1 2
| go get -u gorm.io/gorm go get -u gorm.io/driver/mysql
|
首先写入以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| type DBModel struct { DBEngine *gorm.DB DBInfo *DBInfo }
type DBInfo struct { DBType string Host string UserName string Password string Charset string }
type TableColumn struct { COLUMN_NAME string DATA_TYPE string IS_NULLABLE string COLUMN_KEY string COLUMN_TYPE string COLUMN_COMMENT string }
var DBTypeToStructType = map[string]string{ "int": "int32", "tinyint": "int8", "smallint": "int", "mediumint": "int64", "bigint": "int64", "bit": "int", "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", "datetime": "time.Time", "timestamp": "time.Time", "time": "time.Time", "float": "float64", "double": "float64", }
func NewDBModel(info *DBInfo) *DBModel { return &DBModel{DBInfo: info} }
|
接着就是链接数据库与查询的具体方法,同样是写在mysql.go
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import ( "fmt" "gorm.io/driver/mysql" "gorm.io/gorm" )
func (m *DBModel) Connect() error { var err error s := "%s:%s@tcp(%s)/information_schema?" + "charset=%s&parseTime=True&loc=Local" dsn := fmt.Sprintf(s, m.DBInfo.UserName, m.DBInfo.Password, m.DBInfo.Host, m.DBInfo.Charset) m.DBEngine, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) return err }
func (m *DBModel) GetColumns(dbName, tableName string) (*[]TableColumn, error) { var columns []TableColumn err := m.DBEngine.Table("COLUMNS").Where("TABLE_SCHEMA = ? and TABLE_NAME = ?", dbName, tableName).Scan(&columns).Error if err != nil { return nil, err }
return &columns, nil }
|
这里专门把import放出来,确定一下,不要导错了包
模板
在internal/sql2struct下新建文件template.go,写入以下内容
1 2 3 4 5 6 7 8
| const structTpl = `type {{.TableName | ToCamelCase}} struct { {{range .Columns}}{{ $length := len .Comment}}{{ if gt $length 0}} // {{.Comment}}{{else}} // {{.Name}}{{end}} {{ $typeLen := len .Type }}{{ if gt $typeLen 0 }}{{.Name | ToCamelCase}} {{.Type}} {{.Tag}}{{ else }}{{.Name}}{{ end }} {{end}}}
func (model {{.TableName | ToCamelCase}}) TableName() string{ return "{{.TableName}}" }`
|
稍微解释下里面的内容
双层大括号:也就是{{ }}
,”Action”—数据运算和控制单位—由”和“界定;在Action之外的所有文本都不做修改的拷贝到输出中。
点(DOT):会根据点(DOT)标识符进行模板变量的渲染,其参数可以为任何值,但特殊的复杂类型需进行特殊处理。例如,当为指针时,内部会在必要时自动表示为指针所指向的值。如果执行结果生成了一个函数类型的值,如结构体的函数类型字段,那么该函数不会自动调用。
这个模板生成的原型大概如下
1 2 3 4 5 6 7 8 9 10 11
| type 大写驼峰的表名称 struct{ 字段名 字段类型 字段名 字段类型 ... }
func (model 大写驼峰的表名称) TableName() string{ return "表名称" }
|
接着我们来渲染模板,还是在template.go文件中加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| type StructTemplate struct { structTpl string }
type StructColumn struct { Name string Type string Tag string Comment string }
type StructTemplateDB struct { TableName string Columns []*StructColumn }
func NewStructTemplate() *StructTemplate { return &StructTemplate{structTpl: structTpl} }
func (t *StructTemplate) AssemblyColumns(tbColumns *[]TableColumn) []*StructColumn { tplColumns := make([]*StructColumn, 0, len(*tbColumns)) for _, column := range *tbColumns { tag := fmt.Sprintf("`json:"+"\"%s\""+"`", column.COLUMN_NAME) tplColumns = append(tplColumns, &StructColumn{ Name: column.COLUMN_NAME, Type: DBTypeToStructType[column.DATA_TYPE], Tag: tag, Comment: column.COLUMN_COMMENT, }) } return tplColumns }
func (t *StructTemplate) Generate(tableName string, tplColumns []*StructColumn) error { tpl := template.Must(template.New("sql2struct").Funcs(template.FuncMap{ "ToCamelCase": word.UnderscoreToUpperCamelCase, }).Parse(t.structTpl))
tplDB := StructTemplateDB{ TableName: tableName, Columns: tplColumns, } err := tpl.Execute(os.Stdout, tplDB) return err }
|
命令编写
在cmd目录下创建sql.go文件
写入如下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import ( "github.com/spf13/cobra" "log" "wordConvertTool/internal/sql2struct" )
var ( username string password string host string charset string dbType string dbName string tableName string )
var sqlCmd = &cobra.Command{ Use: "sql", Short: "sql转换和处理", Long: "sql转换和处理", Run: func(cmd *cobra.Command, args []string) {}, }
func init() {}
|
首先就是一些变量的声明与sqlCmd的声明
然后就是将sqlCmd添加到命令中,在cmd/root.go文件的init函数中写入
1 2 3 4
| func init() { ... rootCmd.AddCommand(sqlCmd) }
|
接着来书写sqlCmd的子命令,在cmd/sql.go中写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| var sql2structCmd = &cobra.Command{ Use: "struct", Short: "sql转换", Long: "sql转换", Run: func(cmd *cobra.Command, args []string) { dbInfo := &sql2struct.DBInfo{ DBType: dbType, Host: host, UserName: username, Password: password, Charset: charset, } dbModel := sql2struct.NewDBModel(dbInfo) err := dbModel.Connect() if err != nil { log.Fatalf("dbModel.Connect err: %v", err) } columns, err := dbModel.GetColumns(dbName, tableName) if err != nil { log.Fatalf("dbModel.GetColumns err: %v", err) } template := sql2struct.NewStructTemplate() templateColumns := template.AssemblyColumns(columns) err = template.Generate(tableName, templateColumns) if err != nil { log.Fatalf("template.Generate err : %v", err) } }, }
func init() { sqlCmd.AddCommand(sql2structCmd) sql2structCmd.Flags().StringVarP(&username, "username", "", "", "请输入数据库账号") sql2structCmd.Flags().StringVarP(&password, "password", "", "", "请输入数据库密码") sql2structCmd.Flags().StringVarP(&host, "host", "", "127.0.0.1:3306", "请输入数据库Host") sql2structCmd.Flags().StringVarP(&charset, "charset", "", "utf8mb4", "请输入数据库的编码") sql2structCmd.Flags().StringVarP(&dbType, "type", "", "mysql", "请输入数据库的类型") sql2structCmd.Flags().StringVarP(&dbName, "db", "", "", "请输入数据库名称") sql2structCmd.Flags().StringVarP(&tableName, "table", "", "", "请输入表名") }
|
到这里代码就写完了,我们测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| go run main.go sql struct --username 数据库账号 --password 数据库密码 --host 主机地址 --db 数据库名 --table 需要转换的表名
type BlogTag struct { // 创建时间 CreareOn int32 `json:"creare_on"` // 创建人 CreatedBy string `json:"created_by"` // 删除时间 DeletedOn int32 `json:"deleted_on"` // id Id int32 `json:"id"` // 是否删除,0为未删除、1为已删除 IsDel int8 `json:"is_del"` // 修改人 ModifiedBy string `json:"modified_by"` // 修改时间 ModifiedOn int32 `json:"modified_on"` // 标签名称 Name string `json:"name"` // 状态,0为禁用、1为启用 State int8 `json:"state"` }
func (model BlogTag) TableName() string{ return "blog_tag" }
|
如果你发现,字段tag的"
都变成了"
,那就是template的包导入错了,应该导入text/template
包
总结
经过这几个命令行工具案例的学习,基本上了解了cobra的使用方式,以及这种目录结构的妙用