Сцена приложения
Интерфейс прототипа, предоставленный извне, требует проверки параметров. Прошлая практика заключается в добавлении правил проверки в логику кода, и появится следующий пример:
if req.Caller == nil || equip == nil || req.Action == coll_common.Action_OP_UNKNOWN || len(equip.Name) == 0 || equip.CategoryId == 0 ||
len(equip.LogoList) == 0 || len(equip.Model) == 0 || len(equip.TechnicalIndex) == 0 || len(equip.Description) == 0 ||
equip.RentCount == 0 || equip.MinimumRental == 0 || equip.Rent == 0 || equip.ContactPerson == nil || equip.Location == nil {
logrus.Errorf("[AddEquipment] required parameter can not be null")
rsp.CommonRsp.Code = common.CodeInvalidParam
rsp.CommonRsp.Msg = "required parameter can not be null"
return nil
}
Код, написанный таким образом, менее читабелен, не может подробно обрабатывать каждую ошибку и возвращает унифицированный результат ошибки, его трудно устранить. Если обрабатывается каждое сообщение об ошибке, это занимает много времени, и есть много повторяющихся задач. Это проще. чтобы получить ошибки. Из этого я подумал, можно ли ограничить тип данных при определении proto, и привязать метод проверки к запрашиваемой информации через компиляцию, что может улучшить читаемость кода и уменьшить много повторяющейся работы. .
Сравнение инструментов go-proto-validators и protoc-gen-validate
Имя | участники | звезды | язык | в настоящее время | документация завершена :------------ | :------------: | ------------: go-proto-validators | 14 | 605 | go | стабильный | низкий protoc-gen-validate | 56 | 982 | go, java, python, c++ | альфа | высокий
protoc-gen-validate: Он поддерживает несколько языков, а активность сообщества немного выше, чем у go-proto-validators.Документация относительно полная, но она все еще находится в нестабильной версии, и последующие API, вероятно, изменятся.go-proto-validate: Поддерживает различные варианты использования и принципы, по сути мало чем отличается от protoc-gen-validate, но находится в относительно стабильной версии, поэтому это решение временно принято.
использовать
1. Добавьте каталог исполняемого файла под gopath в переменную среды.
export PATH=${PATH}:${GOPATH}/bin
2. Установка
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
3. Написать протобуф
syntax = "proto3";
package test_bid;
import "github.com/mwitkow/go-proto-validators/validator.proto";
import "git.code.oa.com/cloud_industry/proto/common/common.proto";
import "coll_common/common.proto";
import "google/protobuf/descriptor.proto";
option go_package = "git.code.oa.com/cloud_industry/colla_proto/test_bid";
// 投标管理
service BidManagement {
//添加投标
rpc DoBid (DoBidReq) returns (DoBidRsp) {
}
}
enum Action {
OP_UNKNOWN = 0; //未知
OP_DRAFT = 1; //保存草稿
OP_COMMIT = 2; //提交审核
OP_REVOKE = 3; //撤销审核
OP_PASS = 4; //通过
OP_DENY = 5; //驳回
OP_RELEASE = 6; //上架
OP_OFF_RELEASE = 7; //下架
OP_RECOMMEND = 8; //推荐
OP_CANCEL_RECOMMEND = 9; //取消推荐
OP_MOVE_UP = 10; //上移
OP_MOVE_DOWN = 11; //下移
OP_CLOSE = 12; //关闭
OP_SUCC = 13; //交易成功
OP_FAIL = 14; //交易失败
}
message DoBidReq {
common.Caller Caller = 1 [(validator.field) = {msg_exists: true}];
int64 DemandId = 2 [(validator.field) = {int_lt: 0}]; // 需求id
int64 ServicePrice = 3; // 交付报价,单位分
int32 ServicePeriod = 4; // 交付周期,单位天
common.AppendixInfo Attachment = 5 [(validator.field) = {msg_exists: true}]; // 附件
string Connect = 6 [(validator.field) = {string_not_empty: true}]; // 联系人
bytes ConnectPhone = 7 [(validator.field) = {length_gt: 0, length_lt: 255}]; // 联系手机
string SMSVerifyCode = 8; // 短信验证码
Action Action = 9 [(validator.field) = {is_in_enum: true}]; // 操作
}
message DoBidRsp {
common.CommonRsp CommonRsp = 1;
}
Соответствующую поддержку синтаксиса можно посмотреть в файле validator.proto.
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
// Protocol Buffers extensions for defining auto-generateable validators for messages.
// TODO(mwitkow): Add example.
syntax = "proto2";
package validator;
import "google/protobuf/descriptor.proto";
option go_package = "github.com/mwitkow/go-proto-validators;validator";
// TODO(mwitkow): Email protobuf-global-extension-registry@google.com to get an extension ID.
extend google.protobuf.FieldOptions {
optional FieldValidator field = 65020;
}
extend google.protobuf.OneofOptions {
optional OneofValidator oneof = 65021;
}
message FieldValidator {
// Uses a Golang RE2-syntax regex to match the field contents.
optional string regex = 1;
// Field value of integer strictly greater than this value.
optional int64 int_gt = 2;
// Field value of integer strictly smaller than this value.
optional int64 int_lt = 3;
// Used for nested message types, requires that the message type exists.
optional bool msg_exists = 4;
// Human error specifies a user-customizable error that is visible to the user.
optional string human_error = 5;
// Field value of double strictly greater than this value.
// Note that this value can only take on a valid floating point
// value. Use together with float_epsilon if you need something more specific.
optional double float_gt = 6;
// Field value of double strictly smaller than this value.
// Note that this value can only take on a valid floating point
// value. Use together with float_epsilon if you need something more specific.
optional double float_lt = 7;
// Field value of double describing the epsilon within which
// any comparison should be considered to be true. For example,
// when using float_gt = 0.35, using a float_epsilon of 0.05
// would mean that any value above 0.30 is acceptable. It can be
// thought of as a {float_value_condition} +- {float_epsilon}.
// If unset, no correction for floating point inaccuracies in
// comparisons will be attempted.
optional double float_epsilon = 8;
// Floating-point value compared to which the field content should be greater or equal.
optional double float_gte = 9;
// Floating-point value compared to which the field content should be smaller or equal.
optional double float_lte = 10;
// Used for string fields, requires the string to be not empty (i.e different from "").
optional bool string_not_empty = 11;
// Repeated field with at least this number of elements.
optional int64 repeated_count_min = 12;
// Repeated field with at most this number of elements.
optional int64 repeated_count_max = 13;
// Field value of length greater than this value.
optional int64 length_gt = 14;
// Field value of length smaller than this value.
optional int64 length_lt = 15;
// Field value of length strictly equal to this value.
optional int64 length_eq = 16;
// Requires that the value is in the enum.
optional bool is_in_enum = 17;
// Ensures that a string value is in UUID format.
// uuid_ver specifies the valid UUID versions. Valid values are: 0-5.
// If uuid_ver is 0 all UUID versions are accepted.
optional int32 uuid_ver = 18;
}
message OneofValidator {
// Require that one of the oneof fields is set.
optional bool required = 1;
}
4. Скомпилируйте
Добавьте еще один параметр govalidators_out в синтаксис компиляции protoc.
protoc \
--proto_path=${GOPATH}/src \
--proto_path=${GOPATH}/src/github.com/google/protobuf/src \
--proto_path=. \
--go_out=. \
--govalidators_out=. \
*.proto
После выполнения будет сгенерирован файл test_bid.validator.pb.go
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: test_bid/test_bid.proto
package test_bid
import (
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
_ "github.com/mwitkow/go-proto-validators"
_ "git.code.oa.com/cloud_industry/proto/common"
_ "git.code.oa.com/cloud_industry/colla_proto/coll_common"
github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
func (this *DoBidReq) Validate() error {
if nil == this.Caller {
return github_com_mwitkow_go_proto_validators.FieldError("Caller", fmt.Errorf("message must exist"))
}
if this.Caller != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Caller); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Caller", err)
}
}
if !(this.DemandId < 0) {
return github_com_mwitkow_go_proto_validators.FieldError("DemandId", fmt.Errorf(`value '%v' must be less than '0'`, this.DemandId))
}
if nil == this.Attachment {
return github_com_mwitkow_go_proto_validators.FieldError("Attachment", fmt.Errorf("message must exist"))
}
if this.Attachment != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Attachment); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Attachment", err)
}
}
if this.Connect == "" {
return github_com_mwitkow_go_proto_validators.FieldError("Connect", fmt.Errorf(`value '%v' must not be an empty string`, this.Connect))
}
if !(len(this.ConnectPhone) > 0) {
return github_com_mwitkow_go_proto_validators.FieldError("ConnectPhone", fmt.Errorf(`value '%v' must have a length greater than '0'`, this.ConnectPhone))
}
if !(len(this.ConnectPhone) < 255) {
return github_com_mwitkow_go_proto_validators.FieldError("ConnectPhone", fmt.Errorf(`value '%v' must have a length smaller than '255'`, this.ConnectPhone))
}
if _, ok := Action_name[int32(this.Action)]; !ok {
return github_com_mwitkow_go_proto_validators.FieldError("Action", fmt.Errorf(`value '%v' must be a valid Action field`, this.Action))
}
return nil
}
func (this *DoBidRsp) Validate() error {
if this.CommonRsp != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.CommonRsp); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("CommonRsp", err)
}
}
return nil
}
Этот файл привязывает метод проверки к запросу DoBidReq, и каждая проверка параметра имеет собственное сообщение об ошибке логики проверки.
использовать
func (c *CollaborativeAgent) DoBid(ctx context.Context, req *test_bid.DoBidReq, rsp *test_bid.DoBidRsp) error {
if err := req.Validate(); err != nil {
logrus.Errorf("error is %v\n", err)
rsp.CommonRsp = &coll_common.CommonRsp {
Code: 400,
Msg: err.Error(),
}
} else {
rsp.CommonRsp = &coll_common.CommonRsp {
Code: 200,
Msg: "success",
}
}
return nil
}
существующие проблемы
1. Когда is_in_enum использует тип перечисления для проверки, если ссылаются на тип перечисления других пакетов, возникнут проблемы при генерации кода проверки.
coll_common.Action Action = 9 [(validator.field) = {is_in_enum: true}]; // 操作
Скомпилированный код
if _, ok := Action_name[int32(this.Action)]; !ok {
return github_com_mwitkow_go_proto_validators.FieldError("Action", fmt.Errorf(`value '%v' must be a valid Action field`, this.Action))
}
Карта Action_name генерируется в файле pb.go в общем пакете
package coll_common
type Action int32
const (
Action_OP_UNKNOWN Action = 0
Action_OP_DRAFT Action = 1
Action_OP_COMMIT Action = 2
Action_OP_REVOKE Action = 3
Action_OP_PASS Action = 4
Action_OP_DENY Action = 5
Action_OP_RELEASE Action = 6
Action_OP_OFF_RELEASE Action = 7
Action_OP_RECOMMEND Action = 8
Action_OP_CANCEL_RECOMMEND Action = 9
Action_OP_MOVE_UP Action = 10
Action_OP_MOVE_DOWN Action = 11
Action_OP_CLOSE Action = 12
Action_OP_SUCC Action = 13
Action_OP_FAIL Action = 14
)
var Action_name = map[int32]string{
0: "OP_UNKNOWN",
1: "OP_DRAFT",
2: "OP_COMMIT",
3: "OP_REVOKE",
4: "OP_PASS",
5: "OP_DENY",
6: "OP_RELEASE",
7: "OP_OFF_RELEASE",
8: "OP_RECOMMEND",
9: "OP_CANCEL_RECOMMEND",
10: "OP_MOVE_UP",
11: "OP_MOVE_DOWN",
12: "OP_CLOSE",
13: "OP_SUCC",
14: "OP_FAIL",
}
var Action_value = map[string]int32{
"OP_UNKNOWN": 0,
"OP_DRAFT": 1,
"OP_COMMIT": 2,
"OP_REVOKE": 3,
"OP_PASS": 4,
"OP_DENY": 5,
"OP_RELEASE": 6,
"OP_OFF_RELEASE": 7,
"OP_RECOMMEND": 8,
"OP_CANCEL_RECOMMEND": 9,
"OP_MOVE_UP": 10,
"OP_MOVE_DOWN": 11,
"OP_CLOSE": 12,
"OP_SUCC": 13,
"OP_FAIL": 14,
}
Существует проблема, на которую здесь нельзя ссылаться, поэтому рекомендуется, чтобы определение перечисления было записано в том же прото-файле.
2. Как правильно оценить длину строки
string ConnectPhone = 7 [(validator.field) = {length_gt: 0, length_lt: 255}]
Сгенерированный проверочный код
if !(len(this.ConnectPhone) > 0) {
return github_com_mwitkow_go_proto_validators.FieldError("ConnectPhone", fmt.Errorf(`value '%v' must have a length greater than '0'`, this.ConnectPhone))
}
if !(len(this.ConnectPhone) < 255) {
return github_com_mwitkow_go_proto_validators.FieldError("ConnectPhone", fmt.Errorf(`value '%v' must have a length smaller than '255'`, this.ConnectPhone))
}
Для китайских и спецсимволов этот метод не может правильно судить о реальной длине строки.Кодировка голанга по умолчанию utf8, а в utf8 китайский занимает 3 байта, поэтому длина китайского символа должна быть 3, поэтому при проектировании прото Пора обратить на это внимание. В бизнес-коде вы можете передать
len([]rune(str))
Определите реальную длину строки, потому что rune основан на int32, а базовый байт типа строки основан на uint8.