goctl added

This commit is contained in:
kim
2020-07-29 17:11:41 +08:00
parent b1975d29a7
commit 121323b8c3
142 changed files with 10690 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
package configgen
const configTemplate = `// TODO replace * to your real value
{
"WithCache": false,
"Force": true,
"Username": "***",
"Password": "***",
"Address": "**",
"TableSchema":"*",
"Tables": [
"**"
]
}
`

View File

@@ -0,0 +1,29 @@
package configgen
import (
"fmt"
"os"
"zero/tools/modelctl/model"
"github.com/urfave/cli"
)
var (
configFileName = "config.json"
)
func ConfigCommand(_ *cli.Context) error {
_, err := os.Stat(configFileName)
if err == nil {
return nil
}
file, err := os.OpenFile(configFileName, os.O_CREATE|os.O_WRONLY, model.ModeDirPerm)
if err != nil {
return err
}
file.WriteString(configTemplate)
defer file.Close()
fmt.Println("config json template generate done ... ")
return nil
}

View File

@@ -0,0 +1,57 @@
package modelgen
import (
"zero/core/stores/sqlx"
)
type (
FieldModel struct {
dataSource string
conn sqlx.SqlConn
table string
}
Field struct {
// 字段名称,下划线
Name string `db:"name"`
// 字段数据类型
Type string `db:"type"`
// 字段顺序
Position int `db:"position"`
// 字段注释
Comment string `db:"comment"`
// key
Primary string `db:"k"`
}
Table struct {
Name string `db:"name"`
}
)
func NewFieldModel(dataSource, table string) *FieldModel {
return &FieldModel{conn: sqlx.NewMysql(dataSource), table: table}
}
func (fm *FieldModel) findTables() ([]string, error) {
querySql := `select TABLE_NAME AS name from COLUMNS where TABLE_SCHEMA = ? GROUP BY TABLE_NAME`
var tables []*Table
err := fm.conn.QueryRows(&tables, querySql, fm.table)
if err != nil {
return nil, err
}
tableList := make([]string, 0)
for _, item := range tables {
tableList = append(tableList, item.Name)
}
return tableList, nil
}
func (fm *FieldModel) findColumns(tableName string) ([]*Field, error) {
querySql := `select ` + queryRows + ` from COLUMNS where TABLE_SCHEMA = ? and TABLE_NAME = ?`
var resp []*Field
err := fm.conn.QueryRows(&resp, querySql, fm.table, tableName)
if err != nil {
return nil, err
}
return resp, nil
}

View File

@@ -0,0 +1,42 @@
package modelgen
import (
"errors"
"fmt"
"strings"
"zero/core/lang"
"github.com/urfave/cli"
)
func FileModelCommand(c *cli.Context) error {
configFile := c.String("config")
if len(configFile) == 0 {
return errors.New("missing config value")
}
lang.Must(genModelWithConfigFile(configFile))
return nil
}
func CmdModelCommand(c *cli.Context) error {
address := c.String("address")
force := c.Bool("force")
schema := c.String("schema")
redis := c.Bool("redis")
if len(address) == 0 {
return errors.New("missing [-address|-a]")
}
if len(schema) == 0 {
return errors.New("missing [--schema|-s]")
}
addressArr := strings.Split(address, "@")
if len(addressArr) < 2 {
return errors.New("the address format is incorrect")
}
user := addressArr[0]
host := addressArr[1]
address = fmt.Sprintf("%v@tcp(%v)/information_schema", user, host)
lang.Must(genModelWithDataSource(address, schema, force, redis, nil))
return nil
}

View File

@@ -0,0 +1,164 @@
package modelgen
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"text/template"
"zero/tools/modelctl/model"
)
var (
queryRows = `COLUMN_NAME AS name,ORDINAL_POSITION AS position,DATA_TYPE AS type,COLUMN_KEY AS k,COLUMN_COMMENT AS comment`
)
type (
Config struct {
// 是否需要生成redis缓存代码逻辑
WithCache bool
// 是否强制覆盖已有文件,如果是将导致原已修改文件找不回
Force bool
// mysql访问用户
Username string
// mysql访问密码
Password string
// mysql连接地址
Address string
// 库名
TableSchema string
// 待生成model所依赖的表
Tables []string `json:"Tables,omitempty"`
}
)
// 生成model相关go文件
func genModelWithConfigFile(path string) error {
bts, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var c Config
err = json.Unmarshal(bts, &c)
if err != nil {
return err
}
dataSourceTemplate := `{{.Username}}:{{.Password}}@tcp({{.Address}})/information_schema`
tl, err := template.New("").Parse(dataSourceTemplate)
if err != nil {
return err
}
var dataSourceBuffer = new(bytes.Buffer)
err = tl.Execute(dataSourceBuffer, c)
if err != nil {
return err
}
err = genModelWithDataSource(dataSourceBuffer.String(), c.TableSchema, c.Force, c.WithCache, c.Tables)
if err != nil {
return err
}
return nil
}
func genModelWithDataSource(dataSource, schema string, force, redis bool, tables []string) error {
fieldModel := NewFieldModel(dataSource, schema)
if len(tables) == 0 {
tableList, err := fieldModel.findTables()
if err != nil {
return err
}
tables = append(tables, tableList...)
}
// 暂定package为model
packageName := "model"
utilTemplate := &Template{Package: packageName, WithCache: redis}
err := generateUtilModel(force, utilTemplate)
if err != nil {
return err
}
for _, table := range tables {
fieldList, err := fieldModel.findColumns(table)
if err != nil {
return err
}
modelTemplate, err := generateModelTemplate(packageName, table, fieldList)
if err != nil {
return err
}
modelTemplate.WithCache = redis
err = generateSqlModel(force, modelTemplate)
if err != nil {
return err
}
}
fmt.Println("model generate done ...")
return nil
}
// 生成util model
func generateUtilModel(force bool, data *Template) error {
tl, err := template.New("").Parse(utilTemplateText)
if err != nil {
return err
}
fileName := "util.go"
_, err = os.Stat(fileName)
if err == nil {
if !force {
return nil
}
os.Remove(fileName)
}
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, model.ModeDirPerm)
if err != nil {
return err
}
defer file.Close()
err = tl.Execute(file, data)
if err != nil {
return err
}
cmd := exec.Command("goimports", "-w", fileName)
err = cmd.Run()
if err != nil {
return err
}
return nil
}
// 生成sql对应model
func generateSqlModel(force bool, data *Template) error {
tl, err := template.New("").Parse(modelTemplateText)
if err != nil {
return err
}
fileName := strings.ToLower(data.ModelCamelWithLowerStart + "model.go")
_, err = os.Stat(fileName)
if err == nil {
if !force {
fmt.Println(fileName + " already exists")
return nil
}
os.Remove(fileName)
}
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
return err
}
defer file.Close()
err = tl.Execute(file, data)
if err != nil {
return err
}
cmd := exec.Command("goimports", "-w", fileName)
err = cmd.Run()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,247 @@
package modelgen
import (
"bytes"
"errors"
"fmt"
"strings"
"text/template"
"zero/tools/modelctl/model"
"zero/tools/modelctl/util"
)
type (
Query struct {
Rows string
Args string
Values string
}
Update struct {
Rows string
Values string
}
Template struct {
Package string
PrimaryKeyField string
PrimaryKeyFieldCamel string
PrimaryKeyType string
ModelCamelWithLowerStart string
ModelLowerSplitByPound string
ModelCamelWithUpperStart string
Fields string
Abbr string
WithCache bool
Insert Query
// 包含where语句
Update Update
}
StructField struct {
// 字段名称,下划线
NameWithUnderline string
// 字段名称,驼峰式,大写开头
NameCamelWithUpperStart string
// 字段数据类型
DataType string
// 字段注释
Comment string
// 是否为主键
PrimaryKey bool
}
)
const (
fieldTemplateText = "{{.NameCamelWithUpperStart}} {{.DataType}} `db:\"{{.NameWithUnderline}}\" json:\"{{.NameWithUnderline}},omitempty\"` {{.Comment}}"
modelTemplateText = `package {{.Package}}
import (
"strings"
"time"
"zero/core/stores/redis"
"zero/core/stores/sqlx"
"zero/service/shared/builderx"
"zero/service/shared/cache"
)
var (
{{.ModelCamelWithLowerStart}}QueryRows = strings.Join(builderx.FieldNames(&{{.ModelCamelWithUpperStart}}{}), ",")
{{if .WithCache}}{{.ModelCamelWithLowerStart}}CachePrefix = "xjy#{{.ModelLowerSplitByPound}}#"
{{.ModelCamelWithLowerStart}}Expire = 7 * 24 * 3600{{end}}
)
type (
{{.ModelCamelWithUpperStart}}Model struct {
{{if .WithCache}} *CachedModel{{else}}
table string
conn sqlx.SqlConn{{end}}
}
{{.ModelCamelWithUpperStart}} struct {
{{.Fields}}
}
)
func New{{.ModelCamelWithUpperStart}}Model(table string, conn sqlx.SqlConn{{if .WithCache}}, rds *redis.Redis{{end}}) *{{.ModelCamelWithUpperStart}}Model {
{{if .WithCache}} return &{{.ModelCamelWithUpperStart}}Model{NewCachedModel(conn, table, rds)}
{{else}}return &{{.ModelCamelWithUpperStart}}Model{table:table,conn:conn}{{end}}
}
func ({{.Abbr}} *{{.ModelCamelWithUpperStart}}Model) Insert(data *{{.ModelCamelWithUpperStart}}) error {
querySql:="insert into "+{{.Abbr}}.table+" ({{.Insert.Rows}}) value ({{.Insert.Args}})"
_, err := {{.Abbr}}.conn.Exec(querySql, {{.Insert.Values}})
return err
}
func ({{.Abbr}} *{{.ModelCamelWithUpperStart}}Model) Update(data *{{.ModelCamelWithUpperStart}}) error {
{{if .WithCache}}err := {{.Abbr}}.cleanCache(data.{{.PrimaryKeyField}})
if err != nil {
return err
}
querySql := "update " + {{.Abbr}}.table + " set {{.Update.Rows}}"
_, err = {{.Abbr}}.conn.Exec(querySql,{{.Update.Values}} )
return err
{{else}}querySql := "update " + {{.Abbr}}.table + " set {{.Update.Rows}}"
_, err := {{.Abbr}}.conn.Exec(querySql,{{.Update.Values}} )
return err{{end}}
}
func ({{.Abbr}} *{{.ModelCamelWithUpperStart}}Model) FindOne({{.PrimaryKeyFieldCamel}} {{.PrimaryKeyType}})(*{{.ModelCamelWithUpperStart}},error){
querySql:="select "+{{.ModelCamelWithLowerStart}}QueryRows+" from "+{{.Abbr}}.table+" where {{.PrimaryKeyFieldCamel}} = ? limit 1"
var resp {{.ModelCamelWithUpperStart}}
{{if .WithCache}}key := cache.FormatKey({{.ModelCamelWithLowerStart}}CachePrefix,{{.PrimaryKeyFieldCamel}})
err := {{.Abbr}}.QueryRow(&resp, key, {{.ModelCamelWithLowerStart}}Expire, func(conn sqlx.Session, v interface{}) error {
return conn.QueryRow(v, querySql, {{.PrimaryKeyFieldCamel}})
})
if err != nil {
if err == sqlx.ErrNotFound {
return nil, ErrNotFound
}
return nil, err
}
{{else}}err := {{.Abbr}}.conn.QueryRow(&resp, querySql, {{.PrimaryKeyFieldCamel}})
if err != nil {
if err == sqlx.ErrNotFound {
return nil, ErrNotFound
}
return nil, err
}{{end}}
return &resp, nil
}
func ({{.Abbr}} *{{.ModelCamelWithUpperStart}}Model) DeleteById({{.PrimaryKeyFieldCamel}} {{.PrimaryKeyType}}) error {
{{if .WithCache}}err := {{.Abbr}}.cleanCache({{.PrimaryKeyFieldCamel}})
if err != nil {
return err
}
querySql := "delete from " + {{.Abbr}}.table + " where {{.PrimaryKeyFieldCamel}} = ? "
_, err = {{.Abbr}}.conn.Exec(querySql, {{.PrimaryKeyFieldCamel}})
return err
{{else}}querySql := "delete from " + {{.Abbr}}.table + " where {{.PrimaryKeyFieldCamel}} = ? "
_, err := {{.Abbr}}.conn.Exec(querySql, {{.PrimaryKeyFieldCamel}})
return err{{end}}
}
{{if .WithCache}}
func ({{.Abbr}} *{{.ModelCamelWithUpperStart}}Model) cleanCache({{.PrimaryKeyFieldCamel}} {{.PrimaryKeyType}}) error {
key := cache.FormatKey({{.ModelCamelWithLowerStart}}CachePrefix,{{.PrimaryKeyFieldCamel}})
_, err := {{.Abbr}}.rds.Del(key)
return err
}
{{end}}
`
)
func generateModelTemplate(packageName, table string, fileds []*Field) (*Template, error) {
list := make([]*StructField, 0)
var containsPrimaryKey bool
for _, item := range fileds {
goType, ok := model.CommonMysqlDataTypeMap[item.Type]
if !ok {
return nil, errors.New(fmt.Sprintf("table:%v,the data type %v of mysql does not match", table, item.Type))
}
if !containsPrimaryKey {
containsPrimaryKey = item.Primary == "PRI"
}
list = append(list, &StructField{
NameWithUnderline: item.Name,
NameCamelWithUpperStart: util.FmtUnderLine2Camel(item.Name, true),
DataType: goType,
Comment: item.Comment,
PrimaryKey: item.Primary == "PRI",
})
}
if !containsPrimaryKey {
return nil, errors.New(fmt.Sprintf("table:%v,primary key does not exist", table))
}
var structBuffer, insertRowsBuffer, insertArgBuffer, insertValuesBuffer, updateRowsBuffer, updateValuesBuffer bytes.Buffer
var primaryField *StructField
for index, item := range list {
out, err := convertField(item)
if err != nil {
return nil, err
}
structBuffer.WriteString(out + "\n")
if !item.PrimaryKey {
insertRowsBuffer.WriteString(item.NameWithUnderline)
insertArgBuffer.WriteString("?")
insertValuesBuffer.WriteString("data." + item.NameCamelWithUpperStart)
updateRowsBuffer.WriteString(item.NameWithUnderline + "=?")
updateValuesBuffer.WriteString("data." + item.NameCamelWithUpperStart)
if index < len(list)-1 {
insertRowsBuffer.WriteString(",")
insertArgBuffer.WriteString(",")
insertValuesBuffer.WriteString(",")
updateRowsBuffer.WriteString(",")
}
updateValuesBuffer.WriteString(",")
} else {
primaryField = item
}
}
updateRowsBuffer.WriteString(" where " + primaryField.NameWithUnderline + "=?")
updateValuesBuffer.WriteString(" data." + primaryField.NameCamelWithUpperStart)
modelSplitByPoundArr := strings.Replace(table, "_", "#", -1)
templateStruct := Template{
Package: packageName,
PrimaryKeyField: primaryField.NameCamelWithUpperStart,
PrimaryKeyFieldCamel: primaryField.NameWithUnderline,
PrimaryKeyType: primaryField.DataType,
ModelCamelWithLowerStart: util.FmtUnderLine2Camel(table, false),
ModelLowerSplitByPound: modelSplitByPoundArr,
ModelCamelWithUpperStart: util.FmtUnderLine2Camel(table, true),
Fields: structBuffer.String(),
Abbr: util.Abbr(table) + "m",
Insert: Query{
Rows: insertRowsBuffer.String(),
Args: insertArgBuffer.String(),
Values: insertValuesBuffer.String(),
},
Update: Update{
Rows: updateRowsBuffer.String(),
Values: updateValuesBuffer.String(),
},
}
return &templateStruct, nil
}
func convertField(field *StructField) (string, error) {
if strings.TrimSpace(field.Comment) != "" {
field.Comment = "// " + field.Comment
}
tl, err := template.New("").Parse(fieldTemplateText)
if err != nil {
return "", err
}
buf := bytes.NewBufferString("")
err = tl.Execute(buf, field)
if err != nil {
return "", err
}
return buf.String(), nil
}

View File

@@ -0,0 +1,34 @@
package modelgen
const (
utilTemplateText = `package {{.Package}}
import (
"errors"
{{if .WithCache}}"zero/core/stores/redis"
"zero/core/stores/sqlc"
"zero/core/stores/sqlx"{{end}}
)
{{if .WithCache}}
type CachedModel struct {
table string
conn sqlx.SqlConn
rds *redis.Redis
sqlc.CachedConn
}
func NewCachedModel(conn sqlx.SqlConn, table string, rds *redis.Redis) *CachedModel {
return &CachedModel{
table: table,
conn: conn,
rds: rds,
CachedConn: sqlc.NewCachedConn(conn, rds),
}
}
{{end}}
var (
ErrNotFound = errors.New("not found")
)
`
)

View File

@@ -0,0 +1,34 @@
package model
var (
CommonMysqlDataTypeMap = map[string]string{
"tinyint": "int",
"smallint": "int",
"mediumint": "int64",
"int": "int64",
"integer": "int64",
"bigint": "int64",
"float": "float32",
"double": "float64",
"decimal": "float64",
"date": "time.Time",
"time": "string",
"year": "int",
"datetime": "time.Time",
"timestamp": "time.Time",
"char": "string",
"varchar": "string",
"tinyblob": "string",
"tinytext": "string",
"blob": "string",
"text": "string",
"mediumblob": "string",
"mediumtext": "string",
"longblob": "string",
"longtext": "string",
}
)
const (
ModeDirPerm = 0755
)