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

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

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

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

1. 介绍

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

2. text/template 基础

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

2.1 定义模板

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

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

2.2 解析和执行模板

我们可以使用 template.New 和 template.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 、docker 、gateway 、kube 、model 、mongo 、newapi 、rpc

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

.
├── 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/http 包 xhttp "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

相关文章
|
Java API Nacos
巧用网关白名单实现接口免鉴权
分享技术,用心生活 场景描述:一般系统中提供的接口都是经过统一配置鉴权的,比如不登录不能访问。但是,一些接口是需要开放给客户用的,我称作open API。那么这时候你不能要求客户先登录你的接口再去调用吧。那么,这时候就可以通过网关白名单来实现免鉴权
巧用网关白名单实现接口免鉴权
|
7月前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
51955 73
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
消息中间件 人工智能 供应链
go-zero 微服务实战系列(二、服务拆分)
go-zero 微服务实战系列(二、服务拆分)
|
前端开发 JavaScript Go
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:介绍
风行(GoWind Admin)是一款开箱即用的企业级Golang全栈中后台框架,基于go-kratos与Vben Admin,支持单体/微服务架构。功能完备,涵盖用户、租户、角色、权限、日志等管理模块,助力开发者高效构建管理系统。
1650 0
|
自然语言处理 搜索推荐 Go
goctl 技术系列 - Go 模板入门
goctl 技术系列 - Go 模板入门
|
开发框架 前端开发 Linux
Go语言实战框架,GoFly全栈开发社区的Go快速开发框架简介与阿里服务器部署说明
GoFly中后台框架永久开源可商用。api文档管理并一键生成api接口代码,一键生成 CRUD前后端代码, GoFly快速开发框架是一款基于Go语言的 Gin和 Vue3的Arco Design的快速后台开发框架,基于JWT接口验证和Auth验证的权限管理系统,附件管理系统,天生支持saas架构。可打包部署在阿里云Linux系统上。
1809 1
|
缓存 分布式计算 API
go-zero 微服务实战系列(一、开篇)
go-zero 微服务实战系列(一、开篇)
|
应用服务中间件 API 数据库
Docker 安装 KONG 带你玩转 API 网关
**摘要:** 在微服务架构中,API网关Kong作为流行开源选择,提供身份验证、安全和流量控制等功能。通过Docker部署Kong简单高效。步骤包括:创建Docker网络,部署PostgreSQL数据库,初始化Kong数据库,启动Kong容器,并检查运行状态。此外,安装Konga管理界面便于直观管理Kong。使用Docker命令行,逐步设置环境变量和网络连接,即可完成安装。当不再需要时,可清理相关容器和网络。Kong结合Konga,为API管理提供强大且用户友好的解决方案。
2179 1
|
缓存 监控 Kubernetes
go-zero解读与最佳实践(上)
go-zero解读与最佳实践(上)

热门文章

最新文章