go-zero模板定制化

概述

模板(Template)是数据驱动生成的基础,所有的代码(rest api、rpc、model、docker、kube)生成都会依赖模板, 默认情况下,模板生成器会选择内存中的模板进行生成,而对于有模板修改需求的开发者来讲,则需要将模板进行落盘, 从而进行模板修改,在下次代码生成时会加载指定路径下的模板进行生成

使用方法

命令使用详情,参考官网文档

初始化模板到本地

goctl template init --home $HOME/template

注意:如果不指定–home 他会初始化到$HOME/.goctl

模板定制化

场景1 api响应定制

实现统一格式的 body 响应,格式如下:

{
  "code": 0,
  "msg": "OK",
  "data": {} // ①
}

实际响应数据,go-zero生成的代码没有对其进行处理

准备工作

我们提前在 module 为 greet 的工程下的 response 包中写一个 Response 方法,目录树类似如下

greet
├── response
│   └── response.go
└── xxx...

代码如下

package response

import (
    "net/http"

    "github.com/zeromicro/go-zero/rest/httpx"
)

type Body struct {
    Code int         `json:"code"`
    Msg  string      `json:"msg"`
    Data interface{} `json:"data,omitempty"`
}

func Response(w http.ResponseWriter, resp interface{}, err error) {
    var body Body
    if err != nil {
        body.Code = -1
        body.Msg = err.Error()
    } else {
        body.Msg = "OK"
        body.Data = resp
    }
    httpx.OkJson(w, body)
}

修改 handler 模板

vim $HOME/template/${goctl版本号}/api/handler.tpl

将模板替换为以下内容

package handler

import (
    "net/http"
    "greet/response"// ①
    {{.ImportPackages}}
)

func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        {{if .HasRequest}}var req types.{{.RequestType}}
        if err := httpx.Parse(r, &req); err != nil {
            httpx.Error(w, err)
            return
        }{{end}}

        l := logic.New{{.LogicType}}(r.Context(), svcCtx)
        {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
        {{if .HasResp}}response.Response(w, resp, err){{else}}response.Response(w, nil, err){{end}}//②

    }
}

① 替换为你真实的response包名,仅供参考
② 自定义模板内容

使用tpl模板生成代码

goctl api go --api *.api --dir . --home $HOME/template/${goctl版本号}

–api是被指定生成的api文件;–dir是生成代码的目录;–home就是生成代码指定的模板

修改模板前后对比

修改前

func GreetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req types.Request
        if err := httpx.Parse(r, &req); err != nil {
            httpx.Error(w, err)
            return
        }

        l := logic.NewGreetLogic(r.Context(), svcCtx)
        resp, err := l.Greet(&req)
        // 以下内容将被自定义模板替换
        if err != nil {
            httpx.Error(w, err)
        } else {
            httpx.OkJson(w, resp)
        }
    }
}

修改后

func GreetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req types.Request
        if err := httpx.Parse(r, &req); err != nil {
            httpx.Error(w, err)
            return
        }

        l := logic.NewGreetLogic(r.Context(), svcCtx)
        resp, err := l.Greet(&req)
        response.Response(w, resp, err)
    }
}

修改模板前后响应体对比

修改前

{
  "message": "Hello go-zero!"
}

修改后

{
  "code": 0,
  "msg": "OK",
  "data": {
    "message": "Hello go-zero!"
  }
}

场景2 数据库操作方法定制

为数据库添加多一个根据ID进行软删除操作,DeleteSoft,内容大概如下

func (m *defaultUserModel) DeleteSoft(ctx context.Context, id int) error {
  //delState = globalkey.DelStateYes
  //deleteTime = time.Now()
  //进行软删除更新-伪代码
  updateCount,err := 1,nil
  if err != nil {
      return err
  }
  if updateCount == 0 {
      return ErrNoRowsUpdate
  }
  return nil
}

准备工作

我们提前在 module 为 greet 的工程下的 globalkey 包中定义好需要使用的方法或者常量,目录树类似如下

greet
├── globalkey
│   └── globalkey.go
└── xxx...

代码如下

package globalkey

// 软删除
var DelStateNo int64 = 0  //未删除
var DelStateYes int64 = 1 //已删除

这里我只用到了常量

修改 handler 模板

//自定义错误
vim $HOME/template/${goctl版本号}/model/err.tpl 
//不使用缓存改import-no-cache.tpl,使用缓存改import.tpl
//引入使用到的第三方包
vim $HOME/template/${goctl版本号}/model/import-no-cache.tpl
//定义接口,写在哪个文件(查询、插入、删除、更新)就对应interface-*.tpl
vim $HOME/template/${goctl版本号}/model/interface-update.tpl
//实现接口
vim $HOME/template/${goctl版本号}/model/update.tpl

将模板替换为以下内容
自定义的错误: H O M E / t e m p l a t e / HOME/template/ HOME/template/{goctl版本号}/model/err.tpl

package {{.pkg}}

import (
    "errors"
    "github.com/zeromicro/go-zero/core/stores/sqlx"
)

var ErrNotFound = sqlx.ErrNotFound
var ErrNoRowsUpdate = errors.New("update db no rows change")//定义的错误

引入的包: H O M E / t e m p l a t e / HOME/template/ HOME/template/{goctl版本号}/model/import-no-cache.tpl(使用缓存是import.tpl)

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	{{if .time}}"time"{{end}}

    {{if .containsPQ}}"github.com/lib/pq"{{end}}
    "github.com/Masterminds/squirrel" //引入的包
    "github.com/pkg/errors" //引入的包
	"github.com/zeromicro/go-zero/core/stores/builder"
	"github.com/zeromicro/go-zero/core/stores/sqlc"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"github.com/zeromicro/go-zero/core/stringx"
)

定义想添加的接口: H O M E / t e m p l a t e / HOME/template/ HOME/template/{goctl版本号}/model/interface-update.tpl

Update(ctx context.Context, {{if .containsIndexCache}}newData{{else}}data{{end}} *{{.upperStartCamelObject}}) error
DeleteSoft(ctx context.Context, id int) error //添加的接口

实现想添加的接口: H O M E / t e m p l a t e / HOME/template/ HOME/template/{goctl版本号}/model/update.tpl

func (m *default{{.upperStartCamelObject}}Model) Update(ctx context.Context, {{if .containsIndexCache}}newData{{else}}data{{end}} *{{.upperStartCamelObject}}) error {
	{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, newData.{{.upperStartCamelPrimaryKey}})
	if err!=nil{
		return err
	}

{{end}}	{{.keys}}
    _, {{if .containsIndexCache}}err{{else}}err:{{end}}= m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
		return conn.ExecCtx(ctx, query, {{.expressionValues}})
	}, {{.keyValues}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
    _,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}}
	return err
}

//接口实现
func (m *default{{.upperStartCamelObject}}Model) DeleteSoft(ctx context.Context, id int) error {
  //自定义的常量
  //delState = globalkey.DelStateYes 
  //deleteTime = time.Now()
  //进行软删除更新-伪代码
  updateCount,err := 1,nil
  if err != nil {
      return err
  }
  if updateCount == 0 {
      //自定义的错误
      return ErrNoRowsUpdate
  }
  return nil
}

使用tpl模板生成代码

这里使用链接生成,也可以使用SQL文件生成

goctl model mysql datasource -url=* -table=*  -dir=. --home $HOME/template/${goctl版本号}

–url是数据库连接;–table是要生成的表名;–dir是生成代码的目录;–home就是生成代码指定的模板

修改模板前后对比

  • 太长了就不输出了
  • vars.go:在这里文件里面看到自定义的错误
  • 表名model_gen.go:在这个文件看到自定义:引入的包、定义的接口、实现的接口

模板自定义规则

  • 在 goctl 提供的有效数据范围内修改,即不支持外部变量
  • 不支持新增模板文件
  • 不支持变量修改

总结

  • 如果只想维护一份模板,那么我们初始化模板的时候和生成代码的时候都不用指定–home了,默认的模板tpl文件在.goctl里面;初始化模板的时候会提示你在哪 Templates are generated in C:\Users\wangzhirong.goctl\1.5.0, edit on your risk!
  • 关于引用包名;一般来说除非是开源的包,我们可以直接指定写,但是有些包是项目内的包,模板直接指定的话换了项目就不行了;而且容易出错,所以我们可以直接写错,直接爆红;让开发知道这里是需要自己import的,也可以让编译器自动识别帮你import
  • 已经存在的代码不会再生成;例如我已经生产了一套代码;现在想用另外的模板生成,只能是把文件删除了然后再生成
  • 在模板中添加文件是不生效的,只能是修改他已有的tpl文件
  • –remote远端模板;一般情况下不使用远端模板;使用自己本地的就好了;开发者可能是想我们做一套好用的模板,但是目前看来开源的模板都没几个;这个开源 链接只是增加了事务的模板
  • 对于模板的其他操作;例如回滚、清除。回滚其实就是把你修改的东西不要了,要回初始的;清除就是把你的template文件删除了

原理和使用详解

参考视频https://www.bilibili.com/video/BV1NY411w7w7/?spm_id_from=333.788&vd_source=10332ffe931de86faa42900544751c8c

参考文章https://go-zero.dev/docs/tutorials/customization/template

你可能感兴趣的:(后端)