為 Go 項目自動生成 model
本文記錄了使用gen項目實現(xiàn)了一個model自動生成命令的思路,其最終展示效果:https://github.com/gohade/hade/blob/main/docs/guide/model.md
我們寫業(yè)務的時候和db接觸是少不了的,那么要生成model也是少不了的,如何自動生成model,想著要給hade框架增加個這樣的命令。
看了下網(wǎng)上的幾個開源項目,最終聚焦在兩個項目中:
https://github.com/go-gorm/gen
https://github.com/xxjwxc/gormt
gormt的gui是非常強大的,看文檔都支持終端gui和windows的gui。但是gormt是一個工具,無法在另外一個項目中引入。
gen項目是gorm官方推出的,有jinzhu作者的參與。所以我嘗試選擇gen項目。(對的,原因就是這么膚淺...)
gen
gen其實不只是工具,它更像一個全新的orm封裝。gen項目生成出來的文件有其實有兩個部分,一個是model,就是db的表和對應的model,以xxx.gen.go 命名。而另一個部分是每個model對應一套gen函數(shù),這套gen函數(shù)基本上是對orm的二次封裝了。

當然這套函數(shù)是基于gorm來封裝的,不過你可以完全脫離gorm來使用這套函數(shù)。
生成的方法示例如下:
g?:=?gen.NewGenerator(gen.Config{
???OutPath:??????"/Users/yejianfeng/Documents/workspace/gohade/hade/app/dal",
???ModelPkgPath:?"/Users/yejianfeng/Documents/workspace/gohade/hade/app/dal/model",
???WithUnitTest:?true,
???FieldNullable:?????false,
???FieldCoverable:????true,
???FieldWithIndexTag:?false,
???FieldWithTypeTag:??false,
???Mode:?gen.WithDefaultQuery,
??})
??gormService?:=?container.MustMake(contract.ORMKey).(contract.ORMService)
??db,?err?:=?gormService.GetDB(orm.WithConfigPath("database.default"))
??if?err?!=?nil?{
???return?err
??}
??g.UseDB(db)
??g.WithDataTypeMap(dataMap)
??//g.WithJSONTagNameStrategy(func(c?string)?string?{?return?"-"?})
??//g.ApplyBasic(model.Customer{})
??//g.ApplyBasic(g.GenerateAllTable()...)
??//g.GenerateModel("users")
??//g.GenerateModel("answers")
??//g.GenerateAllTable()
??g.ApplyBasic(g.GenerateAllTable()...)
??g.Execute()
使用起來像是這樣:
u?:=?query.Use(db).User
//?Get?first?matched?record
user,?err?:=?u.WithContext(ctx).Where(u.Name.Eq("modi")).First()
//?SELECT?*?FROM?users?WHERE?name?=?'modi'?ORDER?BY?id?LIMIT?1;
//?Get?all?matched?records
users,?err?:=?u.WithContext(ctx).Where(u.Name.Neq("modi")).Find()
//?SELECT?*?FROM?users?WHERE?name?<>?'modi';
//?IN
users,?err?:=?u.WithContext(ctx).Where(u.Name.In("modi",?"zhangqiang")).Find()
//?SELECT?*?FROM?users?WHERE?name?IN?('modi','zhangqiang');
//?LIKE
users,?err?:=?u.WithContext(ctx).Where(u.Name.Like("%modi%")).Find()
//?SELECT?*?FROM?users?WHERE?name?LIKE?'%modi%';
最終生成的文件如下:

gen有一些高級的功能:
自定義模型函數(shù),且提供了通過sql語句的注釋自實現(xiàn)函數(shù)的方法 提供了單元測試框架,你可以自己定義TestCase,來實現(xiàn)對某個自實現(xiàn)函數(shù)的單測 智能字段查詢,在select語句的時候,可以根據(jù)輸出對象自動生成select的field
其實gen更像是另一個orm框架了,和facebook的ent類似,為每個model生成一套orm方法。gen是字節(jié)跳動的無恒實驗室開發(fā)的產品,據(jù)說字節(jié)內部正在將gorm切換到gen。gen的主打是安全,意思是,如果你的orm是完全使用gen來生成的,通過注釋sql而不是自己裸寫sql來生成,它更能保證安全性。(當然,因為所有的實現(xiàn)代碼都是gen來自動生成的)。
我目前的認知還是覺得這套東西太重了一些,整個熟悉下來無異于需要了解另外一個orm框架的語法了。在使用gorm和gen上并沒有什么太大的區(qū)別。
//?插入一條數(shù)據(jù)
?email?:=?"[email protected]"
?name?:=?"foo"
?user?:=?&model.User{
??ID:????????0,
??Username:??name,
??Password:??"",
??Email:?????email,
??CreatedAt:?time.Time{},
?}
?dal.SetDefault(db)
?err?:=?dal.User.WithContext(c).Create(user)
?if?err?!=?nil?{
??c.AbortWithError(50001,?err)
??return
?}
//?插入一條數(shù)據(jù)
?email?:=?"[email protected]"
?name?:=?"foo"
?age?:=?uint8(25)
?birthday?:=?time.Date(2001,?1,?1,?1,?1,?1,?1,?time.Local)
?user?:=?&User{
??Name:?????????name,
??Email:????????&email,
??Age:??????????age,
??Birthday:?????&birthday,
??MemberNumber:?sql.NullString{},
??ActivatedAt:??sql.NullTime{},
??CreatedAt:????time.Now(),
??UpdatedAt:????time.Now(),
?}
?err?=?db.Create(user).Error
?logger.Info(c,?"insert?user",?map[string]interface{}{
??"id":??user.ID,
??"err":?err,
?})
而且如果要寫出官網(wǎng)給出的這么復雜的語句:
p?:=?query.Use(db).Pizza
pizzas,?err?:=?p.WithContext(ctx).Where(
????p.WithContext(ctx).Where(p.Pizza.Eq("pepperoni")).
????????Where(p.WithContext(ctx).Where(p.Size.Eq("small")).Or(p.Size.Eq("medium"))),
).Or(
????p.WithContext(ctx).Where(p.Pizza.Eq("hawaiian")).Where(p.Size.Eq("xlarge")),
).Find()
//?SELECT?*?FROM?`pizzas`?WHERE?(pizza?=?"pepperoni"?AND?(size?=?"small"?OR?size?=?"medium"))?OR?(pizza?=?"hawaiian"?AND?size?=?"xlarge")
我相信對新手來說真是一個不大容易的事情。
所以目前我還只傾向于使用gen的生成model的部分。
自動生成model命令設計
首先設計一下這個命令的產品形態(tài)。
./hade?model?gen?--output=document/app/model/?--database=database.default
在命令行中的參數(shù):
output:?必選,表示輸出的路徑
database:?可選,默認使用database.default
如果沒有設置db,或者output沒有設置,直接返回錯誤。
第一步是一個交互命令行工具,首先展示要生成的表列表選擇:
請選擇要生成模型的表格:
[]?*
[]?users
[]?answers
[]?questions
第二步確認要生成的目錄和文件,以及覆蓋提示:
繼續(xù)下列操作會在目錄(xxxx)生成下列文件:
user.gen.go(覆蓋)
answer.gen.go(新文件)
請確認是否繼續(xù)?(Y/N)
第三步選擇后是一個生成模型的選項:
請選擇模型規(guī)則:
[]?FieldNullable,?對于數(shù)據(jù)庫的可null字段設置指針
[]?FieldCoverable,?根據(jù)數(shù)據(jù)庫的Default設置字段的默認值
[]?FieldWithIndexTag,?根據(jù)數(shù)據(jù)庫的索引關系設置索引標簽
[]?FieldWithTypeTag,?生成類型字段
最后一步就是生成模型文件了。
自動生成model命令實現(xiàn)
了解了gen和命令的設計,實現(xiàn)就很簡單了。
大概就分幾步吧:
獲取數(shù)據(jù)庫中的所有表 讓用戶多選要生成model的表格 和現(xiàn)有的目錄中的文件進行比對 讓用戶多選要生成的model的選項,比如是否可null,是否有default設置等 使用gen生成模型文件
具體代碼在 https://github.com/gohade/hade/blob/feature/model-gen/framework/command/model/model.go
其中代碼實現(xiàn)方便稍微有幾個地方要注意下:
如何查詢一個數(shù)據(jù)庫中的所有表
使用gorm很方便就實現(xiàn)了
dbTables,?err?:=?db.Migrator().GetTables()
當用戶選擇了要生成的表格,要和硬盤中已有的文件進行比對,如何操作
這里其實涉及到兩個集合的交集和差集
我發(fā)現(xiàn)collection庫之前已經(jīng)實現(xiàn)了差集,但是沒有實現(xiàn)交集。
這里我補充實現(xiàn)了colleciton的交集,Intersect,并且將collection庫升級到1.4.1
//?Intersect?比較兩個數(shù)組,獲取兩個數(shù)組交集,僅對基礎元素生效
Intersect?(arr?ICollection)?ICollection
gen 庫如何只生成model不生成gen文件?
g.UseDB(db)
for?_,?table?:=?range?genTables?{
?g.GenerateModel(table)
}
g.Execute()
model命令驗證
驗證一下要model/gen命令
第一步,使用 ./hade model gen --output=app/model

選擇其中的兩個表,answers和questions,提示目錄文件

下一步確認y繼續(xù)

最后生成模型成功

查看文件,確實生成了model

功能完結。
更新項目git:https://github.com/gohade/hade
Hi,我是軒脈刃,一個名不見經(jīng)傳碼農,體制內的小憤青,躁動的騷年,2022年想堅持寫一些學習/工作/思考筆記,謂之倒逼學習。歡迎關注個人公眾號:軒脈刃的刀光劍影。
推薦閱讀
