基于go-zero的goctl二开,gin项目代码生成器
gin框架很轻量以构建http服务为主,但gin本身是没有相关的项目代码生成工具,在业界中也有相关的解决方案
常规的项目中大多以如下目录结构设计
shell
- project
- internal
- handler -- 或者 controller
- logic -- 或者 service
- repo -- 或者 models
- pkg -- 或者 util 或 common
整体上大同小异其目的就是划分模块然后完成功能,在常规工作中主要的重复工作为
- 创建控制器
- 注册控制器至路由处理中
- 对新增方法注册路由
- 创建logic业务处理对象
- 在控制器中引入 主要是上面的几个环节相对而言重复较多,因此基于这个问题我自己就开展了一个代码生成工具,该工具是基于go-zero的goctl二开得来,因此语法仍然是使用go-zero的定义
1. 使用
整体的语法与go-zero的goctl是一致的因此可以参考这里 go-zero.dev/docs/tasks/...
如下是一个文件
api
syntax = "v1"
info(
title: doc title
)
type Request struct {
Name string `path:"name,options=you|me"`
}
type Response struct {
Message string `json:"message"`
}
@server(
group: admin/v1
logic: Admin,User
)
service Aapi {
@server( // C3
handler: GreetHandler
logic: Admin.GreetHandler
)
get /greet/from/:name(Request) returns (Response) // hello
@handler NoResponseHandler // C5
get /greet/get(Request)
}
@server(
group: user/v1
logic: User
)
service User {
@server(
handler: UserHandler
logic: User.UserHandler
)
get /user/from/:name(Request) returns (Response) // hello
@handler NoUserResponseHandler // C5
get /user/get(Request)
}
@server(
group: user/v2
logic: User
)
service User {
@server( // C3
handler: UserHandler2
logic: User.UserHandler2
)
get /user/from2/:name(Request) returns (Response) // hello
@handler NoUserResponseHandler2 // C5
get /user/get2(Request)
}
生成命令
sh
goctl-gin api go -api ./greet.api -dir ./ -style go-zero
生成结构如下
shell
- api
- etc
- user.yaml
- internal
- config
- config.go
- domain
- domain.go
- handler
- greet
- aapi.go
- handler.go
- user.go
- logic
- admin.go
- user.go
- svc
- servicecontext.go
- pkg
- conf
- httpx
- main.go
- greet.api
1.1 参数说明
info . serviceMod
api
info(
title: doc title
serviceMod: 1
)
- serviceMod 是选择类型,参数值为 1 或者 0;
在handler中,即为控制器中代码结构是如下结构
go
type User struct {
svcCtx *svc.ServiceContext
user logic.User
}
func NewUser(svcCtx *svc.ServiceContext, user logic.User) *User {
return &User{
svcCtx: svcCtx,
user: user,
}
}
func (h *User) InitRegister(engine *gin.Engine) {
g0 := engine.Group("user/v1")
g0.GET("/user/from/:name", h.UserHandler)
g0.GET("/user/get", h.NoUserResponseHandler)
g1 := engine.Group("user/v2")
g1.GET("/user/from2/:name", h.UserHandler2)
g1.GET("/user/get2", h.NoUserResponseHandler2)
}
考虑到在开发中还会有其他方法的新增顾设置0与1两种情况,
- 0:不对handler中的控制器对象处理
- 1:根据.api文件新增方法,自动将方法追加至文件末端,以及自动在struct中增加对新增Logic的引用,并且重写InitRegister;注意还需要自己根据情况完善NewUser中的内容
其中handler/module/handler.go会直接重写加载最新的,无需开发者关注,自动根据api中定义重写。
server中的logic
api
@server(
group: admin/v1
logic: Admin,User
)
在@server中定义logic,用于设置当前的控制器结构体对象引用某一个logic,多个用","分割,可以在重复引用,在handler/module/handler.go中会依据上面的规则生成代码,主要影响与initHandler方法
go
func initHandler(cfg config.Config) []Handler {
svc := svc.NewServiceContext(cfg)
// new logics
var (
adminLogic = logic.NewAdmin(svc)
userLogic = logic.NewUser(svc)
)
// new handlers
var (
user = NewUser(svc, userLogic)
aapi = NewAapi(svc, adminLogic, userLogic)
)
return []Handler{
user,
aapi,
}
}
api
@server( // C3
handler: UserHandler2
logic: User.UserHandler2
)
get /user/from2/:name(Request) returns (Response) // hello
在方法中的logic,主要作用是用于约定当前方法使用具体哪个logic中的方法,其效果如下;注意logic中的方法参数返回与路由定义是一致的。
go
func (h *User) UserHandler(ctx *gin.Context) {
var req domain.Request
if err := ctx.ShouldBind(&req); err != nil {
httpx.FailWithErr(ctx, err)
return
}
res, err := h.user.UserHandler(ctx, &req)
if err != nil {
httpx.FailWithErr(ctx, err)
} else {
httpx.OkWithData(ctx, res)
}
}
关于module
module是依据文件名定义,因此一个文件中存在多个server
2. 关于如何基于go-zero的goctl扩展
在下面主要介绍如何基于go-zero的goctl扩展,整体的难度不是特别大;

命令从goctl.go入口开始根据命令的选择进入相应的包中处理,当前我们是api代码的生成,因此命令因是
go
goctl-gin api go -api ./greet.api -dir ./ -style go-zero

而在每个封装的代码生成目录中是如上结构的引用,主要选择的是核心
- spec:定义的是解析后的字段
- parser:是用于文件解析
- xxgen: 带有gen的都是生成文件代码的
调度结构就是这样,在每个xxgen文件中一般会具有一个gen.go的文件其中GoCommand
方法就是命令的入口,剩下的就可以自己根据代码结构来分析啦
go
func GoCommand(_ *cobra.Command, _ []string) error {
apiFile := VarStringAPI
dir := VarStringDir
namingStyle := VarStringStyle
home := VarStringHome
remote := VarStringRemote
branch := VarStringBranch
if len(remote) > 0 {
repo, _ := util.CloneIntoGitHome(remote, branch)
if len(repo) > 0 {
home = repo
}
}
if len(home) > 0 {
pathx.RegisterGoctlHome(home)
}
if len(apiFile) == 0 {
return errors.New("missing -api")
}
if len(dir) == 0 {
return errors.New("missing -dir")
}
return DoGenProject(apiFile, dir, namingStyle)
}