使用Go模板来偷懒

使用Go的模板功能,实现简单的代码生成功能

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)
    }

从入口函数运行这段代码后,就在指定位置生成了你预编写好的代码文件,虽然每个模块下的业务代码不尽相同,但可以将一些基础的结构搭好,真正编写代码时就可以直接上手,而不是复制粘贴半天还粘贴错了,快来一起偷懒吧。

Licensed under CC BY-NC-SA 4.0
加载中...
感谢Jimmy 隐私政策