From d21d770b5bf64c26968aa3ac93eff7a8d0557ad8 Mon Sep 17 00:00:00 2001 From: Keson Date: Wed, 19 Aug 2020 10:41:19 +0800 Subject: [PATCH] goctl model reactor (#15) * reactor sql generation * reactor sql generation * add console & example * optimize unit test & add document * modify default config * remove test file * Revert "remove test file" This reverts commit 81041f9e * fix stringx.go & optimize example * remove unused code --- go.mod | 2 + go.sum | 6 + tools/goctl/feature/feature.go | 6 +- tools/goctl/goctl.go | 16 +- tools/goctl/goctl.md | 80 +-- tools/goctl/model/sql/README.MD | 625 +++++++----------- tools/goctl/model/sql/builderx/builder.go | 97 +++ .../goctl/model/sql/builderx/builder_test.go | 101 +++ tools/goctl/model/sql/command/command.go | 26 + .../model/sql/converter/types.go} | 30 +- tools/goctl/model/sql/example/generator.sh | 4 + tools/goctl/model/sql/example/sql/user.sql | 15 + tools/goctl/model/sql/gen/convert.go | 108 --- tools/goctl/model/sql/gen/delete.go | 62 +- tools/goctl/model/sql/gen/field.go | 40 +- tools/goctl/model/sql/gen/findallbyfield.go | 55 -- tools/goctl/model/sql/gen/findallbylimit.go | 63 -- tools/goctl/model/sql/gen/findone.go | 39 +- tools/goctl/model/sql/gen/fineonebyfield.go | 76 +-- tools/goctl/model/sql/gen/gen.go | 196 ++++++ tools/goctl/model/sql/gen/imports.go | 22 +- tools/goctl/model/sql/gen/insert.go | 40 +- tools/goctl/model/sql/gen/keys.go | 119 +--- tools/goctl/model/sql/gen/keys_test.go | 100 --- tools/goctl/model/sql/gen/model.go | 86 --- tools/goctl/model/sql/gen/new.go | 25 +- tools/goctl/model/sql/gen/shared.go | 99 --- tools/goctl/model/sql/gen/split.go | 23 + tools/goctl/model/sql/gen/tag.go | 21 +- tools/goctl/model/sql/gen/types.go | 27 +- tools/goctl/model/sql/gen/update.go | 56 +- tools/goctl/model/sql/gen/vars.go | 43 +- tools/goctl/model/sql/parser/error.go | 11 + tools/goctl/model/sql/parser/parser.go | 135 ++++ tools/goctl/model/sql/parser/parser_test.go | 27 + tools/goctl/model/sql/sqlctl.go | 22 - tools/goctl/model/sql/template/delete.go | 16 +- tools/goctl/model/sql/template/errors.go | 2 +- tools/goctl/model/sql/template/field.go | 4 +- tools/goctl/model/sql/template/find.go | 87 +-- tools/goctl/model/sql/template/import.go | 25 +- tools/goctl/model/sql/template/insert.go | 9 +- tools/goctl/model/sql/template/model.go | 2 +- tools/goctl/model/sql/template/new.go | 8 +- tools/goctl/model/sql/template/tag.go | 2 +- tools/goctl/model/sql/template/types.go | 8 +- tools/goctl/model/sql/template/update.go | 12 +- tools/goctl/model/sql/template/vars.go | 14 +- tools/goctl/model/sql/util/stringurtl_test.go | 12 - tools/goctl/model/sql/util/stringutil.go | 48 -- tools/goctl/util/console/console.go | 58 ++ tools/goctl/util/stringx/string.go | 126 ++++ tools/goctl/util/stringx/string_test.go | 42 ++ tools/goctl/util/templatex/templatex.go | 67 ++ .../model/configtemplategen/configtemplate.go | 15 - tools/modelctl/model/configtemplategen/gen.go | 28 - tools/modelctl/model/modelgen/fieldmodel.go | 55 -- tools/modelctl/model/modelgen/gen.go | 41 -- tools/modelctl/model/modelgen/genmodel.go | 164 ----- .../modelctl/model/modelgen/modeltemplate.go | 247 ------- tools/modelctl/model/modelgen/utiltemplate.go | 34 - tools/modelctl/modelctl.go | 71 -- tools/modelctl/modelctl.md | 60 -- tools/modelctl/util/util.go | 51 -- 64 files changed, 1505 insertions(+), 2306 deletions(-) create mode 100644 tools/goctl/model/sql/builderx/builder.go create mode 100644 tools/goctl/model/sql/builderx/builder_test.go create mode 100644 tools/goctl/model/sql/command/command.go rename tools/{modelctl/model/vars.go => goctl/model/sql/converter/types.go} (51%) create mode 100644 tools/goctl/model/sql/example/generator.sh create mode 100644 tools/goctl/model/sql/example/sql/user.sql delete mode 100644 tools/goctl/model/sql/gen/convert.go delete mode 100644 tools/goctl/model/sql/gen/findallbyfield.go delete mode 100644 tools/goctl/model/sql/gen/findallbylimit.go create mode 100644 tools/goctl/model/sql/gen/gen.go delete mode 100644 tools/goctl/model/sql/gen/keys_test.go delete mode 100644 tools/goctl/model/sql/gen/model.go delete mode 100644 tools/goctl/model/sql/gen/shared.go create mode 100644 tools/goctl/model/sql/gen/split.go create mode 100644 tools/goctl/model/sql/parser/error.go create mode 100644 tools/goctl/model/sql/parser/parser.go create mode 100644 tools/goctl/model/sql/parser/parser_test.go delete mode 100644 tools/goctl/model/sql/sqlctl.go delete mode 100644 tools/goctl/model/sql/util/stringurtl_test.go delete mode 100644 tools/goctl/model/sql/util/stringutil.go create mode 100644 tools/goctl/util/console/console.go create mode 100644 tools/goctl/util/stringx/string.go create mode 100644 tools/goctl/util/stringx/string_test.go create mode 100644 tools/goctl/util/templatex/templatex.go delete mode 100644 tools/modelctl/model/configtemplategen/configtemplate.go delete mode 100644 tools/modelctl/model/configtemplategen/gen.go delete mode 100644 tools/modelctl/model/modelgen/fieldmodel.go delete mode 100644 tools/modelctl/model/modelgen/gen.go delete mode 100644 tools/modelctl/model/modelgen/genmodel.go delete mode 100644 tools/modelctl/model/modelgen/modeltemplate.go delete mode 100644 tools/modelctl/model/modelgen/utiltemplate.go delete mode 100644 tools/modelctl/modelctl.go delete mode 100644 tools/modelctl/modelctl.md delete mode 100644 tools/modelctl/util/util.go diff --git a/go.mod b/go.mod index 22c3fa4f..e458bc81 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/go-redis/redis v6.15.7+incompatible github.com/go-sql-driver/mysql v1.5.0 + github.com/go-xorm/builder v0.3.4 github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/mock v1.4.3 @@ -42,6 +43,7 @@ require ( github.com/stretchr/testify v1.5.1 github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect github.com/urfave/cli v1.22.4 + github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 go.uber.org/automaxprocs v1.3.0 diff --git a/go.sum b/go.sum index 047533ab..0c06f154 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,10 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY= +github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= +github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -268,6 +272,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ= +github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= diff --git a/tools/goctl/feature/feature.go b/tools/goctl/feature/feature.go index 33e85f2e..9b950edb 100644 --- a/tools/goctl/feature/feature.go +++ b/tools/goctl/feature/feature.go @@ -8,12 +8,10 @@ import ( ) var feature = ` -1、新增对rpc错误转换处理 - 1.1、目前暂时仅处理not found 和 unknown错误 -2、增加feature命令支持,详细使用请通过命令[goctl -feature]查看 +1、增加goctl model支持 ` -func Feature(c *cli.Context) error { +func Feature(_ *cli.Context) error { fmt.Println(aurora.Blue("\nFEATURE:")) fmt.Println(aurora.Blue(feature)) return nil diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go index 56d0bd26..49cb90b5 100644 --- a/tools/goctl/goctl.go +++ b/tools/goctl/goctl.go @@ -17,6 +17,7 @@ import ( "github.com/tal-tech/go-zero/tools/goctl/configgen" "github.com/tal-tech/go-zero/tools/goctl/docker" "github.com/tal-tech/go-zero/tools/goctl/feature" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/command" "github.com/urfave/cli" ) @@ -189,17 +190,26 @@ var ( }, { Name: "model", - Usage: "generate sql model", + Usage: "generate model code", Flags: []cli.Flag{ cli.StringFlag{ - Name: "config, c", - Usage: "the file that contains main function", + Name: "src, s", + Usage: "the file path of the ddl source file", }, cli.StringFlag{ Name: "dir, d", Usage: "the target dir", }, + cli.BoolFlag{ + Name: "cache, c", + Usage: "generate code with cache [optional]", + }, + cli.BoolFlag{ + Name: "idea", + Usage: "for idea plugin [optional]", + }, }, + Action: command.Mysql, }, { Name: "config", diff --git a/tools/goctl/goctl.md b/tools/goctl/goctl.md index 8277ae09..dff92939 100644 --- a/tools/goctl/goctl.md +++ b/tools/goctl/goctl.md @@ -3,9 +3,7 @@ ## goctl用途 * 定义api请求 * 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序),dart(flutter) -* 生成MySQL CURD (https://goctl.xiaoheiban.cn) -* 生成MongoDB CURD (https://goctl.xiaoheiban.cn) - +* 生成MySQL CURD 详情见[goctl model模块](https://github.com/tal-tech/go-zero/tools/goctl/model) ## goctl使用说明 #### goctl参数说明 @@ -188,79 +186,5 @@ service user-api { #### 根据定义好的api文件生成Dart代码 `goctl api dart -api user/user.api -dir ./src` - -## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用) - `goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes` - - -src需要提供简单的usermodel.go文件,里面只需要提供一个结构体即可 - -cache 控制是否需要缓存 yes=需要 no=不需要 - src 示例代码如下 - ``` - package model - - type User struct { - Name string `o:"find,get,set" c:"姓名"` - Age int `o:"find,get,set" c:"年纪"` - School string `c:"学校"` - } - - ``` - 结构体中不需要提供Id,CreateTime,UpdateTime三个字段,会自动生成 - 结构体中每个tag有两个可选标签 c 和 o - c是改字段的注释 - o是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法 - 生成的目标文件会覆盖该简单go文件 - -## goctl rpc生成 - - 命令 `goctl rpc proto -proto ${proto} -service ${serviceName} -project ${projectName} -dir ${directory} -shared ${shared}` - 如: `goctl rpc proto -proto test.proto -service test -project xjy -dir .` - - 参数说明: - - - ${proto}: proto文件 - - ${serviceName}: rpc服务名称 - - ${projectName}: 所属项目,如xjy,xhb,crm,hera,具体查看help,主要为了根据不同项目服务往redis注册key,可选 - - ${directory}: 输出目录 - - ${shared}: shared文件生成目录,可选,默认为${pwd}/shared - - 生成目录结构示例: - - ``` go - . - ├── shared [示例目录,可自己指定,强制覆盖更新] - │   └── contentservicemodel.go - ├── test - │   ├── etc - │   │   └── test.json - │   ├── internal - │   │   ├── config - │   │   │   └── config.go - │   │   ├── handler [强制覆盖更新] - │   │   │   ├── changeavatarhandler.go - │   │   │   ├── changebirthdayhandler.go - │   │   │   ├── changenamehandler.go - │   │   │   ├── changepasswordhandler.go - │   │   │   ├── changeuserinfohandler.go - │   │   │   ├── getuserinfohandler.go - │   │   │   ├── loginhandler.go - │   │   │   ├── logouthandler.go - │   │   │   └── testhandler.go - │   │   ├── logic - │   │   │   ├── changeavatarlogic.go - │   │   │   ├── changebirthdaylogic.go - │   │   │   ├── changenamelogic.go - │   │   │   ├── changepasswordlogic.go - │   │   │   ├── changeuserinfologic.go - │   │   │   ├── getuserinfologic.go - │   │   │   ├── loginlogic.go - │   │   │   └── logoutlogic.go - │   │   └── svc - │   │   └── servicecontext.go - │   ├── pb - │   │   └── test.pb.go - │   └── test.go [强制覆盖更新] - └── test.proto - ``` - - 注意 :目前rpc目录生成的proto文件暂不支持import外部proto文件 + * 如有不理解的地方,随时问Kim/Kevin \ No newline at end of file diff --git a/tools/goctl/model/sql/README.MD b/tools/goctl/model/sql/README.MD index 3045638b..e2cbe48c 100644 --- a/tools/goctl/model/sql/README.MD +++ b/tools/goctl/model/sql/README.MD @@ -1,430 +1,253 @@ -

Sql生成工具说明文档

+# Goctl Model -

前言

-在当前Sql代码生成工具是基于sqlc生成的逻辑。 +goctl model 为go-zero下的工具模块中的组件之一,目前支持识别mysql ddl进行model层代码生成,通过命令行或者idea插件(即将支持)可以有选择地生成带redis cache或者不带redis cache的代码逻辑。 -

关键字

+# 快速开始 -+ 查询类型(前暂不支持同一字段多种类型混合生成,如按照campus_id查询单结果又查询All或者Limit) - - 单结果查询 - - FindOne(主键特有) - - FindOneByXxx - - 多结果查询 - - FindAllByXxx - - FindLimitByXxx -- withCache -- withoutCache +``` +$ goctl model -src ./sql/user.sql -dir ./model -c true +``` -

准备工作

+详情用法请参考[example](https://github.com/tal-tech/go-zero/tools/goctl/model/sql/example) -- table +执行上述命令后即可快速生成CURD代码。 - ``` - CREATE TABLE `user_info` ( - `id` bigint(20) NOT NULL COMMENT '主键', - `campus_id` bigint(20) DEFAULT NULL COMMENT '整校id', - `name` varchar(255) DEFAULT NULL COMMENT '用户姓名', - `id_number` varchar(255) DEFAULT NULL COMMENT '身份证', - `age` int(10) DEFAULT NULL COMMENT '年龄', - `gender` tinyint(1) DEFAULT NULL COMMENT '性别,0-男,1-女,2-不限', - `mobile` varchar(20) DEFAULT NULL COMMENT '手机号', - `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - ``` +``` +model +│   ├── error.go +│   └── usermodel.go +``` -

imports生成

-imports代码生成对应model中包的引入管理,仅使用于晓黑板项目中(非相对路径动态生成),目前受`withCache`参数的影响,除此之外其实为固定代码。 +> 注意:这里的目录结构中有usercoursemodel.go目录,在example中我为了体现带cache与不带cache代码的区别,因此将sql文件分别使用了独立的sql文件(user.sql&course.sql),在实际项目开发中你可以将ddl建表语句放在一个sql文件中,`goctl model`会自动解析并分割,最终按照每个ddl建表语句为单位生成独立的go文件。 -- withCache +* 生成代码示例 - ``` + ``` go + package model + import ( - "database/sql""fmt" - "strings" - "time" - - "github.com/tal-tech/go-zero/core/stores/sqlc" - "github.com/tal-tech/go-zero/core/stores/sqlx" - "github.com/tal-tech/go-zero/core/stringx" - "xiao/service/shared/builderx" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/tal-tech/go-zero/core/stores/cache" + "github.com/tal-tech/go-zero/core/stores/sqlc" + "github.com/tal-tech/go-zero/core/stores/sqlx" + "github.com/tal-tech/go-zero/core/stringx" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" ) - ``` + + var ( + userFieldNames = builderx.FieldNames(&User{}) + userRows = strings.Join(userFieldNames, ",") + userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",") + userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" + + cacheUserMobilePrefix = "cache#User#mobile#" + cacheUserIdPrefix = "cache#User#id#" + cacheUserNamePrefix = "cache#User#name#" + ) + + type ( + UserModel struct { + sqlc.CachedConn + table string + } + + User struct { + Id int64 `db:"id"` + Name string `db:"name"` // 用户名称 + Password string `db:"password"` // 用户密码 + Mobile string `db:"mobile"` // 手机号 + Gender string `db:"gender"` // 男|女|未公开 + Nickname string `db:"nickname"` // 用户昵称 + CreateTime time.Time `db:"create_time"` + UpdateTime time.Time `db:"update_time"` + } + ) + + func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel { + return &UserModel{ + CachedConn: sqlc.NewConn(conn, c), + table: table, + } + } + + func (m *UserModel) Insert(data User) (sql.Result, error) { + query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)` + return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname) + } + + func (m *UserModel) FindOne(id int64) (*User, error) { + userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) + var resp User + err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error { + query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + } + + func (m *UserModel) FindOneByName(name string) (*User, error) { + userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name) + var resp User + err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary) + }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1` + if err := conn.QueryRow(&resp, query, name); err != nil { + return nil, err + } + return resp.Id, nil + }, func(conn sqlx.SqlConn, v, primary interface{}) error { + query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, primary) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + } + + func (m *UserModel) FindOneByMobile(mobile string) (*User, error) { + userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile) + var resp User + err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary) + }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1` + if err := conn.QueryRow(&resp, query, mobile); err != nil { + return nil, err + } + return resp.Id, nil + }, func(conn sqlx.SqlConn, v, primary interface{}) error { + query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, primary) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + } + + func (m *UserModel) Update(data User) error { + userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id) + _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?` + return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id) + }, userIdKey) + return err + } + + func (m *UserModel) Delete(id int64) error { + data, err := m.FindOne(id) + if err != nil { + return err + } + userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) + userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name) + userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile) + _, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := `delete from ` + m.table + ` where id = ?` + return conn.Exec(query, id) + }, userIdKey, userNameKey, userMobileKey) + return err + } + ``` -- withoutCache +# 用法 + +``` +$ goctl model -h +``` + +``` +NAME: + goctl model - generate model code + +USAGE: + goctl model [command options] [arguments...] + +OPTIONS: + --src value, -s value the file path of the ddl source file + --dir value, -d value the target dir + --cache, -c generate code with cache [optional] + --idea for idea plugin [optional] + +``` + +# 生成规则 + +* 默认规则 - ``` - import ( - "database/sql""fmt" - "strings" - "time" - - "github.com/tal-tech/go-zero/core/stores/sqlx" - "github.com/tal-tech/go-zero/core/stringx" - "xiao/service/shared/builderx" - ) - ``` - -

vars生成

- -vars部分对应model中var声明的包含的代码块,由`table`名和`withCache`来决定其中的代码生成内容,`withCache`决定是否要生成缓存key变量的声明。 - -- withCache + 我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`,而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。 +* 带缓存模式 ``` - var ( - UserInfoFieldNames = builderx.FieldNames(&UserInfo{}) - UserInfoRows = strings.Join(UserInfoFieldNames, ",") - UserInfoRowsExpectAutoSet = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), ",") - UserInfoRowsWithPlaceHolder = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" - - cacheUserInfoIdPrefix = "cache#userInfo#id#" - cacheUserInfoCampusIdPrefix = "cache#userInfo#campusId#" - cacheUserInfoNamePrefix = "cache#userInfo#name#" - cacheUserInfoMobilePrefix = "cache#userInfo#mobile#" - ) + $ goctl model -src {filename} -dir {dir} -cache true ``` + + 目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。 -- withoutCache +* 不带缓存模式 + + ``` + $ goctl model -src {filename} -dir {dir} + ``` + or + ``` + $ goctl model -src {filename} -dir {dir} -cache false + ``` + 生成代码仅基本的CURD结构。 - ``` - var ( - UserInfoFieldNames = builderx.FieldNames(&UserInfo{}) - UserInfoRows = strings.Join(UserInfoFieldNames, ",") - UserInfoRowsExpectAutoSet = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), ",") - UserInfoRowsWithPlaceHolder = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" - ) - ``` +# 缓存 -

types生成

+ 对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。 -ypes部分对应model中type声明的包含的代码块,由`table`名和`withCache`来决定其中的代码生成内容,`withCache`决定引入sqlc还是sqlx。 +* 缓存会缓存哪些信息? -- withCache - ``` - type ( - UserInfoModel struct { - conn sqlc.CachedConn - table string - } + 对于主键字段缓存,会缓存整个结构体信息,而对于单索引字段(除全文索引)则缓存主键字段值。 - UserInfo struct { - Id int64 `db:"id"` // 主键id - CampusId int64 `db:"campus_id"` // 整校id - Name string `db:"name"` // 用户姓名 - IdNumber string `db:"id_number"` // 身份证 - Age int64 `db:"age"` // 年龄 - Gender int64 `db:"gender"` // 性别,0-男,1-女,2-不限 - Mobile string `db:"mobile"` // 手机号 - CreateTime time.Time `db:"create_time"` // 创建时间 - UpdateTime time.Time `db:"update_time"` // 更新时间 - } - ) - ``` +* 数据有更新(`update`)操作会清空缓存吗? + + 会,但仅清空主键缓存的信息,why?这里就不做详细赘述了。 -- withoutCache - ``` - type ( - UserInfoModel struct { - conn sqlx.SqlConn - table string - } +* 为什么不按照单索引字段生成`updateByXxx`和`deleteByXxx`的代码? + + 理论上是没任何问题,但是我们认为,对于model层的数据操作均是以整个结构体为单位,包括查询,我不建议只查询某部分字段(不反对),否则我们的缓存就没有意义了。 - UserInfo struct { - Id int64 `db:"id"` // 主键id - CampusId int64 `db:"campus_id"` // 整校id - Name string `db:"name"` // 用户姓名 - IdNumber string `db:"id_number"` // 身份证 - Age int64 `db:"age"` // 年龄 - Gender int64 `db:"gender"` // 性别,0-男,1-女,2-不限 - Mobile string `db:"mobile"` // 手机号 - CreateTime time.Time `db:"create_time"` // 创建时间 - UpdateTime time.Time `db:"update_time"` // 更新时间 - } - ) - ``` -

New生成

-new生成对应model中struct的New函数,受`withCache`影响决定是否要引入cacheRedis +* 为什么不支持`findPageLimit`、`findAll`这么模式代码生层? + + 目前,我认为除了基本的CURD外,其他的代码均属于业务型代码,这个我觉得开发人员根据业务需要进行编写更好。 -- withCache - ``` - func NewUserInfoModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserInfoModel { - return &UserInfoModel{ - CachedConn: sqlc.NewConn(conn, c), - table: table, - } - } - ``` -- withoutCache - ``` - func NewUserInfoModel(conn sqlx.SqlConn, table string) *UserInfoModel { - return &UserInfoModel{conn: conn, table: table} - } - ``` +# QA + +* goctl model支持根据数据库连接后选择表生成代码吗? + + 目前暂时不支持,在后面会向这个方向扩展。 + +* goctl model除了命令行模式,支持插件模式吗? + + 很快支持idea插件。 -

FindOne查询生成

-FindOne查询代码生成仅对主键有效。如`user_info`中生成的FindOne如下: + -- withCache - ``` - func (m *UserInfoModel) FindOne(id int64) (*UserInfo, error) { - idKey := fmt.Sprintf("%s%v", cacheUserInfoIdPrefix, id) - var resp UserInfo - err := m.QueryRow(&resp, idKey, func(conn sqlx.SqlConn, v interface{}) error { - query := `select ` + userInfoRows + ` from ` + m.table + `where id = ? limit 1` - return conn.QueryRow(v, query, id) - }) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - ``` - -- withoutCache - - ``` - func (m *UserInfoModel) FindOne(id int64) (*UserInfo, error) { - - query := `select ` + userInfoRows + ` from ` + m.table + `where id = ? limit 1` - var resp UserInfo - err := m.conn.QueryRow(&resp, query, id) - switch err { - case nil: - return &resp, nil - case sqlx.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - - } - ``` - -

FindOneByXxx查询生成

- -FindOneByXxx查询生成可以按照单个字段查询、多个字段以AND关系且表达式符号为`=`的查询(下称:组合查询),对除主键之外的字段有效,对于单个字段可以用`withCache`来控制是否需要缓存,这里的缓存只缓存主键,并不缓存整个struct,注意:这里有一个隐藏的规则,如果单个字段查询需要cache,那么主键一定有cache;多个字段组成的`组合查询`一律没有缓存处理,且组合查询不能相互嵌套,否则会报`circle query with other fields`错误,下面我们按场景来依次查看对应代码生成后的示例。 - ->注:目前暂不支持除equals之外的条件查询。 - -+ 单字段查询 - 以name查询为例 - - withCache - ``` - func (m *UserInfoModel) FindOneByName(name string) (*UserInfo, error) { - nameKey := fmt.Sprintf("%s%v", cacheUserInfoNamePrefix, name) - var id string - err := m.GetCache(key, &id) - if err != nil { - return nil, err - } - if id != "" { - return m.FindOne(id) - } - var resp UserInfo - query := `select ` + userInfoRows + ` from ` + m.table + `where name = ? limit 1` - err = m.QueryRowNoCache(&resp, query, name) - switch err { - case nil: - err = m.SetCache(nameKey, resp.Id) - if err != nil { - logx.Error(err) - } - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - ``` - - withoutCache - - ``` - func (m *UserInfoModel) FindOneByName(name string) (*UserInfo, error) { - var resp UserInfo - query := `select ` + userInfoRows + ` from ` + m.table + `where name = ? limit 1` - err = m.conn.QueryRow(&resp, query, name) - switch err { - case nil: - return &resp, nil - case sqlx.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - ``` - -- 组合查询 - 以`campus_id`和`id_number`查询为例。 - - ``` - func (m *UserInfoModel) FindOneByCampusIdAndIdNumber(campusId int64,idNumber string) (*UserInfo, error) { - var resp UserInfo - query := `select ` + userInfoRows + ` from ` + m.table + `where campus_id = ? AND id_number = ? limit 1` - err = m.QueryRowNoCache(&resp, query, campusId, idNumber) - // err = m.conn.QueryRows(&resp, query, campusId, idNumber) - switch err { - case nil: - return &resp, nil - case sqlx.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - ``` -

FindAllByXxx生成

-FindAllByXxx查询和FindOneByXxx功能相似,只是FindOneByXxx限制了limit等于1,而FindAllByXxx是查询所有,以两个例子来说明 - -- 查询单个字段`name`等于某值的所有数据 - ``` - func (m *UserInfoModel) FindAllByName(name string) ([]*UserInfo, error) { - var resp []*UserInfo - query := `select ` + userInfoRows + ` from ` + m.table + `where name = ?` - err := m.QueryRowsNoCache(&resp, query, name) - // err := m.conn.QueryRows(&resp, query, name) - if err != nil { - return nil, err - } - return resp, nil - } - ``` -- 查询多个组合字段`campus_id`等于某值且`gender`等于某值的所有数据 - ``` - func (m *UserInfoModel) FindAllByCampusIdAndGender(campusId int64,gender int64) ([]*UserInfo, error) { - var resp []*UserInfo - query := `select ` + userInfoRows + ` from ` + m.table + `where campus_id = ? AND gender = ?` - err := m.QueryRowsNoCache(&resp, query, campusId, gender) - // err := m.conn.QueryRows(&resp, query, campusId, gender) - if err != nil { - return nil, err - } - return resp, nil - } - ``` - -

FindLimitByXxx生成

-FindLimitByXxx查询和FindAllByXxx功能相似,只是FindAllByXxx限制了limit,除此之外还会生成查询对应Count总数的代码,而FindAllByXxx是查询所有数据,以几个例子来说明 - -- 查询`gender`等于某值的分页数据,按照`create_time`降序 - ``` - func (m *UserInfoModel) FindLimitByGender(gender int64, page, limit int) ([]*UserInfo, error) { - var resp []*UserInfo - query := `select ` + userInfoRows + `from ` + m.table + `where gender = ? order by create_time DESC limit ?,?` - err := m.QueryRowsNoCache(&resp, query, gender, (page-1)*limit, limit) - // err := m.conn.QueryRows(&resp, query, gender, (page-1)*limit, limit) - if err != nil { - return nil, err - } - return resp, nil - } - - func (m *UserInfoModel) FindAllCountByGender(gender int64) (int64, error) { - var count int64 - query := `select count(1) from ` + m.table + `where gender = ? ` - err := m.QueryRowsNoCache(&count, query, gender) - // err := m.conn.QueryRow(&count, query, gender) - if err != nil { - return 0, err - } - return count, nil - } - ``` -- 查询`gender`等于某值的分页数据,按照`create_time`降序、`update_time`生序排序 - ``` - func (m *UserInfoModel) FindLimitByGender(gender int64, page, limit int) ([]*UserInfo, error) { - var resp []*UserInfo - query := `select ` + userInfoRows + `from ` + m.table + `where gender = ? order by create_time DESC,update_time ASC limit ?,?` - err := m.QueryRowsNoCache(&resp, query, gender, (page-1)*limit, limit) - // err := m.conn.QueryRows(&resp, query, gender, (page-1)*limit, limit) - if err != nil { - return nil, err - } - return resp, nil - } - - func (m *UserInfoModel) FindAllCountByGender(gender int64) (int64, error) { - var count int64 - query := `select count(1) from ` + m.table + `where gender = ? ` - err := m.QueryRowNoCache(&count, query, gender) - // err := m.conn.QueryRow(&count, query, gender) - if err != nil { - return 0, err - } - return count, nil - } - ``` -- 查询`gender`等于某值且`campus_id`为某值按照`create_time`降序的分页数据 - ``` - func (m *UserInfoModel) FindLimitByGenderAndCampusId(gender int64,campusId int64, page, limit int) ([]*UserInfo, error) { - var resp []*UserInfo - query := `select ` + userInfoRows + `from ` + m.table + `where gender = ? AND campus_id = ? order by create_time DESC limit ?,?` - err := m.QueryRowsNoCache(&resp, query, gender, campusId, (page-1)*limit, limit) - // err := m.conn.QueryRows(&resp, query, gender, campusId, (page-1)*limit, limit) - if err != nil { - return nil, err - } - return resp, nil - } - - func (m *UserInfoModel) FindAllCountByGenderAndCampusId(gender int64,campusId int64) (int64, error) { - var count int64 - query := `select count(1) from ` + m.table + `where gender = ? AND campus_id = ? ` - err := m.QueryRowsNoCache(&count, query, gender, campusId) - // err := m.conn.QueryRow(&count, query, gender, campusId) - if err != nil { - return 0, err - } - return count, nil - } - ``` - -

Delete生成

-Delete代码根据`withCache`的不同可以生成带缓存逻辑代码和不带缓存逻辑代码,Delete代码生成仅按照主键删除。从FindOneByXxx方法描述得知,非主键`withCache`了那么主键会强制被cache,因此在delete时也会删除主键cache。 - -- withCache - 根据`mobile`查询用户信息 - - ``` - func (m *UserInfoModel) Delete(userId int64) error { - userIdKey := fmt.Sprintf("%s%v", cacheUserInfoUserIdPrefix, userId) - mobileKey := fmt.Sprintf("%s%v", cacheUserInfoMobilePrefix, mobile) - _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { - query := `delete from ` + m.table + + `where user_id = ?` - return conn.Exec(query, userId) - }, userIdKey, mobileKey) - return err - } - ``` -- withoutCache - ``` - func (m *UserInfoModel) Delete(userId int64) error { - _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { - query := `delete from ` + m.table + + `where user_id = ?` - return conn.Exec(query, userId) - }, ) - return err -} - ``` -

Insert生成

- -

Update生成

- -

待完善(TODO)

- -- 同一字段多种查询方式代码生成(优先级较高) -- 条件查询 -- 范围查询 -- ... - -

反馈与建议

- -- 无 \ No newline at end of file + diff --git a/tools/goctl/model/sql/builderx/builder.go b/tools/goctl/model/sql/builderx/builder.go new file mode 100644 index 00000000..ad80ba65 --- /dev/null +++ b/tools/goctl/model/sql/builderx/builder.go @@ -0,0 +1,97 @@ +package builderx + +import ( + "fmt" + "reflect" + + "github.com/go-xorm/builder" +) + +const dbTag = "db" + +func NewEq(in interface{}) builder.Eq { + return builder.Eq(ToMap(in)) +} + +func NewGt(in interface{}) builder.Gt { + return builder.Gt(ToMap(in)) +} + +func ToMap(in interface{}) map[string]interface{} { + out := make(map[string]interface{}) + v := reflect.ValueOf(in) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + // we only accept structs + if v.Kind() != reflect.Struct { + panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) + } + typ := v.Type() + for i := 0; i < v.NumField(); i++ { + // gets us a StructField + fi := typ.Field(i) + if tagv := fi.Tag.Get(dbTag); tagv != "" { + // set key of map to value in struct field + val := v.Field(i) + zero := reflect.Zero(val.Type()).Interface() + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + continue + } + out[tagv] = current + } + } + return out +} + +func FieldNames(in interface{}) []string { + out := make([]string, 0) + v := reflect.ValueOf(in) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + // we only accept structs + if v.Kind() != reflect.Struct { + panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) + } + typ := v.Type() + for i := 0; i < v.NumField(); i++ { + // gets us a StructField + fi := typ.Field(i) + if tagv := fi.Tag.Get(dbTag); tagv != "" { + out = append(out, tagv) + } else { + out = append(out, fi.Name) + } + } + return out +} +func FieldNamesAlias(in interface{}, alias string) []string { + out := make([]string, 0) + v := reflect.ValueOf(in) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + // we only accept structs + if v.Kind() != reflect.Struct { + panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) + } + typ := v.Type() + for i := 0; i < v.NumField(); i++ { + // gets us a StructField + fi := typ.Field(i) + tagName := "" + if tagv := fi.Tag.Get(dbTag); tagv != "" { + tagName = tagv + } else { + tagName = fi.Name + } + if len(alias) > 0 { + tagName = alias + "." + tagName + } + out = append(out, tagName) + } + return out +} diff --git a/tools/goctl/model/sql/builderx/builder_test.go b/tools/goctl/model/sql/builderx/builder_test.go new file mode 100644 index 00000000..28d1db01 --- /dev/null +++ b/tools/goctl/model/sql/builderx/builder_test.go @@ -0,0 +1,101 @@ +package builderx + +import ( + "fmt" + "testing" + + "github.com/go-xorm/builder" + "github.com/stretchr/testify/assert" +) + +type ( + User struct { + // 自增id + Id string `db:"id" json:"id,omitempty"` + // 姓名 + UserName string `db:"user_name" json:"userName,omitempty"` + // 1男,2女 + Sex int `db:"sex" json:"sex,omitempty"` + + Uuid string `db:"uuid" uuid:"uuid,omitempty"` + + Age int `db:"age" json:"age"` + } +) + +var userFields = FieldNames(User{}) + +func TestFieldNames(t *testing.T) { + var u User + out := FieldNames(&u) + fmt.Println(out) + actual := []string{"id", "user_name", "sex", "uuid", "age"} + assert.Equal(t, out, actual) +} + +func TestNewEq(t *testing.T) { + u := &User{ + Id: "123456", + UserName: "wahaha", + } + out := NewEq(u) + fmt.Println(out) + actual := builder.Eq{"id": "123456", "user_name": "wahaha"} + assert.Equal(t, out, actual) +} + +// @see https://github.com/go-xorm/builder +func TestBuilderSql(t *testing.T) { + u := &User{ + Id: "123123", + } + fields := FieldNames(u) + eq := NewEq(u) + sql, args, err := builder.Select(fields...).From("user").Where(eq).ToSQL() + fmt.Println(sql, args, err) + + actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE id=?" + actualArgs := []interface{}{"123123"} + assert.Equal(t, sql, actualSql) + assert.Equal(t, args, actualArgs) +} + +func TestBuildSqlDefaultValue(t *testing.T) { + var eq = builder.Eq{} + eq["age"] = 0 + eq["user_name"] = "" + + sql, args, err := builder.Select(userFields...).From("user").Where(eq).ToSQL() + fmt.Println(sql, args, err) + + actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE age=? AND user_name=?" + actualArgs := []interface{}{0, ""} + assert.Equal(t, sql, actualSql) + assert.Equal(t, args, actualArgs) +} + +func TestBuilderSqlIn(t *testing.T) { + u := &User{ + Age: 18, + } + gtU := NewGt(u) + in := builder.In("id", []string{"1", "2", "3"}) + sql, args, err := builder.Select(userFields...).From("user").Where(in).And(gtU).ToSQL() + fmt.Println(sql, args, err) + + actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE id IN (?,?,?) AND age>?" + actualArgs := []interface{}{"1", "2", "3", 18} + assert.Equal(t, sql, actualSql) + assert.Equal(t, args, actualArgs) +} + +func TestBuildSqlLike(t *testing.T) { + like := builder.Like{"name", "wang"} + sql, args, err := builder.Select(userFields...).From("user").Where(like).ToSQL() + fmt.Println(sql, args, err) + + actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE name LIKE ?" + actualArgs := []interface{}{"%wang%"} + assert.Equal(t, sql, actualSql) + assert.Equal(t, args, actualArgs) +} diff --git a/tools/goctl/model/sql/command/command.go b/tools/goctl/model/sql/command/command.go new file mode 100644 index 00000000..7604699c --- /dev/null +++ b/tools/goctl/model/sql/command/command.go @@ -0,0 +1,26 @@ +package command + +import ( + "github.com/tal-tech/go-zero/tools/goctl/model/sql/gen" + "github.com/tal-tech/go-zero/tools/goctl/util/console" + "github.com/urfave/cli" +) + +func Mysql(ctx *cli.Context) error { + src := ctx.String("src") + dir := ctx.String("dir") + cache := ctx.Bool("cache") + idea := ctx.Bool("idea") + var log console.Console + if idea { + log = console.NewIdeaConsole() + } else { + log = console.NewColorConsole() + } + generator := gen.NewDefaultGenerator(src, dir, gen.WithConsoleOption(log)) + err := generator.Start(cache) + if err != nil { + log.Error("%v", err) + } + return nil +} diff --git a/tools/modelctl/model/vars.go b/tools/goctl/model/sql/converter/types.go similarity index 51% rename from tools/modelctl/model/vars.go rename to tools/goctl/model/sql/converter/types.go index 38b40ef2..c6f93105 100644 --- a/tools/modelctl/model/vars.go +++ b/tools/goctl/model/sql/converter/types.go @@ -1,19 +1,25 @@ -package model +package converter + +import ( + "fmt" + "strings" +) var ( - CommonMysqlDataTypeMap = map[string]string{ - "tinyint": "int", - "smallint": "int", + commonMysqlDataTypeMap = map[string]string{ + // For consistency, all integer types are converted to int64 + "tinyint": "int64", + "smallint": "int64", "mediumint": "int64", "int": "int64", "integer": "int64", "bigint": "int64", - "float": "float32", + "float": "float64", "double": "float64", "decimal": "float64", "date": "time.Time", "time": "string", - "year": "int", + "year": "int64", "datetime": "time.Time", "timestamp": "time.Time", "char": "string", @@ -29,6 +35,12 @@ var ( } ) -const ( - ModeDirPerm = 0755 -) +func ConvertDataType(dataBaseType string) (goDataType string, err error) { + tp, ok := commonMysqlDataTypeMap[strings.ToLower(dataBaseType)] + if !ok { + err = fmt.Errorf("unexpected database type: %s", dataBaseType) + return + } + goDataType = tp + return +} diff --git a/tools/goctl/model/sql/example/generator.sh b/tools/goctl/model/sql/example/generator.sh new file mode 100644 index 00000000..4a44d5e1 --- /dev/null +++ b/tools/goctl/model/sql/example/generator.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# generate usermodel with cache +goctl model -src ./sql/user.sql -dir ./model -c true diff --git a/tools/goctl/model/sql/example/sql/user.sql b/tools/goctl/model/sql/example/sql/user.sql new file mode 100644 index 00000000..33a42a33 --- /dev/null +++ b/tools/goctl/model/sql/example/sql/user.sql @@ -0,0 +1,15 @@ +-- 用户表 -- +CREATE TABLE `user` ( + `id` bigint(10) NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称', + `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码', + `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号', + `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开', + `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称', + `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name_index` (`name`), + KEY `mobile_index` (`mobile`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + diff --git a/tools/goctl/model/sql/gen/convert.go b/tools/goctl/model/sql/gen/convert.go deleted file mode 100644 index 239656ab..00000000 --- a/tools/goctl/model/sql/gen/convert.go +++ /dev/null @@ -1,108 +0,0 @@ -package gen - -import ( - "errors" - "fmt" - "sort" - "strings" - - "github.com/tal-tech/go-zero/tools/goctl/model/sql/util" -) - -func TableConvert(outerTable OuterTable) (*InnerTable, error) { - var table InnerTable - table.CreateNotFound = outerTable.CreateNotFound - tableSnakeCase, tableUpperCamelCase, tableLowerCamelCase := util.FormatField(outerTable.Table) - table.SnakeCase = tableSnakeCase - table.UpperCamelCase = tableUpperCamelCase - table.LowerCamelCase = tableLowerCamelCase - fields := make([]*InnerField, 0) - var primaryField *InnerField - conflict := make(map[string]struct{}) - var containsCache bool - for _, field := range outerTable.Fields { - if field.Cache && !containsCache { - containsCache = true - } - fieldSnakeCase, fieldUpperCamelCase, fieldLowerCamelCase := util.FormatField(field.Name) - tag, err := genTag(fieldSnakeCase) - if err != nil { - return nil, err - } - var comment string - if field.Comment != "" { - comment = fmt.Sprintf("// %s", field.Comment) - } - withFields := make([]InnerWithField, 0) - unique := make([]string, 0) - unique = append(unique, fmt.Sprintf("%v", field.QueryType)) - unique = append(unique, field.Name) - - for _, item := range field.WithFields { - unique = append(unique, item.Name) - withFieldSnakeCase, withFieldUpperCamelCase, withFieldLowerCamelCase := util.FormatField(item.Name) - withFields = append(withFields, InnerWithField{ - Case: Case{ - SnakeCase: withFieldSnakeCase, - LowerCamelCase: withFieldLowerCamelCase, - UpperCamelCase: withFieldUpperCamelCase, - }, - DataType: commonMysqlDataTypeMap[item.DataBaseType], - }) - } - sort.Strings(unique) - uniqueKey := strings.Join(unique, "#") - if _, ok := conflict[uniqueKey]; ok { - return nil, ErrCircleQuery - } else { - conflict[uniqueKey] = struct{}{} - } - sortFields := make([]InnerSort, 0) - for _, sortField := range field.OuterSort { - sortSnake, sortUpperCamelCase, sortLowerCamelCase := util.FormatField(sortField.Field) - sortFields = append(sortFields, InnerSort{ - Field: Case{ - SnakeCase: sortSnake, - LowerCamelCase: sortUpperCamelCase, - UpperCamelCase: sortLowerCamelCase, - }, - Asc: sortField.Asc, - }) - } - innerField := &InnerField{ - IsPrimaryKey: field.IsPrimaryKey, - InnerWithField: InnerWithField{ - Case: Case{ - SnakeCase: fieldSnakeCase, - LowerCamelCase: fieldLowerCamelCase, - UpperCamelCase: fieldUpperCamelCase, - }, - DataType: commonMysqlDataTypeMap[field.DataBaseType], - }, - DataBaseType: field.DataBaseType, - Tag: tag, - Comment: comment, - Cache: field.Cache, - QueryType: field.QueryType, - WithFields: withFields, - Sort: sortFields, - } - if field.IsPrimaryKey { - primaryField = innerField - } - fields = append(fields, innerField) - } - if primaryField == nil { - return nil, errors.New("please ensure that primary exists") - } - table.ContainsCache = containsCache - primaryField.Cache = containsCache - table.PrimaryField = primaryField - table.Fields = fields - cacheKey, err := genCacheKeys(&table) - if err != nil { - return nil, err - } - table.CacheKey = cacheKey - return &table, nil -} diff --git a/tools/goctl/model/sql/gen/delete.go b/tools/goctl/model/sql/gen/delete.go index 23343936..71abbd79 100644 --- a/tools/goctl/model/sql/gen/delete.go +++ b/tools/goctl/model/sql/gen/delete.go @@ -1,51 +1,47 @@ package gen import ( - "bytes" "strings" - "text/template" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/core/collection" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genDelete(table *InnerTable) (string, error) { - t, err := template.New("delete").Parse(sqltemplate.Delete) - if err != nil { - return "", nil - } - deleteBuffer := new(bytes.Buffer) - keys := make([]string, 0) - keyValues := make([]string, 0) - for snake, key := range table.CacheKey { - if snake == table.PrimaryField.SnakeCase { - keys = append(keys, key.Key) +func genDelete(table Table, withCache bool) (string, error) { + keySet := collection.NewSet() + keyVariableSet := collection.NewSet() + for fieldName, key := range table.CacheKey { + if fieldName == table.PrimaryKey.Name.Source() { + keySet.AddStr(key.KeyExpression) } else { - keys = append(keys, key.DataKey) + keySet.AddStr(key.DataKeyExpression) } - keyValues = append(keyValues, key.KeyVariable) + keyVariableSet.AddStr(key.Variable) } - var isOnlyPrimaryKeyCache = true + var containsIndexCache = false for _, item := range table.Fields { - if item.IsPrimaryKey { - continue - } - if item.Cache { - isOnlyPrimaryKeyCache = false + if item.IsKey { + containsIndexCache = true break } } - err = t.Execute(deleteBuffer, map[string]interface{}{ - "upperObject": table.UpperCamelCase, - "containsCache": table.ContainsCache, - "isNotPrimaryKey": !isOnlyPrimaryKeyCache, - "lowerPrimaryKey": table.PrimaryField.LowerCamelCase, - "dataType": table.PrimaryField.DataType, - "keys": strings.Join(keys, "\r\n"), - "snakePrimaryKey": table.PrimaryField.SnakeCase, - "keyValues": strings.Join(keyValues, ", "), - }) + camel := table.Name.Snake2Camel() + output, err := templatex.With("delete"). + Parse(template.Delete). + Execute(map[string]interface{}{ + "upperStartCamelObject": camel, + "withCache": withCache, + "containsIndexCache": containsIndexCache, + "lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(), + "dataType": table.PrimaryKey.DataType, + "keys": strings.Join(keySet.KeysStr(), "\n"), + "originalPrimaryKey": table.PrimaryKey.Name.Source(), + "keyValues": strings.Join(keyVariableSet.KeysStr(), ", "), + }) if err != nil { return "", err } - return deleteBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/field.go b/tools/goctl/model/sql/gen/field.go index fe8413c5..125800db 100644 --- a/tools/goctl/model/sql/gen/field.go +++ b/tools/goctl/model/sql/gen/field.go @@ -1,15 +1,15 @@ package gen import ( - "bytes" "strings" - "text/template" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/parser" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genFields(fields []*InnerField) (string, error) { - list := make([]string, 0) +func genFields(fields []parser.Field) (string, error) { + var list []string for _, field := range fields { result, err := genField(field) if err != nil { @@ -17,23 +17,25 @@ func genFields(fields []*InnerField) (string, error) { } list = append(list, result) } - return strings.Join(list, "\r\n"), nil + return strings.Join(list, "\n"), nil } -func genField(field *InnerField) (string, error) { - t, err := template.New("types").Parse(sqltemplate.Field) - if err != nil { - return "", nil - } - var typeBuffer = new(bytes.Buffer) - err = t.Execute(typeBuffer, map[string]string{ - "name": field.UpperCamelCase, - "type": field.DataType, - "tag": field.Tag, - "comment": field.Comment, - }) +func genField(field parser.Field) (string, error) { + tag, err := genTag(field.Name.Source()) if err != nil { return "", err } - return typeBuffer.String(), nil + output, err := templatex.With("types"). + Parse(template.Field). + Execute(map[string]interface{}{ + "name": field.Name.Snake2Camel(), + "type": field.DataType, + "tag": tag, + "hasComment": field.Comment != "", + "comment": field.Comment, + }) + if err != nil { + return "", err + } + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/findallbyfield.go b/tools/goctl/model/sql/gen/findallbyfield.go deleted file mode 100644 index 9e1c6be2..00000000 --- a/tools/goctl/model/sql/gen/findallbyfield.go +++ /dev/null @@ -1,55 +0,0 @@ -package gen - -import ( - "bytes" - "strings" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" -) - -func genFindAllByField(table *InnerTable) (string, error) { - t, err := template.New("findAllByField").Parse(sqltemplate.FindAllByField) - if err != nil { - return "", err - } - list := make([]string, 0) - for _, field := range table.Fields { - if field.IsPrimaryKey { - continue - } - if field.QueryType != QueryAll { - continue - } - fineOneByFieldBuffer := new(bytes.Buffer) - upperFields := make([]string, 0) - in := make([]string, 0) - expressionFields := make([]string, 0) - expressionValuesFields := make([]string, 0) - upperFields = append(upperFields, field.UpperCamelCase) - in = append(in, field.LowerCamelCase+" "+field.DataType) - expressionFields = append(expressionFields, field.SnakeCase+" = ?") - expressionValuesFields = append(expressionValuesFields, field.LowerCamelCase) - for _, withField := range field.WithFields { - upperFields = append(upperFields, withField.UpperCamelCase) - in = append(in, withField.LowerCamelCase+" "+withField.DataType) - expressionFields = append(expressionFields, withField.SnakeCase+" = ?") - expressionValuesFields = append(expressionValuesFields, withField.LowerCamelCase) - } - err = t.Execute(fineOneByFieldBuffer, map[string]interface{}{ - "in": strings.Join(in, ","), - "upperObject": table.UpperCamelCase, - "upperFields": strings.Join(upperFields, "And"), - "lowerObject": table.LowerCamelCase, - "snakePrimaryKey": field.SnakeCase, - "expression": strings.Join(expressionFields, " AND "), - "expressionValues": strings.Join(expressionValuesFields, ", "), - "containsCache": table.ContainsCache, - }) - if err != nil { - return "", err - } - list = append(list, fineOneByFieldBuffer.String()) - } - return strings.Join(list, ""), nil -} diff --git a/tools/goctl/model/sql/gen/findallbylimit.go b/tools/goctl/model/sql/gen/findallbylimit.go deleted file mode 100644 index de7e5dbf..00000000 --- a/tools/goctl/model/sql/gen/findallbylimit.go +++ /dev/null @@ -1,63 +0,0 @@ -package gen - -import ( - "bytes" - "strings" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" -) - -func genFindLimitByField(table *InnerTable) (string, error) { - t, err := template.New("findLimitByField").Parse(sqltemplate.FindLimitByField) - if err != nil { - return "", err - } - list := make([]string, 0) - for _, field := range table.Fields { - if field.IsPrimaryKey { - continue - } - if field.QueryType != QueryLimit { - continue - } - fineOneByFieldBuffer := new(bytes.Buffer) - upperFields := make([]string, 0) - in := make([]string, 0) - expressionFields := make([]string, 0) - expressionValuesFields := make([]string, 0) - upperFields = append(upperFields, field.UpperCamelCase) - in = append(in, field.LowerCamelCase+" "+field.DataType) - expressionFields = append(expressionFields, field.SnakeCase+" = ?") - expressionValuesFields = append(expressionValuesFields, field.LowerCamelCase) - for _, withField := range field.WithFields { - upperFields = append(upperFields, withField.UpperCamelCase) - in = append(in, withField.LowerCamelCase+" "+withField.DataType) - expressionFields = append(expressionFields, withField.SnakeCase+" = ?") - expressionValuesFields = append(expressionValuesFields, withField.LowerCamelCase) - } - sortList := make([]string, 0) - for _, item := range field.Sort { - var sort = "ASC" - if !item.Asc { - sort = "DESC" - } - sortList = append(sortList, item.Field.SnakeCase+" "+sort) - } - err = t.Execute(fineOneByFieldBuffer, map[string]interface{}{ - "in": strings.Join(in, ","), - "upperObject": table.UpperCamelCase, - "upperFields": strings.Join(upperFields, "And"), - "lowerObject": table.LowerCamelCase, - "expression": strings.Join(expressionFields, " AND "), - "expressionValues": strings.Join(expressionValuesFields, ", "), - "sortExpression": strings.Join(sortList, ","), - "containsCache": table.ContainsCache, - }) - if err != nil { - return "", err - } - list = append(list, fineOneByFieldBuffer.String()) - } - return strings.Join(list, ""), nil -} diff --git a/tools/goctl/model/sql/gen/findone.go b/tools/goctl/model/sql/gen/findone.go index 080928b7..b7b1a1b5 100644 --- a/tools/goctl/model/sql/gen/findone.go +++ b/tools/goctl/model/sql/gen/findone.go @@ -1,30 +1,27 @@ package gen import ( - "bytes" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genFindOne(table *InnerTable) (string, error) { - t, err := template.New("findOne").Parse(sqltemplate.FindOne) +func genFindOne(table Table, withCache bool) (string, error) { + camel := table.Name.Snake2Camel() + output, err := templatex.With("findOne"). + Parse(template.FindOne). + Execute(map[string]interface{}{ + "withCache": withCache, + "upperStartCamelObject": camel, + "lowerStartCamelObject": stringx.From(camel).LowerStart(), + "originalPrimaryKey": table.PrimaryKey.Name.Source(), + "lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(), + "dataType": table.PrimaryKey.DataType, + "cacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].KeyExpression, + "cacheKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable, + }) if err != nil { return "", err } - fineOneBuffer := new(bytes.Buffer) - err = t.Execute(fineOneBuffer, map[string]interface{}{ - "withCache": table.PrimaryField.Cache, - "upperObject": table.UpperCamelCase, - "lowerObject": table.LowerCamelCase, - "snakePrimaryKey": table.PrimaryField.SnakeCase, - "lowerPrimaryKey": table.PrimaryField.LowerCamelCase, - "dataType": table.PrimaryField.DataType, - "cacheKey": table.CacheKey[table.PrimaryField.SnakeCase].Key, - "cacheKeyVariable": table.CacheKey[table.PrimaryField.SnakeCase].KeyVariable, - }) - if err != nil { - return "", err - } - return fineOneBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/fineonebyfield.go b/tools/goctl/model/sql/gen/fineonebyfield.go index 2fbc4f37..62c66000 100644 --- a/tools/goctl/model/sql/gen/fineonebyfield.go +++ b/tools/goctl/model/sql/gen/fineonebyfield.go @@ -1,67 +1,41 @@ package gen import ( - "bytes" + "fmt" "strings" - "text/template" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genFineOneByField(table *InnerTable) (string, error) { - t, err := template.New("findOneByField").Parse(sqltemplate.FindOneByField) - if err != nil { - return "", err - } - list := make([]string, 0) +func genFineOneByField(table Table, withCache bool) (string, error) { + t := templatex.With("findOneByField").Parse(template.FindOneByField) + var list []string + camelTableName := table.Name.Snake2Camel() for _, field := range table.Fields { - if field.IsPrimaryKey { + if field.IsPrimaryKey || !field.IsKey { continue } - if field.QueryType != QueryOne { - continue - } - fineOneByFieldBuffer := new(bytes.Buffer) - upperFields := make([]string, 0) - in := make([]string, 0) - expressionFields := make([]string, 0) - expressionValuesFields := make([]string, 0) - upperFields = append(upperFields, field.UpperCamelCase) - in = append(in, field.LowerCamelCase+" "+field.DataType) - expressionFields = append(expressionFields, field.SnakeCase+" = ?") - expressionValuesFields = append(expressionValuesFields, field.LowerCamelCase) - for _, withField := range field.WithFields { - upperFields = append(upperFields, withField.UpperCamelCase) - in = append(in, withField.LowerCamelCase+" "+withField.DataType) - expressionFields = append(expressionFields, withField.SnakeCase+" = ?") - expressionValuesFields = append(expressionValuesFields, withField.LowerCamelCase) - } - err = t.Execute(fineOneByFieldBuffer, map[string]interface{}{ - "in": strings.Join(in, ","), - "upperObject": table.UpperCamelCase, - "upperFields": strings.Join(upperFields, "And"), - "onlyOneFiled": len(field.WithFields) == 0, - "withCache": field.Cache, - "containsCache": table.ContainsCache, - "lowerObject": table.LowerCamelCase, - "lowerField": field.LowerCamelCase, - "snakeField": field.SnakeCase, - "lowerPrimaryKey": table.PrimaryField.LowerCamelCase, - "UpperPrimaryKey": table.PrimaryField.UpperCamelCase, - "primaryKeyDefine": table.CacheKey[table.PrimaryField.SnakeCase].Define, - "primarySnakeField": table.PrimaryField.SnakeCase, - "primaryDataType": table.PrimaryField.DataType, - "primaryDataTypeString": table.PrimaryField.DataType == "string", - "upperObjectKey": table.PrimaryField.UpperCamelCase, - "cacheKey": table.CacheKey[field.SnakeCase].Key, - "cacheKeyVariable": table.CacheKey[field.SnakeCase].KeyVariable, - "expression": strings.Join(expressionFields, " AND "), - "expressionValues": strings.Join(expressionValuesFields, ", "), + camelFieldName := field.Name.Snake2Camel() + output, err := t.Execute(map[string]interface{}{ + "upperStartCamelObject": camelTableName, + "upperField": camelFieldName, + "in": fmt.Sprintf("%s %s", stringx.From(camelFieldName).LowerStart(), field.DataType), + "withCache": withCache, + "cacheKey": table.CacheKey[field.Name.Source()].KeyExpression, + "cacheKeyVariable": table.CacheKey[field.Name.Source()].Variable, + "primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left, + "lowerStartCamelObject": stringx.From(camelTableName).LowerStart(), + "lowerStartCamelField": stringx.From(camelFieldName).LowerStart(), + "upperStartCamelPrimaryKey": table.PrimaryKey.Name.Snake2Camel(), + "originalField": field.Name.Source(), + "originalPrimaryField": table.PrimaryKey.Name.Source(), }) if err != nil { return "", err } - list = append(list, fineOneByFieldBuffer.String()) + list = append(list, output.String()) } - return strings.Join(list, ""), nil + return strings.Join(list, "\n"), nil } diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go new file mode 100644 index 00000000..e7097d0a --- /dev/null +++ b/tools/goctl/model/sql/gen/gen.go @@ -0,0 +1,196 @@ +package gen + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/tal-tech/go-zero/tools/goctl/model/sql/parser" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util" + "github.com/tal-tech/go-zero/tools/goctl/util/console" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" +) + +const ( + pwd = "." + createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case +) + +type ( + defaultGenerator struct { + source string + src string + dir string + console.Console + } + Option func(generator *defaultGenerator) +) + +func NewDefaultGenerator(src, dir string, opt ...Option) *defaultGenerator { + if src == "" { + src = pwd + } + if dir == "" { + dir = pwd + } + generator := &defaultGenerator{src: src, dir: dir} + var optionList []Option + optionList = append(optionList, newDefaultOption()) + optionList = append(optionList, opt...) + for _, fn := range optionList { + fn(generator) + } + return generator +} + +func WithConsoleOption(c console.Console) Option { + return func(generator *defaultGenerator) { + generator.Console = c + } +} + +func newDefaultOption() Option { + return func(generator *defaultGenerator) { + generator.Console = console.NewColorConsole() + } +} + +func (g *defaultGenerator) Start(withCache bool) error { + fileSrc, err := filepath.Abs(g.src) + if err != nil { + return err + } + dirAbs, err := filepath.Abs(g.dir) + if err != nil { + return err + } + err = util.MkdirIfNotExist(dirAbs) + if err != nil { + return err + } + data, err := ioutil.ReadFile(fileSrc) + if err != nil { + return err + } + g.source = string(data) + modelList, err := g.genFromDDL(withCache) + if err != nil { + return err + } + + for tableName, code := range modelList { + name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).Snake2Camel())) + filename := filepath.Join(dirAbs, name) + if util.FileExists(filename) { + g.Warning("%s already exists,ignored.", name) + continue + } + err = ioutil.WriteFile(filename, []byte(code), os.ModePerm) + if err != nil { + return err + } + } + // generate error file + filename := filepath.Join(dirAbs, "error.go") + if !util.FileExists(filename) { + err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm) + if err != nil { + return err + } + } + g.Success("Done.") + return nil +} + +// ret1: key-table name,value-code +func (g *defaultGenerator) genFromDDL(withCache bool) (map[string]string, error) { + ddlList := g.split() + m := make(map[string]string) + for _, ddl := range ddlList { + table, err := parser.Parse(ddl) + if err != nil { + return nil, err + } + code, err := g.genModel(*table, withCache) + if err != nil { + return nil, err + } + m[table.Name.Source()] = code + } + return m, nil +} + +type ( + Table struct { + parser.Table + CacheKey map[string]Key + } +) + +func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) { + t := templatex.With("model"). + Parse(template.Model). + GoFmt(true) + + m, err := genCacheKeys(in) + if err != nil { + return "", err + } + importsCode := genImports(withCache) + var table Table + table.Table = in + table.CacheKey = m + + varsCode, err := genVars(table, withCache) + if err != nil { + return "", err + } + typesCode, err := genTypes(table, withCache) + if err != nil { + return "", err + } + newCode, err := genNew(table, withCache) + if err != nil { + return "", err + } + insertCode, err := genInsert(table, withCache) + if err != nil { + return "", err + } + var findCode = make([]string, 0) + findOneCode, err := genFindOne(table, withCache) + if err != nil { + return "", err + } + findOneByFieldCode, err := genFineOneByField(table, withCache) + if err != nil { + return "", err + } + findCode = append(findCode, findOneCode, findOneByFieldCode) + updateCode, err := genUpdate(table, withCache) + if err != nil { + return "", err + } + deleteCode, err := genDelete(table, withCache) + if err != nil { + return "", err + } + output, err := t.Execute(map[string]interface{}{ + "imports": importsCode, + "vars": varsCode, + "types": typesCode, + "new": newCode, + "insert": insertCode, + "find": strings.Join(findCode, "\r\n"), + "update": updateCode, + "delete": deleteCode, + }) + if err != nil { + return "", err + } + return output.String(), nil +} diff --git a/tools/goctl/model/sql/gen/imports.go b/tools/goctl/model/sql/gen/imports.go index dcf4f0dd..b9f6c93f 100644 --- a/tools/goctl/model/sql/gen/imports.go +++ b/tools/goctl/model/sql/gen/imports.go @@ -1,23 +1,13 @@ package gen import ( - "bytes" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" ) -func genImports(table *InnerTable) (string, error) { - t, err := template.New("imports").Parse(sqltemplate.Imports) - if err != nil { - return "", err +func genImports(withCache bool) string { + if withCache { + return template.Imports + } else { + return template.ImportsNoCache } - importBuffer := new(bytes.Buffer) - err = t.Execute(importBuffer, map[string]interface{}{ - "containsCache": table.ContainsCache, - }) - if err != nil { - return "", err - } - return importBuffer.String(), nil } diff --git a/tools/goctl/model/sql/gen/insert.go b/tools/goctl/model/sql/gen/insert.go index d246c81f..a2b7ca09 100644 --- a/tools/goctl/model/sql/gen/insert.go +++ b/tools/goctl/model/sql/gen/insert.go @@ -1,37 +1,39 @@ package gen import ( - "bytes" "strings" - "text/template" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genInsert(table *InnerTable) (string, error) { - t, err := template.New("insert").Parse(sqltemplate.Insert) - if err != nil { - return "", nil - } - insertBuffer := new(bytes.Buffer) +func genInsert(table Table, withCache bool) (string, error) { expressions := make([]string, 0) expressionValues := make([]string, 0) for _, filed := range table.Fields { - if filed.SnakeCase == "create_time" || filed.SnakeCase == "update_time" || filed.IsPrimaryKey { + camel := filed.Name.Snake2Camel() + if camel == "CreateTime" || camel == "UpdateTime" { + continue + } + if filed.IsPrimaryKey && table.PrimaryKey.AutoIncrement { continue } expressions = append(expressions, "?") - expressionValues = append(expressionValues, "data."+filed.UpperCamelCase) + expressionValues = append(expressionValues, "data."+camel) } - err = t.Execute(insertBuffer, map[string]interface{}{ - "upperObject": table.UpperCamelCase, - "lowerObject": table.LowerCamelCase, - "expression": strings.Join(expressions, ", "), - "expressionValues": strings.Join(expressionValues, ", "), - "containsCache": table.ContainsCache, - }) + camel := table.Name.Snake2Camel() + output, err := templatex.With("insert"). + Parse(template.Insert). + Execute(map[string]interface{}{ + "withCache": withCache, + "upperStartCamelObject": camel, + "lowerStartCamelObject": stringx.From(camel).LowerStart(), + "expression": strings.Join(expressions, ", "), + "expressionValues": strings.Join(expressionValues, ", "), + }) if err != nil { return "", err } - return insertBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/keys.go b/tools/goctl/model/sql/gen/keys.go index 6fa2ce02..3b058ce0 100644 --- a/tools/goctl/model/sql/gen/keys.go +++ b/tools/goctl/model/sql/gen/keys.go @@ -1,105 +1,50 @@ package gen import ( - "bytes" - "strings" - "text/template" -) + "fmt" -var ( - cacheKeyExpressionTemplate = `cache{{.upperCamelTable}}{{.upperCamelField}}Prefix = "cache#{{.lowerCamelTable}}#{{.lowerCamelField}}#"` - keyTemplate = `{{.lowerCamelField}}Key := fmt.Sprintf("%s%v", {{.define}}, {{.lowerCamelField}})` - keyRespTemplate = `{{.lowerCamelField}}Key := fmt.Sprintf("%s%v", {{.define}}, resp.{{.upperCamelField}})` - keyDataTemplate = `{{.lowerCamelField}}Key := fmt.Sprintf("%s%v", {{.define}}, data.{{.upperCamelField}})` + "github.com/tal-tech/go-zero/tools/goctl/model/sql/parser" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" ) type ( + // tableName:user + // {{prefix}}=cache + // key:id Key struct { - Define string // cacheKey define,如:cacheUserUserIdPrefix - Value string // cacheKey value expression,如:cache#user#userId# - Expression string // cacheKey expression,如:cacheUserUserIdPrefix="cache#user#userId#" - KeyVariable string // cacheKey 声明变量,如:userIdKey - Key string // 缓存key的代码,如 userIdKey:=fmt.Sprintf("%s%v", cacheUserUserIdPrefix, userId) - DataKey string // 缓存key的代码,如 userIdKey:=fmt.Sprintf("%s%v", cacheUserUserIdPrefix, data.userId) - RespKey string // 缓存key的代码,如 userIdKey:=fmt.Sprintf("%s%v", cacheUserUserIdPrefix, resp.userId) + VarExpression string // cacheUserIdPrefix="cache#user#id#" + Left string // cacheUserIdPrefix + Right string // cache#user#id# + Variable string // userIdKey + KeyExpression string // userIdKey: = fmt.Sprintf("cache#user#id#%v", userId) + DataKeyExpression string // userIdKey: = fmt.Sprintf("cache#user#id#%v", data.userId) + RespKeyExpression string // userIdKey: = fmt.Sprintf("cache#user#id#%v", resp.userId) } ) -// key-数据库原始字段名,value-缓存key对象 -func genCacheKeys(table *InnerTable) (map[string]Key, error) { +// key-数据库原始字段名,value-缓存key相关数据 +func genCacheKeys(table parser.Table) (map[string]Key, error) { fields := table.Fields - var m = make(map[string]Key) - if !table.ContainsCache { - return m, nil - } + m := make(map[string]Key) + camelTableName := table.Name.Snake2Camel() + lowerStartCamelTableName := stringx.From(camelTableName).LowerStart() for _, field := range fields { - if !field.Cache && !field.IsPrimaryKey { + if !field.IsKey { continue } - t, err := template.New("keyExpression").Parse(cacheKeyExpressionTemplate) - if err != nil { - return nil, err - } - var expressionBuffer = new(bytes.Buffer) - err = t.Execute(expressionBuffer, map[string]string{ - "upperCamelTable": table.UpperCamelCase, - "lowerCamelTable": table.LowerCamelCase, - "upperCamelField": field.UpperCamelCase, - "lowerCamelField": field.LowerCamelCase, - }) - if err != nil { - return nil, err - } - expression := expressionBuffer.String() - expressionAr := strings.Split(expression, "=") - define := strings.TrimSpace(expressionAr[0]) - value := strings.TrimSpace(expressionAr[1]) - t, err = template.New("key").Parse(keyTemplate) - if err != nil { - return nil, err - } - var keyBuffer = new(bytes.Buffer) - err = t.Execute(keyBuffer, map[string]string{ - "lowerCamelField": field.LowerCamelCase, - "define": define, - }) - if err != nil { - return nil, err - } - t, err = template.New("keyData").Parse(keyDataTemplate) - if err != nil { - return nil, err - } - var keyDataBuffer = new(bytes.Buffer) - err = t.Execute(keyDataBuffer, map[string]string{ - "lowerCamelField": field.LowerCamelCase, - "upperCamelField": field.UpperCamelCase, - "define": define, - }) - if err != nil { - return nil, err - } - t, err = template.New("keyResp").Parse(keyRespTemplate) - if err != nil { - return nil, err - } - var keyRespBuffer = new(bytes.Buffer) - err = t.Execute(keyRespBuffer, map[string]string{ - "lowerCamelField": field.LowerCamelCase, - "upperCamelField": field.UpperCamelCase, - "define": define, - }) - if err != nil { - return nil, err - } - m[field.SnakeCase] = Key{ - Define: define, - Value: value, - Expression: expression, - KeyVariable: field.LowerCamelCase + "Key", - Key: keyBuffer.String(), - DataKey: keyDataBuffer.String(), - RespKey: keyRespBuffer.String(), + camelFieldName := field.Name.Snake2Camel() + lowerStartCamelFieldName := stringx.From(camelFieldName).LowerStart() + left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName) + right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName) + variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName) + m[field.Name.Source()] = Key{ + VarExpression: fmt.Sprintf(`%s = "%s"`, left, right), + Left: left, + Right: right, + Variable: variable, + KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,%s)`, variable, "%s", "%v", left, lowerStartCamelFieldName), + DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s",%s, data.%s)`, variable, "%s", "%v", left, camelFieldName), + RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,resp.%s)`, variable, "%s", "%v", left, camelFieldName), } } return m, nil diff --git a/tools/goctl/model/sql/gen/keys_test.go b/tools/goctl/model/sql/gen/keys_test.go deleted file mode 100644 index 1018f21a..00000000 --- a/tools/goctl/model/sql/gen/keys_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package gen - -import ( - "log" - "testing" - - "github.com/tal-tech/go-zero/core/logx" -) - -func TestKeys(t *testing.T) { - var table = OuterTable{ - Table: "user_info", - CreateNotFound: true, - Fields: []*OuterFiled{ - { - IsPrimaryKey: true, - Name: "user_id", - DataBaseType: "bigint", - Comment: "主键id", - }, - { - Name: "campus_id", - DataBaseType: "bigint", - Comment: "整校id", - QueryType: QueryAll, - Cache: false, - }, - { - Name: "name", - DataBaseType: "varchar", - Comment: "用户姓名", - QueryType: QueryOne, - }, - { - Name: "id_number", - DataBaseType: "varchar", - Comment: "身份证", - Cache: false, - QueryType: QueryNone, - WithFields: []OuterWithField{ - { - Name: "name", - DataBaseType: "varchar", - }, - }, - }, - { - Name: "age", - DataBaseType: "int", - Comment: "年龄", - Cache: false, - QueryType: QueryNone, - }, - { - Name: "gender", - DataBaseType: "tinyint", - Comment: "性别,0-男,1-女,2-不限", - QueryType: QueryLimit, - WithFields: []OuterWithField{ - { - Name: "campus_id", - DataBaseType: "bigint", - }, - }, - OuterSort: []OuterSort{ - { - Field: "create_time", - Asc: false, - }, - }, - }, - { - Name: "mobile", - DataBaseType: "varchar", - Comment: "手机号", - QueryType: QueryOne, - Cache: true, - }, - { - Name: "create_time", - DataBaseType: "timestamp", - Comment: "创建时间", - }, - { - Name: "update_time", - DataBaseType: "timestamp", - Comment: "更新时间", - }, - }, - } - innerTable, err := TableConvert(table) - if err != nil { - log.Fatalln(err) - } - tp, err := GenModel(innerTable) - if err != nil { - log.Fatalln(err) - } - logx.Info(tp) -} diff --git a/tools/goctl/model/sql/gen/model.go b/tools/goctl/model/sql/gen/model.go deleted file mode 100644 index 1dcbe9b9..00000000 --- a/tools/goctl/model/sql/gen/model.go +++ /dev/null @@ -1,86 +0,0 @@ -package gen - -import ( - "bytes" - "go/format" - "strings" - "text/template" - - "github.com/tal-tech/go-zero/core/logx" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" -) - -func GenModel(table *InnerTable) (string, error) { - t, err := template.New("model").Parse(sqltemplate.Model) - if err != nil { - return "", nil - } - modelBuffer := new(bytes.Buffer) - importsCode, err := genImports(table) - if err != nil { - return "", err - } - varsCode, err := genVars(table) - if err != nil { - return "", err - } - typesCode, err := genTypes(table) - if err != nil { - return "", err - } - newCode, err := genNew(table) - if err != nil { - return "", err - } - insertCode, err := genInsert(table) - if err != nil { - return "", err - } - var findCode = make([]string, 0) - findOneCode, err := genFindOne(table) - if err != nil { - return "", err - } - findOneByFieldCode, err := genFineOneByField(table) - if err != nil { - return "", err - } - findAllCode, err := genFindAllByField(table) - if err != nil { - return "", err - } - findLimitCode, err := genFindLimitByField(table) - if err != nil { - return "", err - } - findCode = append(findCode, findOneCode, findOneByFieldCode, findAllCode, findLimitCode) - updateCode, err := genUpdate(table) - if err != nil { - return "", err - } - deleteCode, err := genDelete(table) - if err != nil { - return "", err - } - - err = t.Execute(modelBuffer, map[string]interface{}{ - "imports": importsCode, - "vars": varsCode, - "types": typesCode, - "new": newCode, - "insert": insertCode, - "find": strings.Join(findCode, "\r\n"), - "update": updateCode, - "delete": deleteCode, - }) - if err != nil { - return "", err - } - result := modelBuffer.String() - bts, err := format.Source([]byte(result)) - if err != nil { - logx.Errorf("%+v", err) - return "", err - } - return string(bts), nil -} diff --git a/tools/goctl/model/sql/gen/new.go b/tools/goctl/model/sql/gen/new.go index 47047b89..15b598d8 100644 --- a/tools/goctl/model/sql/gen/new.go +++ b/tools/goctl/model/sql/gen/new.go @@ -1,24 +1,19 @@ package gen import ( - "bytes" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genNew(table *InnerTable) (string, error) { - t, err := template.New("new").Parse(sqltemplate.New) +func genNew(table Table, withCache bool) (string, error) { + output, err := templatex.With("new"). + Parse(template.New). + Execute(map[string]interface{}{ + "withCache": withCache, + "upperStartCamelObject": table.Name.Snake2Camel(), + }) if err != nil { return "", err } - newBuffer := new(bytes.Buffer) - err = t.Execute(newBuffer, map[string]interface{}{ - "containsCache": table.ContainsCache, - "upperObject": table.UpperCamelCase, - }) - if err != nil { - return "", err - } - return newBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/shared.go b/tools/goctl/model/sql/gen/shared.go deleted file mode 100644 index fdafab72..00000000 --- a/tools/goctl/model/sql/gen/shared.go +++ /dev/null @@ -1,99 +0,0 @@ -package gen - -var ( - commonMysqlDataTypeMap = map[string]string{ - "tinyint": "int64", - "smallint": "int64", - "mediumint": "int64", - "int": "int64", - "integer": "int64", - "bigint": "int64", - "float": "float64", - "double": "float64", - "decimal": "float64", - "date": "time.Time", - "time": "string", - "year": "int64", - "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 ( - QueryNone QueryType = 0 - QueryOne QueryType = 1 // 仅支持单个字段为查询条件 - QueryAll QueryType = 2 // 可支持多个字段为查询条件,且关系均为and - QueryLimit QueryType = 3 // 可支持多个字段为查询条件,且关系均为and -) - -type ( - QueryType int - - Case struct { - SnakeCase string - LowerCamelCase string - UpperCamelCase string - } - InnerWithField struct { - Case - DataType string - } - InnerTable struct { - Case - ContainsCache bool - CreateNotFound bool - PrimaryField *InnerField - Fields []*InnerField - CacheKey map[string]Key // key-数据库字段 - } - InnerField struct { - IsPrimaryKey bool - InnerWithField - DataBaseType string // 数据库中字段类型 - Tag string // 标签,格式:`db:"xxx"` - Comment string // 注释,以"// 开头" - Cache bool // 是否缓存模式 - QueryType QueryType - WithFields []InnerWithField - Sort []InnerSort - } - InnerSort struct { - Field Case - Asc bool - } - - OuterTable struct { - Table string `json:"table"` - CreateNotFound bool `json:"createNotFound,optional"` - Fields []*OuterFiled `json:"fields"` - } - OuterWithField struct { - Name string `json:"name"` - DataBaseType string `json:"dataBaseType"` - } - OuterSort struct { - Field string `json:"fields"` - Asc bool `json:"asc,optional"` - } - OuterFiled struct { - IsPrimaryKey bool `json:"isPrimaryKey,optional"` - Name string `json:"name"` - DataBaseType string `json:"dataBaseType"` - Comment string `json:"comment"` - Cache bool `json:"cache,optional"` - // if IsPrimaryKey==false下面字段有效 - QueryType QueryType `json:"queryType"` // 查找类型 - WithFields []OuterWithField `json:"withFields,optional"` // 其他字段联合组成条件的字段列表 - OuterSort []OuterSort `json:"sort,optional"` - } -) diff --git a/tools/goctl/model/sql/gen/split.go b/tools/goctl/model/sql/gen/split.go new file mode 100644 index 00000000..abc8e633 --- /dev/null +++ b/tools/goctl/model/sql/gen/split.go @@ -0,0 +1,23 @@ +package gen + +import ( + "regexp" +) + +func (g *defaultGenerator) split() []string { + reg := regexp.MustCompile(createTableFlag) + index := reg.FindAllStringIndex(g.source, -1) + list := make([]string, 0) + source := g.source + for i := len(index) - 1; i >= 0; i-- { + subIndex := index[i] + if len(subIndex) == 0 { + continue + } + start := subIndex[0] + ddl := source[start:] + list = append(list, ddl) + source = source[:start] + } + return list +} diff --git a/tools/goctl/model/sql/gen/tag.go b/tools/goctl/model/sql/gen/tag.go index 6c5edf73..8414a102 100644 --- a/tools/goctl/model/sql/gen/tag.go +++ b/tools/goctl/model/sql/gen/tag.go @@ -1,26 +1,21 @@ package gen import ( - "bytes" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) func genTag(in string) (string, error) { if in == "" { return in, nil } - t, err := template.New("tag").Parse(sqltemplate.Tag) + output, err := templatex.With("tag"). + Parse(template.Tag). + Execute(map[string]interface{}{ + "field": in, + }) if err != nil { return "", err } - var tagBuffer = new(bytes.Buffer) - err = t.Execute(tagBuffer, map[string]interface{}{ - "field": in, - }) - if err != nil { - return "", err - } - return tagBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/types.go b/tools/goctl/model/sql/gen/types.go index 9856f853..9d8be7ab 100644 --- a/tools/goctl/model/sql/gen/types.go +++ b/tools/goctl/model/sql/gen/types.go @@ -1,30 +1,25 @@ package gen import ( - "bytes" - "text/template" - - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genTypes(table *InnerTable) (string, error) { +func genTypes(table Table, withCache bool) (string, error) { fields := table.Fields - t, err := template.New("types").Parse(sqltemplate.Types) - if err != nil { - return "", nil - } - var typeBuffer = new(bytes.Buffer) fieldsString, err := genFields(fields) if err != nil { return "", err } - err = t.Execute(typeBuffer, map[string]interface{}{ - "upperObject": table.UpperCamelCase, - "containsCache": table.ContainsCache, - "fields": fieldsString, - }) + output, err := templatex.With("types"). + Parse(template.Types). + Execute(map[string]interface{}{ + "withCache": withCache, + "upperStartCamelObject": table.Name.Snake2Camel(), + "fields": fieldsString, + }) if err != nil { return "", err } - return typeBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/update.go b/tools/goctl/model/sql/gen/update.go index 27d6de6f..3a034b63 100644 --- a/tools/goctl/model/sql/gen/update.go +++ b/tools/goctl/model/sql/gen/update.go @@ -1,38 +1,40 @@ package gen import ( - "bytes" "strings" - "text/template" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genUpdate(table *InnerTable) (string, error) { - t, err := template.New("update").Parse(sqltemplate.Update) +func genUpdate(table Table, withCache bool) (string, error) { + expressionValues := make([]string, 0) + for _, filed := range table.Fields { + camel := filed.Name.Snake2Camel() + if camel == "CreateTime" || camel == "UpdateTime" { + continue + } + if filed.IsPrimaryKey { + continue + } + expressionValues = append(expressionValues, "data."+camel) + } + expressionValues = append(expressionValues, "data."+table.PrimaryKey.Name.Snake2Camel()) + camelTableName := table.Name.Snake2Camel() + output, err := templatex.With("update"). + Parse(template.Update). + Execute(map[string]interface{}{ + "withCache": withCache, + "upperStartCamelObject": camelTableName, + "primaryCacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].DataKeyExpression, + "primaryKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable, + "lowerStartCamelObject": stringx.From(camelTableName).LowerStart(), + "originalPrimaryKey": table.PrimaryKey.Name.Source(), + "expressionValues": strings.Join(expressionValues, ", "), + }) if err != nil { return "", nil } - updateBuffer := new(bytes.Buffer) - expressionValues := make([]string, 0) - for _, filed := range table.Fields { - if filed.SnakeCase == "create_time" || filed.SnakeCase == "update_time" || filed.IsPrimaryKey { - continue - } - expressionValues = append(expressionValues, "data."+filed.UpperCamelCase) - } - expressionValues = append(expressionValues, "data."+table.PrimaryField.UpperCamelCase) - err = t.Execute(updateBuffer, map[string]interface{}{ - "containsCache": table.ContainsCache, - "upperObject": table.UpperCamelCase, - "primaryCacheKey": table.CacheKey[table.PrimaryField.SnakeCase].DataKey, - "primaryKeyVariable": table.CacheKey[table.PrimaryField.SnakeCase].KeyVariable, - "lowerObject": table.LowerCamelCase, - "primarySnakeCase": table.PrimaryField.SnakeCase, - "expressionValues": strings.Join(expressionValues, ", "), - }) - if err != nil { - return "", err - } - return updateBuffer.String(), nil + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/vars.go b/tools/goctl/model/sql/gen/vars.go index f5ed6aea..d2d00943 100644 --- a/tools/goctl/model/sql/gen/vars.go +++ b/tools/goctl/model/sql/gen/vars.go @@ -1,36 +1,33 @@ package gen import ( - "bytes" "strings" - "text/template" - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genVars(table *InnerTable) (string, error) { - t, err := template.New("vars").Parse(sqltemplate.Vars) - if err != nil { - return "", err - } - varBuffer := new(bytes.Buffer) - m, err := genCacheKeys(table) - if err != nil { - return "", err - } +func genVars(table Table, withCache bool) (string, error) { keys := make([]string, 0) - for _, v := range m { - keys = append(keys, v.Expression) + for _, v := range table.CacheKey { + keys = append(keys, v.VarExpression) } - err = t.Execute(varBuffer, map[string]interface{}{ - "lowerObject": table.LowerCamelCase, - "upperObject": table.UpperCamelCase, - "createNotFound": table.CreateNotFound, - "keysDefine": strings.Join(keys, "\r\n"), - "snakePrimaryKey": table.PrimaryField.SnakeCase, - }) + camel := table.Name.Snake2Camel() + output, err := templatex.With("var"). + Parse(template.Vars). + GoFmt(true). + Execute(map[string]interface{}{ + "lowerStartCamelObject": stringx.From(camel).LowerStart(), + "upperStartCamelObject": camel, + "cacheKeys": strings.Join(keys, "\r\n"), + "autoIncrement": table.PrimaryKey.AutoIncrement, + "originalPrimaryKey": table.PrimaryKey.Name.Source(), + "withCache": withCache, + }) if err != nil { return "", err } - return varBuffer.String(), nil + + return output.String(), nil } diff --git a/tools/goctl/model/sql/parser/error.go b/tools/goctl/model/sql/parser/error.go new file mode 100644 index 00000000..57f56a0c --- /dev/null +++ b/tools/goctl/model/sql/parser/error.go @@ -0,0 +1,11 @@ +package parser + +import ( + "errors" +) + +var ( + unSupportDDL = errors.New("unexpected type") + tableBodyIsNotFound = errors.New("create table spec not found") + errPrimaryKey = errors.New("unexpected joint primary key") +) diff --git a/tools/goctl/model/sql/parser/parser.go b/tools/goctl/model/sql/parser/parser.go new file mode 100644 index 00000000..b3701ae3 --- /dev/null +++ b/tools/goctl/model/sql/parser/parser.go @@ -0,0 +1,135 @@ +package parser + +import ( + "fmt" + + "github.com/tal-tech/go-zero/tools/goctl/model/sql/converter" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/xwb1989/sqlparser" +) + +const ( + none = iota + primary + unique + normal + spatial +) + +type ( + Table struct { + Name stringx.String + PrimaryKey Primary + Fields []Field + } + Primary struct { + Field + AutoIncrement bool + } + Field struct { + Name stringx.String + DataBaseType string + DataType string + IsKey bool + IsPrimaryKey bool + Comment string + } + KeyType int +) + +func Parse(ddl string) (*Table, error) { + stmt, err := sqlparser.ParseStrictDDL(ddl) + if err != nil { + return nil, err + } + ddlStmt, ok := stmt.(*sqlparser.DDL) + if !ok { + return nil, unSupportDDL + } + action := ddlStmt.Action + if action != sqlparser.CreateStr { + return nil, fmt.Errorf("expected [CREATE] action,but found: %s", action) + } + tableName := ddlStmt.NewName.Name.String() + tableSpec := ddlStmt.TableSpec + if tableSpec == nil { + return nil, tableBodyIsNotFound + } + + columns := tableSpec.Columns + indexes := tableSpec.Indexes + + keyMap := make(map[string]KeyType) + for _, index := range indexes { + info := index.Info + if info == nil { + continue + } + if info.Primary { + if len(index.Columns) > 1 { + return nil, errPrimaryKey + } + keyMap[index.Columns[0].Column.String()] = primary + continue + } + // can optimize + if len(index.Columns) > 1 { + continue + } + column := index.Columns[0] + columnName := column.Column.String() + camelColumnName := stringx.From(columnName).Snake2Camel() + // by default, createTime|updateTime findOne is not used. + if camelColumnName == "CreateTime" || camelColumnName == "UpdateTime" { + continue + } + if info.Unique { + keyMap[columnName] = unique + } else if info.Spatial { + keyMap[columnName] = spatial + } else { + keyMap[columnName] = normal + } + } + var ( + fields []Field + primaryKey Primary + ) + + for _, column := range columns { + if column == nil { + continue + } + var comment string + if column.Type.Comment != nil { + comment = string(column.Type.Comment.Val) + } + dataType, err := converter.ConvertDataType(column.Type.Type) + if err != nil { + return nil, err + } + var field Field + field.Name = stringx.From(column.Name.String()) + field.DataBaseType = column.Type.Type + field.DataType = dataType + field.Comment = comment + key, ok := keyMap[column.Name.String()] + if ok { + field.IsKey = true + field.IsPrimaryKey = key == primary + if field.IsPrimaryKey { + primaryKey.Field = field + if column.Type.Autoincrement { + primaryKey.AutoIncrement = true + } + } + } + fields = append(fields, field) + } + return &Table{ + Name: stringx.From(tableName), + PrimaryKey: primaryKey, + Fields: fields, + }, nil + +} diff --git a/tools/goctl/model/sql/parser/parser_test.go b/tools/goctl/model/sql/parser/parser_test.go new file mode 100644 index 00000000..4a837766 --- /dev/null +++ b/tools/goctl/model/sql/parser/parser_test.go @@ -0,0 +1,27 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParsePlainText(t *testing.T) { + _, err := Parse("plain text") + assert.NotNil(t, err) +} + +func TestParseSelect(t *testing.T) { + _, err := Parse("select * from user") + assert.Equal(t, unSupportDDL, err) +} + +func TestParseCreateTable(t *testing.T) { + _, err := Parse("CREATE TABLE `user_snake` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `name_index` (`name`),\n KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;") + assert.Nil(t, err) +} + +func TestParseCreateTable2(t *testing.T) { + _, err := Parse("create table `user_snake` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `name_index` (`name`),\n KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;") + assert.Nil(t, err) +} diff --git a/tools/goctl/model/sql/sqlctl.go b/tools/goctl/model/sql/sqlctl.go deleted file mode 100644 index 56459388..00000000 --- a/tools/goctl/model/sql/sqlctl.go +++ /dev/null @@ -1,22 +0,0 @@ -package sqlgen - -import ( - "errors" - - "github.com/tal-tech/go-zero/tools/goctl/model/sql/gen" -) - -var ( - ErrCircleQuery = errors.New("circle query with other fields") -) - -func Gen(in gen.OuterTable) (string, error) { - t, err := gen.TableConvert(in) - if err != nil { - if err == gen.ErrCircleQuery { - return "", ErrCircleQuery - } - return "", err - } - return gen.GenModel(t) -} diff --git a/tools/goctl/model/sql/template/delete.go b/tools/goctl/model/sql/template/delete.go index 372aff45..1830043e 100644 --- a/tools/goctl/model/sql/template/delete.go +++ b/tools/goctl/model/sql/template/delete.go @@ -1,17 +1,17 @@ -package sqltemplate +package template var Delete = ` -func (m *{{.upperObject}}Model) Delete({{.lowerPrimaryKey}} {{.dataType}}) error { - {{if .containsCache}}{{if .isNotPrimaryKey}}data,err:=m.FindOne({{.lowerPrimaryKey}}) +func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error { + {{if .withCache}}{{if .containsIndexCache}}data,err:=m.FindOne({{.lowerStartCamelPrimaryKey}}) if err!=nil{ return err }{{end}} {{.keys}} - _, err {{if .isNotPrimaryKey}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { - query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.snakePrimaryKey}} = ?` + "`" + ` - return conn.Exec(query, {{.lowerPrimaryKey}}) - }, {{.keyValues}}){{else}}query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.snakePrimaryKey}} = ?` + "`" + ` - _,err:=m.ExecNoCache(query, {{.lowerPrimaryKey}}){{end}} + _, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + ` + return conn.Exec(query, {{.lowerStartCamelPrimaryKey}}) + }, {{.keyValues}}){{else}}query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + ` + _,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}} return err } ` diff --git a/tools/goctl/model/sql/template/errors.go b/tools/goctl/model/sql/template/errors.go index 4df82d95..a24ff3fa 100644 --- a/tools/goctl/model/sql/template/errors.go +++ b/tools/goctl/model/sql/template/errors.go @@ -1,4 +1,4 @@ -package sqltemplate +package template var Error = `package model diff --git a/tools/goctl/model/sql/template/field.go b/tools/goctl/model/sql/template/field.go index 8a76cb40..493091cc 100644 --- a/tools/goctl/model/sql/template/field.go +++ b/tools/goctl/model/sql/template/field.go @@ -1,3 +1,3 @@ -package sqltemplate +package template -var Field = `{{.name}} {{.type}} {{.tag}} {{.comment}}` +var Field = `{{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}}` diff --git a/tools/goctl/model/sql/template/find.go b/tools/goctl/model/sql/template/find.go index 0d59f53e..72aa0776 100644 --- a/tools/goctl/model/sql/template/find.go +++ b/tools/goctl/model/sql/template/find.go @@ -1,13 +1,13 @@ -package sqltemplate +package template // 通过id查询 var FindOne = ` -func (m *{{.upperObject}}Model) FindOne({{.lowerPrimaryKey}} {{.dataType}}) (*{{.upperObject}}, error) { +func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) { {{if .withCache}}{{.cacheKey}} - var resp {{.upperObject}} + var resp {{.upperStartCamelObject}} err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error { - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.snakePrimaryKey}} = ? limit 1` + "`" + ` - return conn.QueryRow(v, query, {{.lowerPrimaryKey}}) + query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + ` + return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}}) }) switch err { case nil: @@ -16,9 +16,9 @@ func (m *{{.upperObject}}Model) FindOne({{.lowerPrimaryKey}} {{.dataType}}) (*{{ return nil, ErrNotFound default: return nil, err - }{{else}}query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.snakePrimaryKey}} = ? limit 1` + "`" + ` - var resp {{.upperObject}} - err := m.QueryRowNoCache(&resp, query, {{.lowerPrimaryKey}}) + }{{else}}query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + ` + var resp {{.upperStartCamelObject}} + err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}}) switch err { case nil: return &resp, nil @@ -32,19 +32,19 @@ func (m *{{.upperObject}}Model) FindOne({{.lowerPrimaryKey}} {{.dataType}}) (*{{ // 通过指定字段查询 var FindOneByField = ` -func (m *{{.upperObject}}Model) FindOneBy{{.upperFields}}({{.in}}) (*{{.upperObject}}, error) { - {{if .onlyOneFiled}}{{if .withCache}}{{.cacheKey}} - var resp {{.upperObject}} +func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) { + {{if .withCache}}{{.cacheKey}} + var resp {{.upperStartCamelObject}} err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, func(primary interface{}) string { - return fmt.Sprintf("%s%v", {{.primaryKeyDefine}}, primary) + return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary) }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.snakeField}} = ? limit 1` + "`" + ` - if err := conn.QueryRow(&resp, query, {{.lowerField}}); err != nil { + query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} = ? limit 1` + "`" + ` + if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil { return nil, err } - return resp.{{.UpperPrimaryKey}}, nil + return resp.{{.upperStartCamelPrimaryKey}}, nil }, func(conn sqlx.SqlConn, v, primary interface{}) error { - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.primarySnakeField}} = ? limit 1` + "`" + ` + query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryField}} = ? limit 1` + "`" + ` return conn.QueryRow(v, query, primary) }) switch err { @@ -54,9 +54,10 @@ func (m *{{.upperObject}}Model) FindOneBy{{.upperFields}}({{.in}}) (*{{.upperObj return nil, ErrNotFound default: return nil, err - }{{else}}var resp {{.upperObject}} - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.expression}} limit 1` + "`" + ` - err := m.QueryRowNoCache(&resp, query, {{.expressionValues}}) + } +}{{else}}var resp {{.upperStartCamelObject}} + query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} limit 1` + "`" + ` + err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}}) switch err { case nil: return &resp, nil @@ -64,52 +65,6 @@ func (m *{{.upperObject}}Model) FindOneBy{{.upperFields}}({{.in}}) (*{{.upperObj return nil, ErrNotFound default: return nil, err - }{{end}}{{else}}var resp {{.upperObject}} - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.expression}} limit 1` + "`" + ` - err := m.QueryRowNoCache(&resp, query, {{.expressionValues}}) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - }{{end}} -} -` - -// 查询all -var FindAllByField = ` -func (m *{{.upperObject}}Model) FindAllBy{{.upperFields}}({{.in}}) ([]*{{.upperObject}}, error) { - var resp []*{{.upperObject}} - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.expression}}` + "`" + ` - err := m.QueryRowsNoCache(&resp, query, {{.expressionValues}}) - if err != nil { - return nil, err } - return resp, nil -} -` - -// limit分页查询 -var FindLimitByField = ` -func (m *{{.upperObject}}Model) FindLimitBy{{.upperFields}}({{.in}}, page, limit int) ([]*{{.upperObject}}, error) { - var resp []*{{.upperObject}} - query := ` + "`" + `select ` + "`" + ` + {{.lowerObject}}Rows + ` + "`" + `from ` + "` + " + `m.table ` + " + `" + ` where {{.expression}} order by {{.sortExpression}} limit ?,?` + "`" + ` - err := m.QueryRowsNoCache(&resp, query, {{.expressionValues}}, (page-1)*limit, limit) - if err != nil { - return nil, err - } - return resp, nil -} - -func (m *{{.upperObject}}Model) FindAllCountBy{{.upperFields}}({{.in}}) (int64, error) { - var count int64 - query := ` + "`" + `select count(1) from ` + "` + " + `m.table ` + " + `" + ` where {{.expression}} ` + "`" + ` - err := m.QueryRowNoCache(&count, query, {{.expressionValues}}) - if err != nil { - return 0, err - } - return count, nil -} +}{{end}} ` diff --git a/tools/goctl/model/sql/template/import.go b/tools/goctl/model/sql/template/import.go index dd2c1555..83ec2abb 100644 --- a/tools/goctl/model/sql/template/import.go +++ b/tools/goctl/model/sql/template/import.go @@ -1,15 +1,28 @@ -package sqltemplate +package template -var Imports = ` -import ( - {{if .containsCache}}"database/sql" - "fmt"{{end}} +var ( + Imports = `import ( + "database/sql" + "fmt" "strings" "time" - "github.com/tal-tech/go-zero/core/stores/cache" + "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/core/stores/sqlc" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/core/stringx" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" ) ` + ImportsNoCache = `import ( + "database/sql" + "strings" + "time" + + "github.com/tal-tech/go-zero/core/stores/sqlc" + "github.com/tal-tech/go-zero/core/stores/sqlx" + "github.com/tal-tech/go-zero/core/stringx" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" +) +` +) diff --git a/tools/goctl/model/sql/template/insert.go b/tools/goctl/model/sql/template/insert.go index c97a9a8e..5524c8b8 100644 --- a/tools/goctl/model/sql/template/insert.go +++ b/tools/goctl/model/sql/template/insert.go @@ -1,9 +1,8 @@ -package sqltemplate +package template var Insert = ` -func (m *{{.upperObject}}Model) Insert(data {{.upperObject}}) error { - query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "`(` + " + `{{.lowerObject}}RowsExpectAutoSet` + " + `) value ({{.expression}})` " + ` - _, err := m.ExecNoCache(query, {{.expressionValues}}) - return err +func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result, error) { + query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "`(` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) value ({{.expression}})` " + ` + return m.{{if .withCache}}ExecNoCache{{else}}conn.Exec{{end}}(query, {{.expressionValues}}) } ` diff --git a/tools/goctl/model/sql/template/model.go b/tools/goctl/model/sql/template/model.go index 3fbe847f..4509b2d6 100644 --- a/tools/goctl/model/sql/template/model.go +++ b/tools/goctl/model/sql/template/model.go @@ -1,4 +1,4 @@ -package sqltemplate +package template var Model = `package model {{.imports}} diff --git a/tools/goctl/model/sql/template/new.go b/tools/goctl/model/sql/template/new.go index 70c2b6cf..2017259a 100644 --- a/tools/goctl/model/sql/template/new.go +++ b/tools/goctl/model/sql/template/new.go @@ -1,9 +1,9 @@ -package sqltemplate +package template var New = ` -func New{{.upperObject}}Model(conn sqlx.SqlConn, c cache.CacheConf, table string) *{{.upperObject}}Model { - return &{{.upperObject}}Model{ - CachedConn: sqlc.NewConn(conn, c), +func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn,{{if .withCache}} c cache.CacheConf,{{end}} table string) *{{.upperStartCamelObject}}Model { + return &{{.upperStartCamelObject}}Model{ + {{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}}, table: table, } } diff --git a/tools/goctl/model/sql/template/tag.go b/tools/goctl/model/sql/template/tag.go index 74389ca4..2491b23f 100644 --- a/tools/goctl/model/sql/template/tag.go +++ b/tools/goctl/model/sql/template/tag.go @@ -1,3 +1,3 @@ -package sqltemplate +package template var Tag = "`db:\"{{.field}}\"`" diff --git a/tools/goctl/model/sql/template/types.go b/tools/goctl/model/sql/template/types.go index e3f11fe5..19df5e4e 100644 --- a/tools/goctl/model/sql/template/types.go +++ b/tools/goctl/model/sql/template/types.go @@ -1,13 +1,13 @@ -package sqltemplate +package template var Types = ` type ( - {{.upperObject}}Model struct { - sqlc.CachedConn + {{.upperStartCamelObject}}Model struct { + {{if .withCache}}sqlc.CachedConn{{else}}conn sqlx.SqlConn{{end}} table string } - {{.upperObject}} struct { + {{.upperStartCamelObject}} struct { {{.fields}} } ) diff --git a/tools/goctl/model/sql/template/update.go b/tools/goctl/model/sql/template/update.go index f2a3f6c2..72befc1a 100644 --- a/tools/goctl/model/sql/template/update.go +++ b/tools/goctl/model/sql/template/update.go @@ -1,13 +1,13 @@ -package sqltemplate +package template var Update = ` -func (m *{{.upperObject}}Model) Update(data {{.upperObject}}) error { - {{if .containsCache}}{{.primaryCacheKey}} +func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error { + {{if .withCache}}{{.primaryCacheKey}} _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { - query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerObject}}RowsWithPlaceHolder` + " + `" + ` where {{.primarySnakeCase}} = ?` + "`" + ` + query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + ` return conn.Exec(query, {{.expressionValues}}) - }, {{.primaryKeyVariable}}){{else}}query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerObject}}RowsWithPlaceHolder` + " + `" + ` where {{.primarySnakeCase}} = ?` + "`" + ` - _,err:=m.ExecNoCache(query, {{.expressionValues}}){{end}} + }, {{.primaryKeyVariable}}){{else}}query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + ` + _,err:=m.conn.Exec(query, {{.expressionValues}}){{end}} return err } ` diff --git a/tools/goctl/model/sql/template/vars.go b/tools/goctl/model/sql/template/vars.go index 18b06b3b..9d0da1b8 100644 --- a/tools/goctl/model/sql/template/vars.go +++ b/tools/goctl/model/sql/template/vars.go @@ -1,14 +1,12 @@ -package sqltemplate +package template var Vars = ` var ( - {{.lowerObject}}FieldNames = builderx.FieldNames(&{{.upperObject}}{}) - {{.lowerObject}}Rows = strings.Join({{.lowerObject}}FieldNames, ",") - {{.lowerObject}}RowsExpectAutoSet = strings.Join(stringx.Remove({{.lowerObject}}FieldNames, "{{.snakePrimaryKey}}", "create_time", "update_time"), ",") - {{.lowerObject}}RowsWithPlaceHolder = strings.Join(stringx.Remove({{.lowerObject}}FieldNames, "{{.snakePrimaryKey}}", "create_time", "update_time"), "=?,") + "=?" + {{.lowerStartCamelObject}}FieldNames = builderx.FieldNames(&{{.upperStartCamelObject}}{}) + {{.lowerStartCamelObject}}Rows = strings.Join({{.lowerStartCamelObject}}FieldNames, ",") + {{.lowerStartCamelObject}}RowsExpectAutoSet = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ",") + {{.lowerStartCamelObject}}RowsWithPlaceHolder = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "create_time", "update_time"), "=?,") + "=?" - {{.keysDefine}} - - {{if .createNotFound}}ErrNotFound = sqlx.ErrNotFound{{end}} + {{if .withCache}}{{.cacheKeys}}{{end}} ) ` diff --git a/tools/goctl/model/sql/util/stringurtl_test.go b/tools/goctl/model/sql/util/stringurtl_test.go deleted file mode 100644 index e43f0b51..00000000 --- a/tools/goctl/model/sql/util/stringurtl_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package util - -import ( - "fmt" - "testing" -) - -func TestFormatField(t *testing.T) { - var in = "go_java" - snakeCase, upperCase, lowerCase := FormatField(in) - fmt.Println(snakeCase, upperCase, lowerCase) -} diff --git a/tools/goctl/model/sql/util/stringutil.go b/tools/goctl/model/sql/util/stringutil.go deleted file mode 100644 index 55b8ae18..00000000 --- a/tools/goctl/model/sql/util/stringutil.go +++ /dev/null @@ -1,48 +0,0 @@ -package util - -import ( - "strings" - "unicode" -) - -func FormatField(field string) (snakeCase, upperCamelCase, lowerCamelCase string) { - snakeCase = field - list := strings.Split(field, "_") - upperCaseList := make([]string, 0) - lowerCaseList := make([]string, 0) - for index, word := range list { - upperStart := convertUpperStart(word) - lowerStart := convertLowerStart(word) - upperCaseList = append(upperCaseList, upperStart) - if index == 0 { - lowerCaseList = append(lowerCaseList, lowerStart) - } else { - lowerCaseList = append(lowerCaseList, upperStart) - } - } - upperCamelCase = strings.Join(upperCaseList, "") - lowerCamelCase = strings.Join(lowerCaseList, "") - return -} - -func convertLowerStart(in string) string { - var resp []rune - for index, r := range in { - if index == 0 { - r = unicode.ToLower(r) - } - resp = append(resp, r) - } - return string(resp) -} - -func convertUpperStart(in string) string { - var resp []rune - for index, r := range in { - if index == 0 { - r = unicode.ToUpper(r) - } - resp = append(resp, r) - } - return string(resp) -} diff --git a/tools/goctl/util/console/console.go b/tools/goctl/util/console/console.go new file mode 100644 index 00000000..946c6574 --- /dev/null +++ b/tools/goctl/util/console/console.go @@ -0,0 +1,58 @@ +package console + +import ( + "fmt" + + "github.com/logrusorgru/aurora" +) + +type ( + Console interface { + Success(format string, a ...interface{}) + Warning(format string, a ...interface{}) + Error(format string, a ...interface{}) + } + colorConsole struct { + } + // for idea log + ideaConsole struct { + } +) + +func NewColorConsole() *colorConsole { + return &colorConsole{} +} + +func (c *colorConsole) Success(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println(aurora.Green(msg)) +} + +func (c *colorConsole) Warning(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println(aurora.Yellow(msg)) +} + +func (c *colorConsole) Error(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println(aurora.Red(msg)) +} + +func NewIdeaConsole() *ideaConsole { + return &ideaConsole{} +} + +func (i *ideaConsole) Success(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println("[SUCCESS]: ", msg) +} + +func (i *ideaConsole) Warning(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println("[WARNING]: ", msg) +} + +func (i *ideaConsole) Error(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println("[ERROR]: ", msg) +} diff --git a/tools/goctl/util/stringx/string.go b/tools/goctl/util/stringx/string.go new file mode 100644 index 00000000..2ce48d57 --- /dev/null +++ b/tools/goctl/util/stringx/string.go @@ -0,0 +1,126 @@ +package stringx + +import ( + "bytes" + "strings" + "unicode" +) + +const ( + emptyString = "" +) + +type ( + String struct { + source string + } +) + +func From(data string) String { + return String{source: data} +} + +func (s String) IsEmptyOrSpace() bool { + if len(s.source) == 0 { + return true + } + if strings.TrimSpace(s.source) == "" { + return true + } + return false +} + +func (s String) Lower() string { + if s.IsEmptyOrSpace() { + return s.source + } + return strings.ToLower(s.source) +} +func (s String) Upper() string { + if s.IsEmptyOrSpace() { + return s.source + } + return strings.ToUpper(s.source) +} +func (s String) Title() string { + if s.IsEmptyOrSpace() { + return s.source + } + return strings.Title(s.source) +} + +// snake->camel(upper start) +func (s String) Snake2Camel() string { + if s.IsEmptyOrSpace() { + return s.source + } + list := s.splitBy(func(r rune) bool { + return r == '_' + }, true) + var target []string + for _, item := range list { + target = append(target, From(item).Title()) + } + return strings.Join(target, "") +} + +// camel->snake +func (s String) Camel2Snake() string { + if s.IsEmptyOrSpace() { + return s.source + } + list := s.splitBy(func(r rune) bool { + return unicode.IsUpper(r) + }, false) + var target []string + for _, item := range list { + target = append(target, From(item).Lower()) + } + return strings.Join(target, "_") +} + +// return original string if rune is not letter at index 0 +func (s String) LowerStart() string { + if s.IsEmptyOrSpace() { + return s.source + } + r := rune(s.source[0]) + if !unicode.IsUpper(r) && !unicode.IsLower(r) { + return s.source + } + return string(unicode.ToLower(r)) + s.source[1:] +} + +// it will not ignore spaces +func (s String) splitBy(fn func(r rune) bool, remove bool) []string { + if s.IsEmptyOrSpace() { + return nil + } + var list []string + buffer := new(bytes.Buffer) + for _, r := range s.source { + if fn(r) { + if buffer.Len() != 0 { + list = append(list, buffer.String()) + buffer.Reset() + } + if !remove { + buffer.WriteRune(r) + } + continue + } + buffer.WriteRune(r) + } + if buffer.Len() != 0 { + list = append(list, buffer.String()) + } + return list +} + +func (s String) ReplaceAll(old, new string) string { + return strings.ReplaceAll(s.source, old, new) +} + +func (s String) Source() string { + return s.source +} diff --git a/tools/goctl/util/stringx/string_test.go b/tools/goctl/util/stringx/string_test.go new file mode 100644 index 00000000..b0ad07ff --- /dev/null +++ b/tools/goctl/util/stringx/string_test.go @@ -0,0 +1,42 @@ +package stringx + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestString_IsEmptyOrSpace(t *testing.T) { + ret := From(" ").IsEmptyOrSpace() + assert.Equal(t, true, ret) + ret2 := From("ll??").IsEmptyOrSpace() + assert.Equal(t, false, ret2) + ret3 := From(` + `).IsEmptyOrSpace() + assert.Equal(t, true, ret3) +} + +func TestString_Snake2Camel(t *testing.T) { + ret := From("____this_is_snake").Snake2Camel() + assert.Equal(t, "ThisIsSnake", ret) + + ret2 := From("测试_test_Data").Snake2Camel() + assert.Equal(t, "测试TestData", ret2) + + ret3 := From("___").Snake2Camel() + assert.Equal(t, "", ret3) + + ret4 := From("testData_").Snake2Camel() + assert.Equal(t, "TestData", ret4) + + ret5 := From("testDataTestData").Snake2Camel() + assert.Equal(t, "TestDataTestData", ret5) +} + +func TestString_Camel2Snake(t *testing.T) { + ret := From("ThisIsCCCamel").Camel2Snake() + assert.Equal(t, "this_is_c_c_camel", ret) + + ret2 := From("测试Test_Data_test_data").Camel2Snake() + assert.Equal(t, "测试_test__data_test_data", ret2) +} diff --git a/tools/goctl/util/templatex/templatex.go b/tools/goctl/util/templatex/templatex.go new file mode 100644 index 00000000..e8b448db --- /dev/null +++ b/tools/goctl/util/templatex/templatex.go @@ -0,0 +1,67 @@ +package templatex + +import ( + "bytes" + goformat "go/format" + "io/ioutil" + "os" + "text/template" +) + +type ( + defaultTemplate struct { + name string + text string + goFmt bool + savePath string + } +) + +func With(name string) *defaultTemplate { + return &defaultTemplate{ + name: name, + } +} +func (t *defaultTemplate) Parse(text string) *defaultTemplate { + t.text = text + return t +} + +func (t *defaultTemplate) GoFmt(format bool) *defaultTemplate { + t.goFmt = format + return t +} + +func (t *defaultTemplate) SaveTo(data interface{}, path string) error { + output, err := t.execute(data) + if err != nil { + return err + } + return ioutil.WriteFile(path, output.Bytes(), os.ModePerm) +} + +func (t *defaultTemplate) Execute(data interface{}) (*bytes.Buffer, error) { + return t.execute(data) +} + +func (t *defaultTemplate) execute(data interface{}) (*bytes.Buffer, error) { + tem, err := template.New(t.name).Parse(t.text) + if err != nil { + return nil, err + } + buf := new(bytes.Buffer) + err = tem.Execute(buf, data) + if err != nil { + return nil, err + } + if !t.goFmt { + return buf, nil + } + formatOutput, err := goformat.Source(buf.Bytes()) + if err != nil { + return nil, err + } + buf.Reset() + buf.Write(formatOutput) + return buf, nil +} diff --git a/tools/modelctl/model/configtemplategen/configtemplate.go b/tools/modelctl/model/configtemplategen/configtemplate.go deleted file mode 100644 index 05ac87a1..00000000 --- a/tools/modelctl/model/configtemplategen/configtemplate.go +++ /dev/null @@ -1,15 +0,0 @@ -package configgen - -const configTemplate = `// TODO replace * to your real value -{ - "WithCache": false, - "Force": true, - "Username": "***", - "Password": "***", - "Address": "**", - "TableSchema":"*", - "Tables": [ - "**" - ] -} -` diff --git a/tools/modelctl/model/configtemplategen/gen.go b/tools/modelctl/model/configtemplategen/gen.go deleted file mode 100644 index c40332ea..00000000 --- a/tools/modelctl/model/configtemplategen/gen.go +++ /dev/null @@ -1,28 +0,0 @@ -package configgen - -import ( - "fmt" - "os" - - "github.com/tal-tech/go-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 -} diff --git a/tools/modelctl/model/modelgen/fieldmodel.go b/tools/modelctl/model/modelgen/fieldmodel.go deleted file mode 100644 index 16887dbe..00000000 --- a/tools/modelctl/model/modelgen/fieldmodel.go +++ /dev/null @@ -1,55 +0,0 @@ -package modelgen - -import "github.com/tal-tech/go-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 -} diff --git a/tools/modelctl/model/modelgen/gen.go b/tools/modelctl/model/modelgen/gen.go deleted file mode 100644 index c30f7cea..00000000 --- a/tools/modelctl/model/modelgen/gen.go +++ /dev/null @@ -1,41 +0,0 @@ -package modelgen - -import ( - "errors" - "fmt" - "strings" - - "github.com/tal-tech/go-zero/core/logx" - "github.com/urfave/cli" -) - -func FileModelCommand(c *cli.Context) error { - configFile := c.String("config") - if len(configFile) == 0 { - return errors.New("missing config value") - } - logx.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) - logx.Must(genModelWithDataSource(address, schema, force, redis, nil)) - return nil -} diff --git a/tools/modelctl/model/modelgen/genmodel.go b/tools/modelctl/model/modelgen/genmodel.go deleted file mode 100644 index 5abe2fb4..00000000 --- a/tools/modelctl/model/modelgen/genmodel.go +++ /dev/null @@ -1,164 +0,0 @@ -package modelgen - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "os/exec" - "strings" - "text/template" - - "github.com/tal-tech/go-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 -} diff --git a/tools/modelctl/model/modelgen/modeltemplate.go b/tools/modelctl/model/modelgen/modeltemplate.go deleted file mode 100644 index 3e19e97e..00000000 --- a/tools/modelctl/model/modelgen/modeltemplate.go +++ /dev/null @@ -1,247 +0,0 @@ -package modelgen - -import ( - "bytes" - "errors" - "fmt" - "strings" - "text/template" - - "github.com/tal-tech/go-zero/tools/modelctl/model" - "github.com/tal-tech/go-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" - - "github.com/tal-tech/go-zero/core/stores/redis" - "github.com/tal-tech/go-zero/core/stores/sqlx" - "github.com/tal-tech/go-zero/service/shared/builderx" - "github.com/tal-tech/go-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 -} diff --git a/tools/modelctl/model/modelgen/utiltemplate.go b/tools/modelctl/model/modelgen/utiltemplate.go deleted file mode 100644 index 159b122d..00000000 --- a/tools/modelctl/model/modelgen/utiltemplate.go +++ /dev/null @@ -1,34 +0,0 @@ -package modelgen - -const ( - utilTemplateText = `package {{.Package}} - -import ( - "errors" - - {{if .WithCache}}"github.com/tal-tech/go-zero/core/stores/redis" - "github.com/tal-tech/go-zero/core/stores/sqlc" - "github.com/tal-tech/go-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") -) -` -) diff --git a/tools/modelctl/modelctl.go b/tools/modelctl/modelctl.go deleted file mode 100644 index 71d0bc1d..00000000 --- a/tools/modelctl/modelctl.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/tal-tech/go-zero/core/logx" - configgen "github.com/tal-tech/go-zero/tools/modelctl/model/configtemplategen" - "github.com/tal-tech/go-zero/tools/modelctl/model/modelgen" - "github.com/urfave/cli" -) - -var commands = []cli.Command{ - { - Name: "model", - Usage: "generate model files", - Subcommands: []cli.Command{ - { - Name: "template", - Usage: "generate the json config template", - Action: configgen.ConfigCommand, - }, - { - Name: "file", - Usage: "generated from a configuration file", - Action: modelgen.FileModelCommand, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "config,c", - Usage: "the file path of config", - }, - }, - }, - { - Name: "cmd", - Usage: "generated from the command line,it will be generated from ALL TABLES", - Action: modelgen.CmdModelCommand, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "address,a", - Usage: "database connection address,format:\"[username]:[password]@[address]\"", - }, - cli.StringFlag{ - Name: "schema,s", - Usage: "the target database name", - }, - cli.BoolFlag{ - Name: "force,f", - Usage: "whether to force the generation, if it is, it may cause the source file to be lost,[default:false]", - }, - cli.BoolFlag{ - Name: "redis,r", - Usage: "whether to generate with redis cache when generating files,[default:false]", - }, - }, - }, - }, - }, -} - -func main() { - logx.Disable() - app := cli.NewApp() - app.Usage = "a cli tool to generate model" - app.Version = "0.0.1" - app.Commands = commands - // cli already print error messages - if err := app.Run(os.Args); err != nil { - fmt.Println("error:", err) - } -} diff --git a/tools/modelctl/modelctl.md b/tools/modelctl/modelctl.md deleted file mode 100644 index f06beef6..00000000 --- a/tools/modelctl/modelctl.md +++ /dev/null @@ -1,60 +0,0 @@ -# modelctl使用说明 - -## modelctl用途 -* 根据数据库中表名生成model.go代码,目前支持通过【指定配置文件】和【命令行参数】两种形式来生成 - -## modelctl使用说明 -* modelctl参数说明 - - > 生成配置文件模板 - - `modelctl model template` - - > 根据指定配置文件生成*model.go,-c参数为配置文件名称 - - 参考命令:`modelctl -c config.json` - - > 根据命令行生成*model.go - - `modelctl cmd [--address|-a,--schema|-s,--force|-f,--redis|-r]` - - 参考命令:`modelctl cmd -a root:123456@127.0.0.1:3306 -s user -f -r ` - - `--address|-a` 数据库连接地址,格式:[username]:[password]@[address],参考格式:root:123456@127.0.0.1:3306 - - `--schema|-s` 指定数据库名称 - - `--force|-f` 是否强制覆盖源文件,默认:false,强制覆盖将导致原或已修改文件丢失 - - `--redis|-r` 是否生成redis缓存逻辑代码,默认:false - - 详细说明见 `--help|-h` - -* 配置文件模板说明 - - ``` - { - "WithCache": false, - "Force": true, - "Username": "***", - "Password": "***", - "Address": "**", - "TableSchema":"*", - "Tables": [ - "**" - ] - } - ``` - `WithCache` 生成文件时是否待redis缓存逻辑代码 - - `Force` 是否强制覆盖原有同名文件,覆盖则会丢失原文件 - - `Username` 数据库访问用户名 - - `Password` 数据库访问用户密码 - - `Address` 数据库访问地址 - - `TableSchema` 数据库名 - - `Tables` 指定生成model的表名,不填或空则按照该库下全部表进行生成 \ No newline at end of file diff --git a/tools/modelctl/util/util.go b/tools/modelctl/util/util.go deleted file mode 100644 index 4a0996d3..00000000 --- a/tools/modelctl/util/util.go +++ /dev/null @@ -1,51 +0,0 @@ -package util - -import ( - "bytes" - "strings" -) - -// 简单的下划线转驼峰格式 -func FmtUnderLine2Camel(in string, upperStart bool) string { - if strings.TrimSpace(in) == "" { - return "" - } - var words []string - if strings.Contains(in, "_") { - words = strings.Split(in, "_") - if len(words) == 0 { - return "" - } - } - if len(words) == 0 { - return strings.Title(in) - } - var buffer bytes.Buffer - for index, word := range words { - if strings.TrimSpace(word) == "" { - continue - } - bts := []byte(word) - if index == 0 && !upperStart { - bts[0] = bytes.ToLower([]byte{bts[0]})[0] - buffer.Write(bts) - continue - } - bts = bytes.Title(bts) - buffer.Write(bts) - } - return buffer.String() -} - -func Abbr(in string) string { - ar := strings.Split(in, "_") - var abbrBuffer bytes.Buffer - for _, item := range ar { - bts := []byte(item) - if len(bts) == 0 { - continue - } - abbrBuffer.Write([]byte{bts[0]}) - } - return abbrBuffer.String() -}