go有两种模板,一种是HTML模板,可以渲染HTML页面,一种是TEXT文本模板,可以渲染文本甚至go语言代码,本文主要讲一下使用TEXT文本模板。
使用任何语言开发web后端,本质上都少不了增删改查这些最基础的功能,即使是go语言这样结构比较简单的,也需要定义router-api-service(个人习惯)几个模块的代码,实际开发中即使复制粘贴,也有不少的工作量,秉着能偷懒就偷懒的原则,使用模板生成代码是一种比较快捷的选择。事实上不少框架也集成了这个功能,比如go-zero就使用goctl这个工具,根据编写的api文件,自动生成router-handle-logic三个部分的代码,当然goctl的功能强大的多,api文件可以像proto那样定义类型和方法,本文只实现了一些很简单的代码生成功能,使用gin框架。
编写模板
{{/* 生成 API 文件 */}}
package {{.Service}}
import (
"examServer/apiServer/serializer"
common "examServer/apiServer/service"
"examServer/apiServer/service/{{.Service}}/{{.PkgName}}"
"github.com/gin-gonic/gin"
)
// {{.StructName}} {{.ModuleName}}API
type {{.StructName}} struct{}
// {{.PkgName}}Service {{.ModuleName}}Service
var {{.PkgName}}Service {{.PkgName}}.{{.StructName}}
// List 获取{{.ModuleName}}列表
func (api *{{.StructName}}) List(c *gin.Context) {
var service common.ListReq
if err := c.ShouldBind(&service); err == nil {
res := {{.PkgName}}Service.List(c,&service)
c.JSON(200, res)
} else {
c.JSON(200, serializer.ErrorResponse(err))
}
}
// Detail 获取{{.ModuleName}}详情
func (api *{{.StructName}}) Detail(c *gin.Context) {
var service common.DetailReq
if err := c.ShouldBind(&service); err == nil {
res := {{.PkgName}}Service.Detail(c,&service)
c.JSON(200, res)
} else {
c.JSON(200, serializer.ErrorResponse(err))
}
}
// Create 新增{{.ModuleName}}
func (api *{{.StructName}}) Create(c *gin.Context) {
var service common.CreateReq
if err := c.ShouldBind(&service); err == nil {
res := {{.PkgName}}Service.Create(c,&service)
c.JSON(200, res)
} else {
c.JSON(200, serializer.ErrorResponse(err))
}
}
// Update 更新{{.ModuleName}}
func (api *{{.StructName}}) Update(c *gin.Context) {
var service common.UpdateReq
if err := c.ShouldBind(&service); err == nil {
res := {{.PkgName}}Service.Update(c,&service)
c.JSON(200, res)
} else {
c.JSON(200, serializer.ErrorResponse(err))
}
}
// Delete 删除{{.ModuleName}}
func (api *{{.StructName}}) Delete(c *gin.Context) {
var service common.DeleteReq
if err := c.ShouldBind(&service); err == nil {
res := {{.PkgName}}Service.Delete(c,&service)
c.JSON(200, res)
} else {
c.JSON(200, serializer.ErrorResponse(err))
}
}
// Status 修改{{.ModuleName}}状态
func (api *{{.StructName}}) Status(c *gin.Context) {
var service common.StatusReq
if err := c.ShouldBind(&service); err == nil {
res := {{.PkgName}}Service.Status(c,&service)
c.JSON(200, res)
} else {
c.JSON(200, serializer.ErrorResponse(err))
}
}
首先新建一个api.tmpl的文件,然后定义了一些handler,用来处理gin的路由。做了一下简单的参数判断,然后交给service处理。 从以上代码可见,声明了某个模块下最基本的增删改查这几种路由。
从这段代码中我们可以看出,在第二步中声明的结构体成员,可以在模板中以{{.StructName}}表示,比如StructName的值是Student,那么代码最后生成的就是 func(api *Student),甚至可以先在go文件中写好代码,然后把变量替换成{{.T}} 这样不容易出错,除此之外这些模板的编写和写go代码近乎一致。
编写代码
type Template struct {
Service string // 服务名
ModuleName string // 模块名
PkgName string // 包名
StructName string // 结构体名
}
func Generate(data Template) error {
// 遍历template目录下的所有文件
dir, err := os.ReadDir("template")
if err != nil {
return err
}
for _, file := range dir {
if !strings.Contains(file.Name(), ".tmpl") {
continue
}
tmpl, err := template.New(file.Name()).ParseFiles("template/" + file.Name())
if err != nil {
return err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
return err
}
var fileName string
// 生成文件
if strings.Contains(file.Name(), "api") {
fileName = fmt.Sprintf("api/%s/%s.go", data.Service, data.PkgName)
} else if strings.Contains(file.Name(), "service") {
dirPath := fmt.Sprintf("service/%s/%s", data.Service, data.PkgName)
err = os.MkdirAll(dirPath, 0777)
if err != nil {
return err
}
ext := file.Name()[7:strings.LastIndex(file.Name(), ".")]
fileName = fmt.Sprintf("%s/%s%s.go", dirPath, data.PkgName, ext)
}
// 存在则跳过
if _, err := os.Stat(fileName); err == nil {
continue
}
err = os.WriteFile(fileName, buf.Bytes(), 0777)
if err != nil {
return err
}
}
return nil
}
首先定义了Template结构体,需要一些基本信息例如:服务名、模块名、包名等,然后遍历template文件夹下的所有模板文件(.tmpl文件) 根据不同的类型api/service,生成不同位置的代码
生成代码
err := template.Generate(template.Template{
Service: "backend",
ModuleName: "班级",
PkgName: "class",
StructName: "Class",
})
if err != nil {
log.Fatalln(err)
}
从入口函数运行这段代码后,就在指定位置生成了你预编写好的代码文件,虽然每个模块下的业务代码不尽相同,但可以将一些基础的结构搭好,真正编写代码时就可以直接上手,而不是复制粘贴半天还粘贴错了,快来一起偷懒吧。