简介
validate 是一个进行参数基础校验的工具,也是通过protobuf生成代码,kitex也集成了这个工具(看起来比较像是对开源项目做了客制化),但是由于官方文档写的实在是一言难尽,因此在这里我写一个我的实战的demo,防止以后我自己忘了。
kitex 官方文档关于 validator 的描述:cloudwego.cn/zh/docs/kit…
kitex 官方的github仓库(我最终是仔细看了他的example,才调试成功的):github.com/cloudwego/p…
安装:
go install github.com/cloudwego/protoc-gen-validator@latest
踩坑点1: 代码生成在哪里?
好了现在你有一个空项目,你给自己的项目通过go mod 起名为 github.com/yzc/example
go mod init github.com/yzc/example
现在的文件结构是这样:
➜ example tree
.
├── Makefile
├── app
│ └── review_server
│ └── go.mod
└── idl
├── api.proto
└── review.proto
4 directories, 4 files
- Makefile 不是必须的,只不过是可以帮你不用每次都输入复杂的命令(换言之,你如果可以每次都输入复杂的命令来生成代码,可以完全不用Makefile)
然后创建了一个idl文件夹,文件夹下面有一个你自己定义的.proto文件:
syntax = "proto3";
package review;
option go_package = "github.com/yzc/example/kitex_gen/review";
import "api.proto";
message CreateReviewRequest {
int64 UserId = 1 [(api.vt).gt="0"];
int64 OrderId = 2 [(api.vt).gt="0"];
int32 ItemScore = 3 [(api.vt)={in:["1", "2", "3", "4", "5"]}];
int32 ServiceScore = 4 [(api.vt)={in:["1", "2", "3", "4", "5"]}];
int32 ExpressScore = 5 [(api.vt)={in:["1", "2", "3", "4", "5"]}];
string content = 6;
string picInfo = 7;
string videoInfo = 8;
bool anonymous = 9;
}
message CreateReviewResponse {
int64 reviewId = 1;
}
service ReviewService {
rpc CreateReview(CreateReviewRequest) returns(CreateReviewResponse) {}
}
- 首先这个go_package十分重要,我们的命令如果仅仅是一个基础命令(这个命令真就是官方文档中的最简单版本),如下:
.PHONY: gen-validator
gen-validator:
@cd app/review_server && protoc \
-I ../../idl \
--go_out=. \
--validator_out=. \
../../idl/review.proto
那么我们来看一下生成的代码会是在哪个位置的:
可以看到,他就直接在命令执行的目录下,生成了go_package指定的声明的目录层级结构。这显然不是我想要的。
我要干什么呢?
我要告诉kitex,我的 go mod 是 github.com/yzc/example 也就是生成目录的根目录,应该是go.mod的上级目录,在这个案例中就是review_server。但是在根目录下,并不需要再为 github.com/yzc/example 创建目录,因为这仅仅是go mod 的名字。
于是按照官方文档,我在命令中加上2行:
.PHONY: gen-validator
gen-validator:
@cd app/review_server && protoc \
-I ../../idl \
--go_out=. \
--go_opt=module=github.com/yzc/example \
--validator_out=. \
--validator_opt=module=github.com/yzc/example \
../../idl/review.proto
- 我理解这两行的意思是,告诉这个代码生成框架,go module 的名字是:github.com/yzc/example 你只需要到 go.mod 的同级目录下,找到 go_package 去掉以 github.com/yzc/example 为前缀的部分的目录(kitex_gen/review),并在该目录下生成代码即可。
注意这个go_package:option go_package = "github.com/yzc/example/kitex_gen/review";
可以看到代码生成的位置,也是符合预期的:
踩坑点2: api文件有误,导致Validate 方法只有return nil
错误展示:
解决办法,我告诉你哈,直接复制下面这个作为api.proto, 你唯一需要修改的地方是:option go_package 这是官方github仓库example的api.proto,不用担心,不用自己编辑,我们的目的是用上validator
syntax = "proto2";
package api;
import "google/protobuf/descriptor.proto";
option go_package = "github.com/yzc/example/kitex_gen/api";
//本文件是简化版的byteapi protobuf idl注解
message FieldRules {
optional string const = 1;
optional string lt = 2;
optional string le = 3;
optional string gt = 4;
optional string ge = 5;
repeated string in = 6;
repeated string not_in = 7;
optional string len = 8;
optional string min_size = 9;
optional string max_size = 10;
optional string pattern = 11;
optional string prefix = 12;
optional string suffix = 13;
optional string contains = 14;
optional string not_contains = 15;
optional string defined_only = 16;
optional string no_sparse = 17;
optional FieldRules key = 18;
optional FieldRules value = 19;
optional FieldRules elem = 20;
optional string skip = 21;
optional string required = 22;
optional string not_nil = 23;
optional string assert = 24;
}
extend google.protobuf.FieldOptions {
optional string raw_body = 50101;
optional string query = 50102;
optional string header = 50103;
optional string cookie = 50104;
optional string body = 50105;
optional string path = 50106;
optional string vd = 50107;
optional string form = 50108;
optional string go_tag = 51001;
optional FieldRules vt = 50110;
}
extend google.protobuf.MethodOptions {
optional string get = 50201;
optional string post = 50202;
optional string put = 50203;
optional string delete = 50204;
optional string patch = 50205;
optional string options = 50206;
optional string head = 50207;
optional string any = 50208;
optional string re_get = 50211;
optional string re_post = 50212;
optional string re_put = 50213;
optional string re_delete = 50214;
optional string re_patch = 50215;
optional string re_options = 50216;
optional string re_head = 50217;
optional string re_any = 50218;
optional string gen_path = 50301; //客户端代码生成时用户指定的path,优先级高于api_version
optional string api_version = 50302; //客户端代码生成时,指定path中的:version变量值
optional string tag = 50303; // rpc标签,可以是多个,逗号分隔
optional string name = 50304; // rpc的名字
optional string api_level = 50305; //接口等级
optional string serializer = 50306; //序列化方式, json
optional string param = 50307; //客户端请求是否带上公共参数
optional string baseurl = 50308; // ttnet选路时使用的baseurl
optional string handler_path = 50309; // handler_path specifies the path to generate the method
}
extend google.protobuf.EnumValueOptions {
optional int32 http_code = 50401;
}
extend google.protobuf.MessageOptions {
optional FieldRules msg_vt = 50111;
}
正确生成的validate文件:
func (m *CreateReviewRequest) Validate() error {
if m.GetUserId() <= int64(0) {
return fmt.Errorf("field UserId gt rule failed, current value: %v", m.GetUserId())
}
if m.GetOrderId() <= int64(0) {
return fmt.Errorf("field OrderId gt rule failed, current value: %v", m.GetOrderId())
}
_src := []int32{int32(1), int32(2), int32(3), int32(4), int32(5)}
var _exist bool
for _, src := range _src {
if m.GetItemScore() == int32(src) {
_exist = true
break
}
}
if !_exist {
return fmt.Errorf("field ItemScore in rule failed, current value: %v", m.GetItemScore())
}
_src1 := []int32{int32(1), int32(2), int32(3), int32(4), int32(5)}
var _exist1 bool
for _, src := range _src1 {
if m.GetServiceScore() == int32(src) {
_exist1 = true
break
}
}
if !_exist1 {
return fmt.Errorf("field ServiceScore in rule failed, current value: %v", m.GetServiceScore())
}
_src2 := []int32{int32(1), int32(2), int32(3), int32(4), int32(5)}
var _exist2 bool
for _, src := range _src2 {
if m.GetExpressScore() == int32(src) {
_exist2 = true
break
}
}
if !_exist2 {
return fmt.Errorf("field ExpressScore in rule failed, current value: %v", m.GetExpressScore())
}
return nil
}
解决bug的收获
勇于开展简单的实验,可以很快从实践中弄清楚是怎么回事。别懒,别想着直接在自己的项目上成功,有时候搞一个最小demo,把最小demo跑通了,你也懂了。