PGV校验必须在.proto文件中定义,Kratos不识别Go结构体tag;仅校验body反序列化的protobuf message,query/form需手动处理;错误响应需自定义ErrorHandler;嵌套校验需dive,无自定义规则支持。

PGV校验必须在proto中定义,不能靠结构体tag补救
Kratos默认不读取Go结构体上的validate:"required"这类标签,它只认Protobuf定义里嵌入的PGV(Protocol Buffer Validation)规则。你写再多json:"name" validate:"required",只要proto里没声明,middleware.Validate()就完全无视。
常见错误是先用protoc生成了Go代码,再手动给生成的struct字段加validate tag——这毫无意义,因为Kratos的PGV中间件只校验从HTTP body反序列化后、经protoc-gen-go生成的结构体实例,且只触发proto里声明的(validate.rules)逻辑。
- 必须在
.proto文件中启用validate.proto并显式添加规则,例如:import "validate/validate.proto"; message CreateUserRequest { string name = 1 [(validate.rules).string.min_len = 1]; uint32 age = 2 [(validate.rules).uint32.gte = 0, (validate.rules).uint32.lte = 150]; } - 确保
buf.gen.yaml中已启用PGV插件:plugin: validate或对应protoc-gen-validate路径 -
buf generate执行后,生成的*_pb.go里会自动带Validate()方法,中间件正是调用它
HTTP请求中query/form参数PGV不生效,得手动处理
PGV中间件(middleware.Validate())只校验body解析后的protobuf message,对URL query、form-data、header里的字段完全不碰。比如?page=0&size=10这种分页参数,即使你在proto里写了int32 page = 1 [(validate.rules).int32.gt = 0],也不会触发校验。
解决方式只有两个:要么把参数挪进body(如PUT/POST JSON),要么在handler里手动提取+校验:
- 用
r.URL.Query().Get("page")或r.PostFormValue("size")取值 - 转成临时struct或直接用
validator.New().Var(...)校验(注意:此时用的是go-playground/validator/v10,不是PGV) - 或者统一收口到一个
BindQuery辅助函数里,避免每个handler重复写
PGV错误返回太原始,必须替换ErrorHandler
默认PGV失败时返回400 Bad Request + 原始validator.FieldError字符串,像field:Name, tag:required, param:,前端根本没法解析。
必须在注册中间件时传入middleware.WithValidateErrorHandler:
httpSrv := http.NewServer(
http.Middleware(
middleware.Validate(
middleware.WithValidateErrorHandler(func(ctx context.Context, err error) error {
if errs, ok := err.(validator.ValidationErrors); ok {
var details []map[string]string
for _, e := range errs {
details = append(details, map[string]string{
"field": e.Field(),
"rule": e.Tag(),
"value": fmt.Sprint(e.Value()),
})
}
return errors.BadRequest("VALIDATION_FAILED", "参数校验失败", details)
}
return errors.BadRequest("VALIDATION_ERROR", "校验异常")
}),
),
),
)
注意:这个ErrorHandler只对PGV生效;如果你同时用了Go结构体tag校验,得另配validator实例。
PGV嵌套与自定义规则需额外配置,不是开箱即用
PGV默认不递归校验嵌套message。比如User里有个Address *Address字段,即使Address本身有PGV规则,User.Validate()也不会自动调用Address.Validate()——除非你在proto里显式加dive:
message User {
Address address = 1 [(validate.rules).message.required = true, (validate.rules).message.dive = true];
}
另外,PGV不支持自定义函数(如“密码必须含数字和大小写字母”),也不支持struct-level校验(如“start_time必须早于end_time”)。这类逻辑必须在service层手写判断,或用go-playground/validator/v10的RegisterValidation扩展,但那就脱离PGV体系了。
真正容易被忽略的是:PGV规则只在protoc-gen-validate生成阶段注入,运行时无法动态增删。改一条规则,就得重新buf generate,再编译部署。别指望它能像Web框架的runtime validator那样热更新。











