基于 Protobuf+JSON+自定义模板生成代码
提示
要求 sponge v1.11.0+ 版本
概述
通过自定义模板和 Protobuf 可以用来生成各类自定义场景代码。只需提供 proto 文件 和 模板目录路径 两个主要参数即可,支持指定多个 proto 文件批量生成代码。
适用场景:
- gRPC service 代码。
- gRPC 服务端和客户端的测试用例。
- http 服务 api、router、service 等代码。
前期准备
环境要求:
已安装 sponge。
准备 proto 文件,如果 proto 文件有依赖其他 proto 文件,把依赖的 proto 文件存放在一个单独目录中。
以下是一个
user.proto
的 Protobuf 文件示例,内容如下:syntax = "proto3"; package api.user.v1; import "google/api/annotations.proto"; import "validate/validate.proto"; option go_package = "user/api/user/v1;v1"; service User { // 登录 rpc Login(LoginRequest) returns (LoginReply) { option (google.api.http) = { post: "/api/v1/auth/login" body: "*" }; } } message LoginRequest { string email = 1 [(validate.rules).string.email = true]; string password = 2 [(validate.rules).string.min_len = 6]; } message LoginReply { uint64 id = 1; string token = 2; }
固定字段与自定义字段
代码生成功能支持两类字段:
- 固定字段
- 自定义字段
无论是固定字段还是自定义字段,每个字段都是对应模板代码中的占位符。
固定字段:是通过 protobuf 自动解析出来不能更改的字段。一个 user.proto 对应的固定字段示例如下:
{
"Proto": {
"FileDir": "api/user/v1",
"FileName": "user.proto",
"FileNamePrefix": "user",
"FileNamePrefixCamel": "User",
"GoPackage": "\"user/api/user/v1\"",
"GoPkgName": "userV1",
"ImportPkgMap": {
"userV1": "userV1 \"user/api/user/v1\""
},
"FieldImportPkgMap": {
"userV1": "userV1 \"user/api/user/v1\""
},
"Package": "api.user.v1",
"Services": [
{
"GoPkgName": "userV1",
"Methods": [
{
"Comment": "// Login",
"HTTPRequestBody": "",
"HTTPRequestMethod": "POST",
"HTTPRouter": "/api/v1/auth/login",
"InvokeType": "unary_call",
"IsIgnoreGinBind": false,
"IsPassGinContext": false,
"MethodName": "Login",
"ReplyFields": [
{
"Comment": "",
"FieldType": "uint64",
"GoType": "uint64",
"GoTypeCrossPkg": "uint64",
"ImportPkgName": "",
"ImportPkgPath": "",
"Name": "Id"
}
],
"ReplyImportPkgName": "userV1",
"ReplyName": "LoginReply",
"RequestFields": [
{
"Comment": "",
"FieldType": "string",
"GoType": "string",
"GoTypeCrossPkg": "string",
"ImportPkgName": "",
"ImportPkgPath": "",
"Name": "Email"
}
],
"RequestImportPkgName": "userV1",
"RequestName": "LoginRequest"
}
],
"ServiceName": "User",
"ServiceNameCamel": "User",
"ServiceNameCamelFCL": "user",
"ServiceNamePluralCamel": "Users",
"ServiceNamePluralCamelFCL": "users"
}
]
}
}
自定义字段:自定义字段是可选的,当模板需要时才将自定义字段定义在一个 JSON 文件中。例如,创建一个名为 fields.json
的文件,其内容如下:
{
"ModuleName": "user",
"PackageName": "service",
"ServerName": "grpc",
"Port": 8282
}
注意
在自定义字段中,避免使用 Proto
作为字段名,因为它与固定字段的名称冲突。
创建自定义模板代码
模板代码是代码生成的核心,基于 Go 的 text/template
库实现。因此,建议先熟悉其基本语法规则,语法规则很简单,几分钟即可熟悉,点击查看章节:Go text/template 基本语法规则。
模板文件名应使用变量形式(如 {{.Proto.FileNamePrefix}}.go.tmpl
),以避免处理多个 proto 文件时的命名冲突。以下是一个 gRPC 服务模板的示例:
package service
import (
"context"
"google.golang.org/grpc"
{{- range $pkgName, $pkgPath := .Proto.ImportPkgMap }}
{{$pkgPath}}
{{- end }}
)
// 注册 gRPC 服务
func Register{{.Proto.FileNamePrefixCamel}}Server(server *grpc.Server) {
{{- range .Proto.Services }}
{{.GoPkgName}}.Register{{.ServiceNameCamel}}Server(server, New{{.ServiceNameCamel}}Server())
{{- end }}
}
{{- range .Proto.Services }}
type {{.ServiceNameCamelFCL}} struct {
{{.GoPkgName}}.Unimplemented{{.ServiceNameCamel}}Server
}
// 创建服务实例
func New{{.ServiceNameCamel}}Server() {{.GoPkgName}}.{{.ServiceNameCamel}}Server {
return &{{.ServiceNameCamelFCL}}{}
}
{{- range .Methods }}
// {{.Comment}}
func (s *{{.ServiceNameCamelFCL}}) {{.MethodName}}(ctx context.Context, req *{{.RequestImportPkgName}}.{{.RequestName}}) (*{{.ReplyImportPkgName}}.{{.ReplyName}}, error) {
return &{{.ReplyImportPkgName}}.{{.ReplyName}}{}, nil
}
{{- end }}
{{- end }}
将模板文件存放在一个目录(如 template
)中,可以包含多个模板文件和子目录。
生成代码
在终端执行命令sponge run
进入生成代码 UI 界面,具体步骤如下:
- 进入界面:点击左侧菜单 【生成自定义代码】 → 【Protobuf】。
- 填写参数(鼠标悬停在参数旁边的问号
?
上可以查看参数说明):- 模板目录路径:如
/home/user/template/grpc/service
- proto 文件路径:如
user.proto
- 依赖 proto 文件目录(可选):指定其他需要导入的 Protobuf 文件目录。
- 自定义字段 JSON 文件(可选):如
fields.json
。
- 模板目录路径:如
- 点击 下载代码 按钮下载生成的代码,如下图所示:

等价命令
sponge template protobuf --tpl-dir=/home/user/template/grpc/service --proto-file=user.proto --fields=fields.json
提示
点击按钮查看模板信息
可以查看字段信息,这些字段信息对应模板的占位符,可以更方便的编写模板代码。