参考书籍《Go语言编程之旅》
书代码github:https://github.com/go-programming-tour-book/tour mygithub :
https://github.com/strive-after/cobra
在mysql中information_schema 提供了对数据库元数据的访问,可以获得mysql服务器自身相关的信息,如数据库、表名称、列数据类型、访问权限等
(1)
在做结构体 转换的时候很少会出现 多层嵌套
在面对类型比较简单的基本转换,可以用go 的template来实现
template 提供了2个库
test/template 基于模板输出文本内容
html/template 基于模板输出安全的html格式的内容可以理解 为其进行了转移,以避免受某些注入攻击
package main
import (
"os"
"strings"
"text/template"
)
/*
tempalte 模板定义
1.双层大括号{{}} 在template中所有actions 数据评估、控制流转都需要用标识符双层大括号包裹,其余模板内容军全部原样输出
2.点 会根据标识符进行模板变量的渲染 复杂的需要特殊处理 如果是指针调用会直接调用指针指向的值 如果生成类函数类型的值那么函数不会主动调用
3.函数调用 通过funcmap注册了title的自定义函数,在模板渲染中一共用了两类处理方法 直接用函数或者用| 传递给后面
*/
const templateText = `
OutPut 0: {{title .Name1}}
OutPut 1: {{title .Name2}}
OutPut 2: {{.Name3 | title}}
`
func main() {
/*
首先调用text/template 的new 其根据我们给定的名称标识 创建一个全新的模板对象,接下来调用parse方法
将常量templateText(预定义的带解析模板)解析为当前文本的模板主体内容,最后调用excute方法对模板进行
渲染,简单来说就是将传入的data胴体参数渲染到模板标识位上 我们将输出制定了os Stdout
*/
funcMap := template.FuncMap{"title":strings.Title}
//这里我们给定义新的template 然后func传入title 然后是吧首字母大写的函数 解析templateText
tpl, _ := template.New("go").Funcs(funcMap).Parse(templateText)
data := map[string]string{
"Name1":"go",
"Name2":"test",
"Name3":"tour",
}
//把data传入
tpl.Execute(os.Stdout,data)
}
// internal/sqlstruct/mysql.go
package sqlstruct
import (
"database/sql"
"errors"
"fmt"
//导入驱动
_ "github.com/go-sql-driver/mysql"
)
// 要获取表涨列的信息 需要访问数据库的information_schema数据库的COLUMNS表 做连接查询
//连接mysql的核心对象
type DBModel struct {
DBEngine *sql.DB
DBInfo *DBInfo
}
//存放mysql的基础信息
type DBInfo struct {
DBType string
Host string
UserName string
Password string
Charset string
}
//村粗COLUMNS表中我们需要的一些字段
type TableColumn struct {
ColumnName string
DataType string
IsNullable string
ColumnKey string
ColumnType string
ColumnComment string
}
//由于datatype字段类型与go结构体不是完全一致的 如varcher等 所以要座一层简单的类型转换 这里用枚举然后用map做映射获取
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}
}
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 = sql.Open(m.DBInfo.DBType, dsn)
if err != nil {
return err
}
return nil
}
//需要针对COLUMNS进行查询和数据组装
func (m *DBModel) GetColumns(dbName, tableName string) ([]*TableColumn, error) {
query := "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, " +
"IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT " +
"FROM COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? "
rows, err := m.DBEngine.Query(query, dbName, tableName)
if err != nil {
return nil, err
}
if rows == nil {
return nil, errors.New("没有数据")
}
defer rows.Close()
var columns []*TableColumn
for rows.Next() {
var column TableColumn
err := rows.Scan(&column.ColumnName, &column.DataType, &column.ColumnKey, &column.IsNullable, &column.ColumnType, &column.ColumnComment)
if err != nil {
return nil, err
}
columns = append(columns, &column)
}
return columns, nil
}
package sqlstruct
// internal/sqlstruct/template.go
import (
"fmt"
"os"
"text/template"
"test/internal/word"
)
//模板渲染 基本结构由一个go结构体 和其所属的tablename方法组成
/*
生成结果大致如下
type 大写驼峰的表的名称struct {
//注释
字段名 字段类型
... ...
}
func (model 大写驼峰的表名称) TableName() string {
return "表名称"
}
*/
const strcutTpl = `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}}"
}`
type StructTemplate struct {
strcutTpl string
}
//用来存储转换后的go结构体中的所有字段信息
type StructColumn struct {
Name string
Type string
Tag string
Comment string
}
//用来存储最终用于渲染的模板对象信息
type StructTemplateDB struct {
TableName string
Columns []*StructColumn
}
func NewStructTemplate() *StructTemplate {
return &StructTemplate{strcutTpl: strcutTpl}
}
// 通过查询COLUMNS表所组装得到的tbColums进一步转换
func (t *StructTemplate) AssemblyColumns(tbColumns []*TableColumn) []*StructColumn {
tplColumns := make([]*StructColumn, 0, len(tbColumns))
for _, column := range tbColumns {
tag := fmt.Sprintf("`"+"json:"+"\"%s\""+"`", column.ColumnName)
tplColumns = append(tplColumns, &StructColumn{
Name: column.ColumnName,
Type: DBTypeToStructType[column.DataType],
Tag: tag,
Comment: column.ColumnComment,
})
}
return tplColumns
}
//对模板自定义函数和模块对象进行处理
func (t *StructTemplate) Generate(tableName string, tplColumns []*StructColumn) error {
//声明sqlstruct模板对象 定义tocamelcase函数与word.UndersoreToUpperCamelCase 做绑定 等同于我们在模板中使用ToCamelCase函数就等于使用word.UndersoreToUpperCamelCase
tpl := template.Must(template.New("sql2struct").Funcs(template.FuncMap{
"ToCamelCase": word.UndersoreToUpperCamelCase,
}).Parse(t.strcutTpl))
tplDB := StructTemplateDB{
TableName: tableName,
Columns: tplColumns,
}
//最后调用excute进行渲染
err := tpl.Execute(os.Stdout, tplDB)
if err != nil {
return err
}
return nil
}
package cmd
// cmd/sql.go
import (
"log"
"test/internal/sqlstruct"
"github.com/spf13/cobra"
)
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) {},
}
var sql2structCmd = &cobra.Command{
Use: "struct",
Short: "sql转换",
Long: "sql转换",
Run: func(cmd *cobra.Command, args []string) {
dbInfo := &sqlstruct.DBInfo{
DBType: dbType,
Host: host,
UserName: username,
Password: password,
Charset: charset,
}
dbModel := sqlstruct.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 := sqlstruct.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", "", "", "请输入表名称")
}