写点什么

Go-Zero 自定义 goctl 实战:定制化模板,加速你的微服务开发效率(四)

作者:王中阳Go
  • 2024-05-09
    北京
  • 本文字数:4961 字

    阅读完需:约 16 分钟

Go-Zero自定义goctl实战:定制化模板,加速你的微服务开发效率(四)

前言

上一篇文章带你实现了Go-Zero和goctl:解锁微服务开发的神器,快速上手指南,本文将继续深入探讨 Go-Zero 的强大之处,并介绍如何使用 goctl 工具实现模板定制化,并根据实际项目业务需求进行模板定制化实现。


通过本文的教程,你能够亲自实践并完成 goctl 模板的定制化,进一步提升你的 Go-Zero 开发技能。

概述

goctl 代码生成是基于 go 的模板去实现数据驱动的,默认情况会选择内存中的模板进行生成,当开发需要修改模板时,就需要定制化模板,goctl 为我们实现了这一功能。

实战前准备

首先需要你在本地安装 goctl、protoc、go-zero,下载教学和地址点击这里,按照教程操作即可,非常简单。


下面按顺序和我操作吧,对整体开发流程不清楚的同学务必先看我前篇文章:GoZero的开发技巧 & 整体开发流程


本文重在实战,如果对 goctl 毫不了解的话,建议先看我前一篇文章:Go-Zero和goctl:解锁微服务开发的神器,快速上手指南


以下均以我的商业项目举例,应该对你有启发:


(后面我会把商业项目脱敏开源出来,欢迎关注我)

数据表生成 Model 方法脚本

首先在 deploy 下新增 script 目录,结构如下图所示。



脚本内容如下:


#!/usr/bin/env bash
# 使用方法:# ./genModel.sh lottery lottery# ./genModel.sh lottery prize# 再将./genModel下的文件剪切到对应服务的model目录里面,记得改package
#生成的表名tables=$2#表生成的genmodel目录modeldir=./genModel
# 数据库配置host=127.0.0.1port=33069dbname=$1username=rootpasswd=PXDN93VRKUm8TeE7template=../../goctl/1.6.1
echo "开始创建库:$dbname 的表:$2"goctl model mysql datasource -url="${username}:${passwd}@tcp(${host}:${port})/${dbname}" -table="${tables}" -dir="${modeldir}" -cache=true --home="${template}" --style=goZero
复制代码

模板定制化使用方法

相关命令使用详情,参考:官网文档具体使用方法网上有很多文章介绍,官网也有详细步骤。这里更加注重商业项目对于模板定制化的实战,对相关操作不进行赘述,快速过一遍流程即可。

初始化模板到本地

依据前文所介绍的项目目录结构,我们将自定义模板放在 deploy 下面即可,并且采用的版本号为 1.6.1(目录路径根据自己实际情况修改)


goctl template init --home $HOME/Desktop/lottery-backend/deploy/goctl/1.6.1
复制代码



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



这样就生成好自己版本的 goctl 模板啦,可以根据自己的实际需求进行模板的修改。


接下来分享我们项目中关于自定义 goctl 的实战。

自定义 goctl 实战

实战 1:Model 层方法定制化

很多时候我们需要对数据进行分页查询。这个方法是一个通用的方法,可以在很多地方复用,所以放入模板去生成,这样可以减少重复代码,提高开发效率。

步骤一:在 model/update.tpl 下面新增一个方法 FindPageListByPage

方法具体实现如下


func (m *default{{.upperStartCamelObject}}Model) FindPageListByPage(ctx context.Context,builder squirrel.SelectBuilder,page ,pageSize int64,orderBy string) ([]*{{.upperStartCamelObject}},error) {
builder = builder.Columns({{.lowerStartCamelObject}}Rows)
if orderBy == ""{ builder = builder.OrderBy("id DESC") }else{ builder = builder.OrderBy(orderBy) }
if page < 1{ page = 1 } offset := (page - 1) * pageSize
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() if err != nil { return nil, err }
var resp []*{{.upperStartCamelObject}} {{if .withCache}}err = m.QueryRowsNoCacheCtx(ctx,&resp, query, values...){{else}} err = m.conn.QueryRowsCtx(ctx,&resp, query, values...) {{end}} switch err { case nil: return resp, nil default: return nil, err }}
复制代码

步骤二:使用之前做好的脚本生成代码

使用 GitBash 打开 deploy/script/mysql 目录,执行脚本



此时 genModel 目录下面就会生成相关代码


步骤三:将生成的代码剪切到项目目录的对应位置

效果

默认模板生成的 Model 层方法

自定义模板生成的 Model 层方法


生成的 FindPageListByPage 方法


func (m *defaultLotteryModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Lottery, error) {
builder = builder.Columns(lotteryRows)
if orderBy == "" { builder = builder.OrderBy("id DESC") } else { builder = builder.OrderBy(orderBy) }
if page < 1 { page = 1 } offset := (page - 1) * pageSize
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() if err != nil { return nil, err }
var resp []*Lottery err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) switch err { case nil: return resp, nil default: return nil, err }}
复制代码

实战 2:api 自定义响应返回以及集成 validator 库校验参数

当我们希望自定义统一返回响应体以及希望每个 api 接口都进行参数校验时,我们可以在模板中修改 handler 层的代码,从而实现这些效果。

步骤一:实现自定义统一返回响应

在 common 目录下新建 result 目录和 httpResult.go 文件,如下图所示



具体实现代码不是本文重点,下面是提供的代码


package result
import ( "fmt" "net/http"
"looklook/common/xerr"
"github.com/pkg/errors" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest/httpx" "google.golang.org/grpc/status")
// http返回func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) {
if err == nil { //成功返回 r := Success(resp) httpx.WriteJson(w, http.StatusOK, r) } else { //错误返回 errcode := xerr.SERVER_COMMON_ERROR errmsg := "服务器开小差啦,稍后再来试一试"
causeErr := errors.Cause(err) // err类型 if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型 //自定义CodeError errcode = e.GetErrCode() errmsg = e.GetErrMsg() } else { if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误 grpcCode := uint32(gstatus.Code()) if xerr.IsCodeErr(grpcCode) { //区分自定义错误跟系统底层、db等错误,底层、db错误不能返回给前端 errcode = grpcCode errmsg = gstatus.Message() } } }
logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err)
httpx.WriteJson(w, http.StatusBadRequest, Error(errcode, errmsg)) }}
// http 参数错误返回func ParamErrorResult(r *http.Request, w http.ResponseWriter, err error) { errMsg := fmt.Sprintf("%s ,%s", xerr.MapErrMsg(xerr.REUQEST_PARAM_ERROR), err.Error()) httpx.WriteJson(w, http.StatusBadRequest, Error(xerr.REUQEST_PARAM_ERROR, errMsg))}
复制代码

步骤二:在 handler 下面引入定制的 validator 包


关于定制 validator 也不是本文重点,感兴趣的同学可以关注我,留言。


package translator
import ( "errors" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" zh_translations "github.com/go-playground/validator/v10/translations/zh" "looklook/app/lottery/cmd/api/internal/logic/lottery" "looklook/app/lottery/cmd/api/internal/types" "reflect" "strings")
func Validate(dataStruct interface{}) error { zh_ch := zh.New() validate := validator.New() // 注册一个函数,获取struct tag里自定义的label作为字段名 validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name })
// 在这里注册自定义结构体/字段校验方法 // 注册自定义结构体校验方法 validate.RegisterStructValidation(lottery.SignUpParamStructLevelValidation, types.TestReq{})
// 注册自定义结构体字段校验方法 if err := validate.RegisterValidation("checkDate", lottery.CheckDate); err != nil { return err }
uni := ut.New(zh_ch) trans, _ := uni.GetTranslator("zh")
// 在这里注册自定义tag翻译 // 注意!因为这里会使用到trans实例 // 所以这一步注册要放到trans初始化的后面
if err := validate.RegisterTranslation( "checkDate", trans, registerTranslator("checkDate", "{0}必须要晚于当前日期"), translate, ); err != nil { return err }
// 验证器注册翻译器 zh_translations.RegisterDefaultTranslations(validate, trans) err := validate.Struct(dataStruct) if err != nil { for _, err := range err.(validator.ValidationErrors) { return errors.New(err.Translate(trans)) } } return nil}
// registerTranslator 为自定义字段添加翻译功能func registerTranslator(tag string, msg string) validator.RegisterTranslationsFunc { return func(trans ut.Translator) error { if err := trans.Add(tag, msg, false); err != nil { return err } return nil }}
// translate 自定义字段的翻译方法func translate(trans ut.Translator, fe validator.FieldError) string { msg, err := trans.T(fe.Tag(), fe.Field()) if err != nil { panic(fe.(error).Error()) } return msg}
复制代码

步骤三:修改 handler.tpl 模板代码


将模板替换为以下内容


package {{.PkgName}}
import ( "net/http"
"looklook/common/result"
"github.com/zeromicro/go-zero/rest/httpx" {{.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.ErrorCtx(r.Context(), w, err) return }
validateErr := translator.Validate(&req) if validateErr != nil { result.ParamErrorResult(r, w, validateErr) return }
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx) {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
result.HttpResult(r, w, {{if .HasResp}}resp{{else}}nil{{end}}, err) }}
复制代码

步骤四:生成对应的代码

注意生成 handler 后需要手动点开生成的 handler 文件,导入 translator 包,否则服务会报错!!!


# 使用自定义的goctl 生成apigoctl api go -api main.api -dir ../  --style=goZero --home=../../../../../deploy/goctl/1.6.1
复制代码

修改后的响应体

{  "code": 200,  "msg": "OK",  "data": {    "message": ""  }}
复制代码

模板自定义规则

  1. 在 goctl 提供的有效数据范围内修改,即不支持外部变量

  2. 不支持新增模板文件

  3. 不支持变量修改

总结

本文介绍了如何使用 Go-Zero 的 goctl 工具进行自定义模板的实战,并提供了一个具体的案例来演示定制化模板的过程。


如果你需要详细的命令使用详情,可以参考官方文档中的相关内容。模板定制化 | go-zero Documentation


我将继续更新 Go-Zero 系列文章,如果你对 Go 语言或者微服务感兴趣,欢迎关注我,也欢迎直接私信我。

gozero&微服务交流群

我将继续更新 Go-Zero 系列文章,如果你对 Go 语言或者微服务感兴趣,欢迎关注我,也欢迎直接私信我。


微信:wangzhongyang1993

发布于: 刚刚阅读数: 3
用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论

发布
暂无评论
Go-Zero自定义goctl实战:定制化模板,加速你的微服务开发效率(四)_Go_王中阳Go_InfoQ写作社区