常见问题解答
提示
如有与 sponge 相关的任何问题或建议,欢迎通过以下方式反馈:
- 在 sponge 问答社区
https://go-sponge.com/qa/question
提问 - 在 GitHub Issues 提交问题
- 直接在下方评论区留言
如何设置请求参数校验规则?
基于 SQL 创建 Web 服务代码
解答:
通过手动添加 tag 来指定哪些参数是必填的,例如在
internal/types/xxx_types.go
文件,具体检验规则看 https://github.com/go-playground/validator。
type CreateUserRequest struct {
Name string `json:"name" binding:"min=2"` // 用户名
Email string `json:"email" binding:"email"` // 邮箱
Password string `json:"password" binding:"md5"` // 密码
Gender int `json:"password" binding:""` // 性别,非必须
}
其中 binding 是指定该字段的校验规则,如果指定规则是
required
表示必填字段,如果 binding tag 为空或删除 binding 表示则非必填字段。
其他方式创建的服务代码
解答:
在 proto 文件添加标注来设置校验规则,例如
api/user/v1/user.proto
文件,具体检验规则看 https://github.com/bufbuild/protoc-gen-validate。
import "validate/validate.proto";
message CreateUserRequest {
string name = 1 [(validate.rules).string.min_len = 2]; // 用户名
string email = 2 [(validate.rules).string.email = true]; // 邮箱
string password = 3 [(validate.rules).string.min_len = 10]; // 密码
int32 gender = 4; // 性别,非必须
}
如何使用 JWT 鉴权中间件?
在 Gin 使用 JWT 鉴权
解答:
点击查看在 gRPC 中使用 jwt 鉴权的 示例。
在 gRPC 使用 JWT 鉴权
解答:
点击查看在 gRPC 中使用 jwt 鉴权的 示例。
执行命令 make proto 报错
错误信息类型1
api/types/types.proto: File not found.
api/xxx/v1/xxx.proto:5:1: Import "api/types/types.proto" was not found or had errors.
api/xxx/v1/xxx.proto:246:3: "types.Params" is not defined.
make: *** [Makefile:81: proto] Error 1
解答:
切换到项目代码目录下,在终端执行命令
make patch TYPE=types-pb
错误信息类型2
# xxx/internal/cache
internal/cache/xxx.go:40:38: undefined: model.CacheType
make: *** [Makefile:103: run] Error 1
解答:
切换到项目代码目录下,在执行命令
make patch TYPE=init-mysql
,如果使用其他数据库类型,把命令中的 mysql 改为对应数据库类型名称。
错误信息类型3
api/xxx/v1/xxx.proto:68:24: Option "(tagger.tags)" unknown. Ensure that your proto definition file imports the proto which defines the option.
make: *** [Makefile:81: proto] Error 1
解答:
在
api/xxx/v1/xxx.proto
文件导入依赖import "tagger/tagger.proto";
错误信息类型4
api/xxx/v1/xxx.proto:232:24: Option "(validate.rules)" unknown ......
解答:
在
api/xxx/v1/xxx.proto
文件导入依赖import "validate/validate.proto";
错误信息类型5
a.proto: File not found.
api/demo/v1/a.proto:8:1: Import "a.proto" was not found or had errors.
原因:protoc 参数 proto_path 相对路径的问题。
解答:
在 b.proto 中把import "a.proto"
改为具体路径 import "api/demo/v1/a.proto"
错误信息类型6
protoc-gen-go: unable to determine Go import path for "api/demo/v1/a.proto"
Please specify either:
• a "go_package" option in the .proto source file, or
• a "M" argument on the command line.
原因:proto 文件缺少 go_package。
解答:
在 a.proto 文件添加 option go_package = "demo/api/demo/v1;v1";
。
在 Swagger 界面请求 API 时出现跨域错误
错误信息:
Failed to fetch.
Possible Reasons:
CORS
Network Failure
URL scheme must be "http" or "https" for CORS request.
原因:
修改了配置文件的默认端口,或者时在跨主机的浏览器的 swagger 界面请求,而服务端默认的 swagger 的 base URL 仍然是 localhost:8080,造成了这个跨域错误。
解答:
- 对于
基于 sql 创建 web 服务
,在 main.go 文件修改 localhost:8080,然后执行命令make docs
- 对于
基于 protobuf 创建 web 服务
,在每个 proto 文件修改 localhost:8080,然后执行命令make proto
- 临时测试使用,修改
docs/swagger.json
文件中的host
字段为实际的服务地址,然后重新启动服务,刷新 swagger 页面即可。注意:make docs
或make proto
命令会替换掉host
为默认值。
在 zsh 执行命令生成代码报错
例如错误信息:
zsh: no matches found: --db-dsn=root:123456@(127.0.0.1:3306)/school
解答:
如果使用
zsh
shell 解析器执行 sponge 命令,参数--db-dsn 值需要加双引号,示例:--db-dsn="root:123456@(127.0.0.1:3306)/school"
。
在前端请求 API,服务端接收到的请求参数字段值为空
在基于 protobuf 创建 web 服务
,基于 protobuf 创建 gRPC 网关服务
中,下面是常见的三种情况:
请求查询参数无法获取
例如请求地址 http://localhost:8080/api/v1/getUser?name=Tom ,服务端获取请求参数name
为空。
解答:
在对应的 proto 文件中的 message 定义的字段添加 tag,例如
string name = 1 [(tagger.tags) = "form:/"name/"];
,然后执行make proto
命令。
路径参数无法获取
例如请求地址 http://localhost:8080/api/v1/user/1 ,其中1是路径参数 id 的值,服务端获取不到 id 值。
解答:
在 proto 文件定义路径参数,例如 /api/v1/user/{id},而且定义的 message 必须包含路径参数的名称 id,该 id 名称必须添加 tag 说明,例如
int64 id = 1 [(tagger.tags) = "uri:/"id/""];
,然后执行make proto
命令。
请求参数的 json 名称不一致
请求参数的字段使用了蛇形命名(snake case),而 xxx.pb.go 文件下的字段的 json tag 是驼峰命名(Camel Case)。
请求示例:
curl -X POST http://localhost:8080/api/v1/user/register -H 'Content-Type: application/json'-d \
'{
"my_email": "string",
}'
xxx.pb.go 文件下的字段的 json tag 值是myEmail
,json 名称与请求参数名称不一致。
解答:
把请求参数
my_email
改为myEmail
可以正常访问。如果确实需要使用蛇形命名my_email
,则需要在 proto 文件的 message 对应字段添加 json tag,例如string my_email = 1 [json_name ="my_email"]
,确保前端和后端 JSON 名称一致,然后执行make proto
命令。
如何连接多个 mysql 数据库,实现读写分离
解答:
打开配置文件configs/xxx.yml
(1) 设置一主多从方式示例:
mysql:
dsn: "root:123456@(127.0.0.1:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true
maxIdleConns: 3
maxOpenConns: 100
connMaxLifetime: 30
slavesDsn:
- "root:123456@(192.168.3.38:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
- "root:123456@(192.168.3.39:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
(2) 设置多主多从方式示例:
mysql:
dsn: "root:123456@(127.0.0.1:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true
maxIdleConns: 3
maxOpenConns: 100
connMaxLifetime: 30
slavesDsn:
- "root:123456@(192.168.3.38:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
- "root:123456@(192.168.3.39:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
mastersDsn:
- "root:123456@(127.0.0.1:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
- "root:123456@(192.168.3.40:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
id 字段名称不一致问题
原因:
sponge 生成的 id 字段名称是 ID 或 xxxID,而 protoc 插件生成代码字段是 Id 或 xxxId,本来是同一个字段但出现了不一致情况。
解答:
注意不能使用 copier 复制,只能手工单独赋值该字段。
如何使用服务注册与发现
注意事项:
- 在 goland IDE 测试 api 时,停止在 golang IDE 配置的代理。
- 在终端启动服务时停止使用网络代理。
服务配置:
app:
# 不要写127.0.0.1,写服务器真实 ip 或域名,如果服务注册与发现平台(etcd、consul、nacos)不是在本地部署,定时监控检查 host 会失败。
host: "192.168.3.79"
registryDiscoveryType: "consul" # 可以填写 etcd、consul、nacos,必须填写配置,例如 consul
# 如果在本服务连接其他 grpc 服务,并且使用服务发现的话,配置如下:
grpcClient:
- name: "user" # 必填,用于服务发现
host: "127.0.0.1" # 可以不填
port: 36790 # 可以不填
registryDiscoveryType: "consul" # 可以填写 etcd、consul、nacos,必须填写配置,例如 consul
如果使用 dtm 使用服务注册与发现,也要写真实 ip 或域名
MicroService:
EndPoint: 'grpc://192.168.3.79:36790'
使用 nacos 作为服务注册与发现时,注册服务端和服务发现端的配置 namespaceID 值必须一致
例如 gRPC 服务 A 要把自己服务真实地址注册到 nacos,在服务 A 的配置文件下的 nacos 的配置
nacosRd:
ipAddr: "127.0.0.1"
port: 8848
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
另一个 gRPC 服务 B 需要通过名称获取到 A 服务的真实地址,在服务 B 的配置文件下的 nacos 的配置
nacosRd:
ipAddr: "127.0.0.1"
port: 8848
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
服务 A 和服务 B 下的 nacos 配置 namespaceID 值必须相同,可以同时为空。
在线上运行的服务如何获取 profile 到本地进行性能分析
sponge 生成的服务支持通过 http api 和 系统信号 两种方式来采集 profile。
通过 HTTP API 采集 profile
需要开启 http api 功能,在 configs 目录下 yaml 文件设置
enableHTTPProfile: true
,默认路由是/debug/pprof
。
通过系统信号采集 profile
# 通过名称查看服务 pid(第二列)
ps aux | grep 服务名称
# 发送信号给服务
kill -trap <pid 值>
服务收到系统信号通知后,开始采集 profile 并保存到/tmp/服务名称_profile 目录
,默认采集时长为60秒,60秒后自动停止采集 profile,如果只想采集30秒,发送第一次信号开始采集,大概30秒后再发送第二次信号停止采集 profile,类似开关。默认采集 cpu、memory、goroutine、block、mutex、threadcreate 六种类型 profile,文件格式日期时间_pid_服务名称_profile 类型.out
,
自适应采集 profile
需要开启资源统计功能,在 configs 目录下 yaml 文件设置
enableStat: true
。
把系统信号采集 profile与资源统计的告警功能结合起来实现,告警条件:
- 记录程序的 cpu 使用率连续3次(默认每分钟一次),3次平均使用率超过80%时触发告警。
- 记录程序的物理内存使用率3次(默认每分钟一次),3次平均占用系统内存超过80%时触发告警。
- 如果持续超过告警阈值,默认间隔15分钟发出一次告警。
采集的 profile 保存到/tmp/服务名称_profile
目录。
基于 Protobuf 创建的 Web 服务,在 handler 如何获取 gin.Context ?
在 proto 文件设置 selector: "[ctx]"标记,说明这个路由透传 gin.Context 到 handler,内部仍然会执行 ShouldBinding 参数,proto 文件示例:
// 登录 rpc Login(LoginRequest) returns (LoginReply) { option (google.api.http) = { post: "/api/v1/auth/login" body: "*" selector: "[ctx]" }; }
执行命令 make proto 生成代码,然后在 handler 获取 gin.Context,示例:
// Login 登录 func (h *userHandler) Login(ctx context.Context, req *userV1.LoginRequest) (*userV1.LoginReply, error) { c, ctx := middleware.AdaptCtx(ctx) // 变量 c 是 gin.Context // ...... }
基于 Protobuf 创建的 Web 服务,如何上传文件?
在 proto 文件设置 selector: "[no_bind]" 标记,说明这个路由设置了透传 gin.Context,并且跳过了 ShouldBingding 参数,proto 文件示例:
// 上传文件 rpc Upload(UploadRequest) returns (UploadReply) { option (google.api.http) = { post: "/api/v1/upload" body: "*" selector: "[no_bind]" }; }
执行命令 make proto 生成代码,然后在 handler 获取 gin.Context,示例:
// Upload 上传文件 func (h *userHandler) Upload(ctx context.Context, req *userV1.UploadRequest) (*userV1.UploadReply, error) { c, ctx := middleware.AdaptCtx(ctx) // 变量 c 是 gin.Context // 处理上传文件逻辑 }
基于 Protobuf 创建的 Web 服务,如何返回非 json 结构数据,例如字符串、图片、http 重定向?
在 proto 文件设置
selector: "[no_bind]"
标记,说明这个路由设置了透传 gin.Context,并且跳过了 ShouldBingding 参数,proto 文件示例rpc Foobar(FoobarRequest) returns (FoobarReply) { option (google.api.http) = { post: "/api/v1/foobar" body: "*" selector: "[no_bind]" }; }
执行命令 make proto 生成代码,在 handler 获取 gin.Context,然后处理上传文件逻辑,示例:
func (h *userHandler) Foobar(ctx context.Context, req *userV1.FoobarRequest) (*userV1.FoobarReply, error) { c, ctx := middleware.AdaptCtx(ctx) // 变量 c 是 gin.Context // 1. 绑定参数(如果需要用到 req 数据,在这里绑定参数) // 2. 处理逻辑 // 3. 如果返回非 json 结构数据或重定向跳转,必须跳过返回结构化数据 // c.String(http.StatusOK, "hello world") // 返回字符串 return nil, ecode.SkipResponse // 跳过返回结构化数据 }