目 录CONTENT

文章目录
Go

GRPC支持protocol结构化数据校验

Hello!你好!我是村望~!
2023-05-31 / 0 评论 / 0 点赞 / 130 阅读 / 1,298 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

GRPC支持protocol结构化数据校验

使用protoc-gen-validate

PGV是一个protoc插件,用于生成多语言消息验证器。虽然 proto buffer 有效地保证了结构化数据的类型,但它们不能对值执行语义规则。这个插件为protoc生成的代码添加了支持,以验证这种约束。

他会在 protocol 生成我们的grpc的代码的时候,会读取一些protoc-gen-validate插件支持的数据结构规则,额外去生成校验的代码!来完成校验的功能!

导入PGV扩展,用约束规则注释他们proto文件中的消息和字段:

安装去 github clone 一下 然后进去make build

~/xx  cd protoc-gen-validate
~/env/protoc-gen-validate   main ±  make build
~/env/protoc-gen-validate   main ±  ...... # 安装输出
~/env/protoc-gen-validate   main ±  cd ~/go/bin
 ~/go/bin  ls
air                 go-outliner         gopls               protoc-gen-go-grpc
dlv                 gomodifytags        gotests             protoc-gen-validate
dlv-dap             gopkgs              impl                staticcheck
go-outline          goplay              protoc-gen-go

还要去copy一下 validate.proto 文件到自己的项目本地,因为校验的一些 protocol 结构在这个文件中! 需要在自己的proto文件引用!

syntax = "proto3"; // 版本

package hello; //包声明
import "_validator/validate.proto"; // 引用 validate.proto
option go_package = "./user";

message AimUser {
  string name = 1 [(validate.rules).string.min_len = 6]; // 这里指定了 name 的最小长度是6
}

service User {
  rpc SayHi(AimUser) returns(AimUser); //定义服务的某一个方法,接受的参数,和返回值
}

这样就可以来生成代码了!

protoc -I=.  --go_out=./ --go-grpc_out=./ --validate_out="lang=go:."  *.proto
  • protoc: Protocol Buffers 编译器的可执行文件名。
  • -I=: 指定包含.proto文件的目录。这里的 . 表示当前目录。
  • --go_out=./: 生成 Go 代码文件的输出目录。这里的 ./ 表示当前目录。
  • --go-grpc_out=./: 生成 gRPC 相关的 Go 代码文件的输出目录。这里的 ./ 表示当前目录。
  • --validate_out="lang=go:.": 生成用于验证消息的 Go 代码文件的输出目录。这里的 lang=go:. 指定生成 Go 代码,并将其输出到当前目录。
  • *.proto: 要编译的.proto文件的通配符。这里使用 *.proto 来匹配当前目录下的所有.proto文件。
➜  microway git:(protoc-gen-validate) ✗ tree         
├── _validator 
│   └── validate.proto # github 的 validate文件!
├── client
│   └── client.go
├── go.mod
├── go.sum
├── grpc_User.proto
├── server
│   └── server.go
└── user
    ├── grpc_User.pb.go  # 消息体生成的结构体代码
    ├── grpc_User.pb.validate.go # protocol 生成的 grpc服务客户端服务的逻辑代码
    └── grpc_User_grpc.pb.go # 一些额外的验证代码,针对我们加入规则验证的结构体,添加了一些验证方法

当对参数进行校验的时候提供了两个validator 方法!

image-20230531160255758

  • ValidateAll() 一次性校验所有的参数,全部返回
  • Validate() 逐条校验返回!
func (m *AimUser) validate(all bool) error {
	if m == nil {
		return nil
	}

	var errors []error

	if utf8.RuneCountInString(m.GetName()) < 6 {
		err := AimUserValidationError{
			field:  "Name",
			reason: "value length must be at least 6 runes",
		}
    // 如果不是全部 就单次返回这一个error 否则就是 errors 切片!
		if !all {
			return err
		}
		errors = append(errors, err)
	}

	if len(errors) > 0 {
		return AimUserMultiError(errors)
	}

	return nil
}

测试校验不通过!

aimUser := &user.AimUser{Name: "村望老弟!"}
err = aimUser.ValidateAll()
if err != nil {
	log.Fatalf(err.Error())
}

image-20230531160949612

测试校验通过

image-20230531161129674

服务端断言校验GRPC的请求数据!

上面我们通过调用客户端的validate方法去进行protocol数据的校验!

如果有的客户端调用之前没有执行呢!(家人们谁懂啊!)

image-20230531162120240

那么我们希望我们的服务端也可以对请求的数据进行一次校验!

思路很简单:我们根据鸭子类型的特点!自定义实现了 ValidateAllValidate的方法!然后断言看看能不能断言为这个类型!

如果成功说明是具有这 ValidateAll 和 Validate 方法的请求参数!那么我们就去执行这些方法来校验参数!

type Validator interface {
	Validate() error
	ValidateAll() error
}
// UnaryServerInterceptor 实现拦截器的 UnaryServerInterceptor 方法里面就是具体的拦截器内部要做的拦截逻辑!
func (i *CustomInterceptor) UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	// 在处理请求之前执行的逻辑
	log.Println("CustomInterceptor: Before handling the request")
	if outgoingContext, b := metadata.FromIncomingContext(ctx); b {
		tokenSlice := outgoingContext.Get("token")
		if len(tokenSlice) < 1 || tokenSlice[0] != "xxx" {
			return nil, status.Error(codes.Unauthenticated, "无认证信息")
		}
	} else {
		return nil, status.Error(codes.Unauthenticated, "无认证信息")
	}
  
  //🙆 断言为MyValidator然后执行校验!
  if r, ok := req.(MyValidator); ok {
		err := r.ValidateAll()
		if err != nil {
			return nil, status.Error(codes.InvalidArgument, "参数错误"+err.Error())
		}
	}
  
	// 调用下一个拦截器或服务处理程序
	resp, err := handler(ctx, req)

	// 在处理请求后执行的逻辑
	log.Println("CustomInterceptor: After handling the request")

	return resp, err
}

image-20230531164126505

通过的测试

image-20230531164235212

0

评论区