我的业务不一样,用 go-zero 怎么搞?

简介: 我的业务不一样,用 go-zero 怎么搞?

在面对复杂且千差万别的业务场景时,通过自定义模板,我们可以生成高效、可维护的代码。在本节中,我们将介绍如何在 goctl 中使用 text/template,并分享一些最佳实践。

近期,我们也将给出 goctl  模板参数规范,并在新版本提供支持,以便大家更好的根据业务需求进行定制。

1. 介绍

text/templateGo 语言标准库中的一个包,用于处理文本模板。通过使用模板,可以将逻辑与表现分离,使代码更加清晰和易于维护。goctl 是一个用于生成 Go 代码的工具,通过结合 text/template,我们可以实现高效的代码生成。

2. text/template 基础

text/template 包提供了丰富的功能来处理文本模板。以下是一些关键概念和用法:

2.1 定义模板

在定义模板时,我们使用双花括号 {{ }} 来表示模板动作。以下是一个简单的模板示例:

const tmpl = `Hello, {{.Name}}!`

2.2 解析和执行模板

我们可以使用 template.Newtemplate.Parse 方法来解析模板,然后使用 Execute 方法来执行模板。

package main
import (
        "os"
        "text/template"
)
func main() {
        const tmpl = `Hello, {{.Name}}!`
        t, err := template.New("example").Parse(tmpl)
        if err != nil {
                panic(err)
        }
        data := map[string]string{
                "Name": "World",
        }
        err = t.Execute(os.Stdout, data)
        if err != nil {
                panic(err)
        }
}

2.3 模板函数

text/template 允许我们自定义模板函数,以扩展模板的功能。我们可以使用 template.FuncMap 来注册自定义函数。

func ToUpper(s string) string {
        return strings.ToUpper(s)
}
t.Funcs(template.FuncMap{
        "ToUpper": ToUpper,
})

更多基础功能请参考往期文章。

3. goctl 模板介绍

goctl 目前支持 api dockergatewaykubemodelmongonewapirpc

指令的模板定制化。对应的模板文件树如下:

.
├── api
│   ├── config.tpl
│   ├── context.tpl
│   ├── etc.tpl
│   ├── handler.tpl
│   ├── logic.tpl
│   ├── main.tpl
│   ├── middleware.tpl
│   ├── route-addition.tpl
│   ├── routes.tpl
│   ├── template.tpl
│   └── types.tpl
├── docker
│   └── docker.tpl
├── gateway
│   ├── etc.tpl
│   └── main.tpl
├── kube
│   ├── deployment.tpl
│   └── job.tpl
├── model
│   ├── customized.tpl
│   ├── delete.tpl
│   ├── err.tpl
│   ├── field.tpl
│   ├── find-one-by-field-extra-method.tpl
│   ├── find-one-by-field.tpl
│   ├── find-one.tpl
│   ├── import-no-cache.tpl
│   ├── import.tpl
│   ├── insert.tpl
│   ├── interface-delete.tpl
│   ├── interface-find-one-by-field.tpl
│   ├── interface-find-one.tpl
│   ├── interface-insert.tpl
│   ├── interface-update.tpl
│   ├── model-gen.tpl
│   ├── model-new.tpl
│   ├── model.tpl
│   ├── table-name.tpl
│   ├── tag.tpl
│   ├── types.tpl
│   ├── update.tpl
│   └── var.tpl
├── mongo
│   ├── err.tpl
│   ├── model.tpl
│   ├── model_custom.tpl
│   └── model_types.tpl
├── newapi
│   └── newtemplate.tpl
└── rpc
    ├── call.tpl
    ├── config.tpl
    ├── etc.tpl
    ├── logic-func.tpl
    ├── logic.tpl
    ├── main.tpl
    ├── server-func.tpl
    ├── server.tpl
    ├── svc.tpl
    └── template.tpl
9 directories, 54 files

3.1 goctl 模板变量

在进行定制化之前,我们需要知道 goctl 内置提供了哪些模板变量,以及每个变量都是什么作用,否则对于定制化可能会有一些受阻。关于模板变量请参考官网介绍(https://go-zero.dev/docs/tutorials/customization/template)。以下是抽样 api logic 文件的模板及变量内容。

logic.tpl

package {{.pkgName}}
import (
    {{.imports}}
)
type {{.logic}} struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}
{{if .hasDoc}}{{.doc}}{{end}}
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} {
    return &{{.logic}}{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}
func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
    // todo: add your logic here and delete this line
    {{.returnString}}
}

模板注入对象为 map[string]any

map[string]any{
    "pkgName":      subDir[strings.LastIndex(subDir, "/")+1:],
    "imports":      imports,
    "logic":        strings.Title(logic),
    "function":     strings.Title(strings.TrimSuffix(logic, "Logic")),
    "responseType": responseString,
    "returnString": returnString,
    "request":      requestString,
    "hasDoc":       len(route.JoinedDoc()) > 0,
    "doc":          getDoc(route.JoinedDoc()),
}
pipeline变量 类型 说明
.pkgName string 包名
.imports string 导入包
.logic string 逻辑结构体名称
.hasDoc bool 是否有文档注释
.doc string 文档注释
.function string logic 函数名称
.request string 请求体表达式,包含参数名称,参数类型
.responseType string 响应类型体表达式,包含参数名称,参数类型
.returnString string 返回语句,返回的结构体

3.2 模板定制化

这里描述的模板定制化均指不修改 goctl 源码的前提下操作模板文件的过程。

首先,模板定制化并非万能的,模板定制化可以:

  1. 引入外部 pkg,比如引入 http 的参数校验包
  2. 增加公用业务逻辑,如生成 code-msg 的数据格式封装
  3. 替换 goctl 内部一些生成逻辑,如定制化请求参数校验
  4. 丰富业务实现,如对 model 增加分页查询

模板定制化不能做什么?

  1. 新增模板文件,新增模板文件 goctl 不会识别
  2. 自定义模板变量,对于非 goctl 注入的模板变量,模板是不会识别的,因此渲染出来的代码肯定是不符合预期的。
  3. 不能从环境变量读取

3.3 模板定制化最佳实践

实践1:http 生成 code-msg 响应格式

统一的 code-msg 格式可以有利于前端数据格式解析,go-zero 并未提供统一的封装,因此需要我们按照自己的业务需求进行改造,此过程可以通过修改模板来实现,加入我们想要响应的数据格式参考如下:

{
    "code": 0,
    "msg: "ok",
    "data":{
        ...
    }
}

思路:响应体的控制是在 handler 中返回的,因此我们只要找到 api 下的 handler.tpl 就可以进行更改了,原模板内容:

package {{.PkgName}}
import (
        "net/http"
        "github.com/zeromicro/go-zero/rest/httpx"
        {{.ImportPackages}}
)
{{if .HasDoc}}{{.Doc}}{{end}}
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
                }
                {{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
                {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
                if err != nil {
                        httpx.ErrorCtx(r.Context(), w, err)
                } else {
                        {{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}}
                }
        }
}

由于没有现成的包提供 code-msg 的解决方案,因此在 zeromicro 提供了一个 x 的扩展包可以来解决此问题。详情可参考 https://github.com/zeromicro/x,我们借助 x 模块下面的 http 包来定制化模板,以下是其部分源码:

// BaseResponse is the base response struct.
type BaseResponse[T any] struct {
        // Code represents the business code, not the http status code.
        Code int `json:"code" xml:"code"`
        // Msg represents the business message, if Code = BusinessCodeOK,
        // and Msg is empty, then the Msg will be set to BusinessMsgOk.
        Msg string `json:"msg" xml:"msg"`
        // Data represents the business data.
        Data T `json:"data,omitempty" xml:"data,omitempty"`
}
type baseXmlResponse[T any] struct {
        XMLName  xml.Name `xml:"xml"`
        Version  string   `xml:"version,attr"`
        Encoding string   `xml:"encoding,attr"`
        BaseResponse[T]
}
// JsonBaseResponse writes v into w with http.StatusOK.
func JsonBaseResponse(w http.ResponseWriter, v any) {
        httpx.OkJson(w, wrapBaseResponse(v))
}
// JsonBaseResponseCtx writes v into w with http.StatusOK.
func JsonBaseResponseCtx(ctx context.Context, w http.ResponseWriter, v any) {
        httpx.OkJsonCtx(ctx, w, wrapBaseResponse(v))
}

通过源码得知,我们可以利用 JsonBaseResponse 或者 JsonBaseResponseCtx 函数,解析来修改模板。

  1. 在模板中引入 x/httpxhttp "github.com/zeromicro/x/http"
  2. 替换响应体代码块内容为 xhttp.JsonBaseResponseCtx即可

修改后的模板内容如下(粗体内容为新增模板内容):

package {{.PkgName}}
import (
        "net/http"
        "github.com/zeromicro/go-zero/rest/httpx"
        xhttp "github.com/zeromicro/x/http"
        {{.ImportPackages}}
)
{{if .HasDoc}}{{.Doc}}{{end}}
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
                }
                {{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
                {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
                if err != nil {
                        // httpx.ErrorCtx(r.Context(), w, err)
                        xhttp.JsonBaseResponseCtx(r.Context(), w, err)
                } else {
                        // {{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}}
                        {{if .HasResp}}xhttp.JsonBaseResponseCtx(r.Context(), w, resp){{else}}xhttp.JsonBaseResponseCtx(r.Context(), w, nil){{end}}
                }
        }
}

实践2:增加 model 分页查询

goctl 内置模板中,考虑到分页不是所有业务都需要的查询,因此没有集成到内置模板中,但是这导致需要使用分页的研发人员没法得到满足,因此可以通过定制化模板来实现。这部分内容可以参考往期文章 goctl 模板分享|model 生成带 ListAll、ListByPage、BatchInsert 模板

3.4 模板定制化后怎么引用

模板定制化修改模板文件的方式有 3 种

  1. 直接修改默认 goctl 模板目录中的模板
  2. 自定义模板目录
  3. git 仓库获取

3.4.1 直接修改默认 goctl 模板目录中的模板

goctl 模板默认模板为 ~/.goctl/${goctl 版本} 下,goctl 版本号获取为 goctl --version | awk '{print $3}',如我当前 goctl 版本为 goctl version 1.6.7 darwin/arm64,因此默认模板就在 ~/.goctl/1.6.7 目录下。

优点:不需要每次在生成代码时通过 --home 来指定模板目录

缺点:会影响默认模板,不能进行模板隔离,生成不同业务的代码时会受影响,升级 goctl 版本后定制化模板无法跟着走。

3.4.2 自定义模板目录

自定义模板目录可以通过在模板初始化时指定一个目录来隔离 goctl template init --home $dir,在使用的时候通过 --home 指定此前模板的目录即可。

优点:业务隔离,不用担心每次升级带来的版本隔离问题,可以统一使用一个模板目录

缺点:每次生成代码都需要通过 --home 来指定模板目录。

3.4.3 从 git 仓库获取

如果模板定制化有团队一致需求,可以将模板统一放进 git 仓库,在代码生成时从 git 仓库统一获取。

如生成 go 代码时的指令:

$ goctl api go --help
Generate go files for provided api in api file
Usage:
  goctl api go [flags]
Flags:
      --api string      The api file
      --branch string   The branch of the remote repo, it does work with --remote
      --dir string      The target dir
  -h, --help            help for go
      --home string     The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
      --remote string   The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
                        The git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure
      --style string    The file naming format, see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md] (default "gozero")

其中的 --remote--branch 就是控制模板从 git 分支获取的参数

参数字段 参数类型 是否必填 默认值 参数说明
... ... ... ... ...
home string NO ${HOME}/.goctl 本地模板文件目录
remote string NO 空字符串 远程模板所在 git 仓库地址,当此字段传值时,优先级高于 home 字段值
... ... ... ... ...

项目地址

https://github.com/zeromicro/go-zero

相关文章
|
存储 算法 NoSQL
还分不清 Cookie、Session、Token、JWT?看这一篇就够了
Cookie、Session、Token 和 JWT(JSON Web Token)都是用于在网络应用中进行身份验证和状态管理的机制。虽然它们有一些相似之处,但在实际应用中有着不同的作用和特点,接下来就让我们一起看看吧,本文转载至http://juejin.im/post/5e055d9ef265da33997a42cc
50948 16
|
存储 Go
Golang底层原理剖析之map
Golang底层原理剖析之map
643 1
|
8月前
|
存储 前端开发 JavaScript
Go语言实战案例-项目实战篇:编写一个轻量级在线聊天室
本文介绍如何用Go语言从零实现一个轻量级在线聊天室,基于WebSocket实现实时通信,支持多人消息广播。涵盖前后端开发、技术选型与功能扩展,助你掌握Go高并发与实时通信核心技术。
873 158
|
消息中间件 人工智能 供应链
go-zero 微服务实战系列(二、服务拆分)
go-zero 微服务实战系列(二、服务拆分)
|
缓存 分布式计算 API
go-zero 微服务实战系列(一、开篇)
go-zero 微服务实战系列(一、开篇)
|
人工智能 搜索推荐 程序员
用 Go 语言轻松构建 MCP 客户端与服务器
本文介绍了如何使用 mcp-go 构建一个完整的 MCP 应用,包括服务端和客户端两部分。 - 服务端支持注册工具(Tool)、资源(Resource)和提示词(Prompt),并可通过 stdio 或 sse 模式对外提供服务; - 客户端通过 stdio 连接服务器,支持初始化、列出服务内容、调用远程工具等操作。
2744 5
|
消息中间件 SQL 关系型数据库
go-zero微服务实战系列(十、分布式事务如何实现)
go-zero微服务实战系列(十、分布式事务如何实现)
|
缓存 Ubuntu Linux
Docker Buildx 简介与安装指南
Docker Buildx 是一个强大的工具,提供了多架构构建、并行构建和高级缓存管理等功能。通过正确安装和配置 Buildx,可以显著提升 Docker 镜像的构建效率和灵活性。希望本文能帮助你更好地理解和使用 Docker Buildx,以提高开发和部署的效率。
6748 16
|
编译器 Go API
go generate指南:代码自动生成
go generate指南:代码自动生成
4516 0
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
2597 1