当前位置: 首页 > news >正文

grpc的验证器

简介

在使用grpc库时候 ,很多时候我们需要对反序列化的参数进行校验,代码中有很多参数校验的代码,如果手动实现,会非常繁琐,对于grpc来说,在定义proto的时候使用直接定义参数的限制规则是一种更合理、更优雅的方式,插件protoc-gen-validate就是来帮助我们实现这一功能的,使用 proto-gen-validate 生成后的代码进行参数校验,我们可以通过在 proto 中编写参数校验规则,然后生成代码,通过中间件自动的进行校验。

安装

go install github.com/envoyproxy/protoc-gen-validate@latest

规则示例

下面为大家列举几种常用类型的参数校验示例,更多的示例可以在 proto-gen-validate 文档中查看。

数字类型

// 参数必须大于 0
int64 id = 1 [(validate.rules).int64 = {gt: 0}];
// 参数必须在 0 到 120 之间
int32 age = 2 [(validate.rules).int64 = {gt:0, lte: 120}];
// 参数是 1 或 2 或 3
uint32 code = 3 [(validate.rules).uint32 = {in: [1,2,3]}];
// 参数不能是 0 或 99.99
float score = 1 [(validate.rules).float = {not_in: [0, 99.99]}];

布尔类型

// 参数必须为 true
bool state = 5 [(validate.rules).bool.const = true];
// 参数必须为 false
bool state = 5 [(validate.rules).bool.const = false];

文本类型

// 参数必须为 /hello
string path = 6 [(validate.rules).string.const = "/hello"];
// 参数文本长度必须为 11
string phone = 7 [(validate.rules).string.len = 11];
// 参数文本长度不能小于 10 个字符
string explain = 8 [(validate.rules).string.min_len =  10];
// 参数文本长度不能小于 1 个字符并且不能大于 10 个字符
string name = 9 [(validate.rules).string = {min_len: 1, max_len: 10}];
// 参数文本使用正则匹配,匹配必须是非空的不区分大小写的十六进制字符串
string card = 10 [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];
// 参数文本必须是 email 格式
string email = 11 [(validate.rules).string.email = true];

消息体

// 参数为必填项
Info info = 11 [(validate.rules).message.required = true];
message Info {string address = 1;
}

定义proto文件

syntax = "proto3";package examplepb;
option go_package = "./example";
import "validate/validate.proto";message Person {uint64 id    = 1 [(validate.rules).uint64.gt = 999];string email = 2 [(validate.rules).string.email = true];string name  = 3 [(validate.rules).string = {pattern:   "^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$",max_bytes: 256,}];Location home = 4 [(validate.rules).message.required = true];
// 参数必须大于 0
int64 ids = 5 [(validate.rules).int64 = {gt: 0}];
// 参数必须在 0 到 120 之间
int32 age = 6 [(validate.rules).int32 = {gt:0, lte: 120}];
// 参数是 1 或 2 或 3
uint32 code = 7 [(validate.rules).uint32 = {in: [1,2,3]}];
// 参数不能是 0 或 99.99
float score = 8 [(validate.rules).float = {not_in: [0, 99.99]}];message Location {double lat = 1 [(validate.rules).double = { gte: -90,  lte: 90 }];double lng = 2 [(validate.rules).double = { gte: -180, lte: 180 }];}
}

使用命令生成go文件

% protoc \                                            -I . \--plugin=$GOPATH/bin/protoc-gen-validate \-I ${GOPATH}/pkg/mod/github.com/envoyproxy/protoc-gen-validate@v0.1.0/ \--go_out=":./generated" \--validate_out="lang=go:./generated" \example.proto

相应的,我们得到了两个文件

learn/pgv/generated/example/example.pb.go

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//   protoc-gen-go v1.28.1
//   protoc        v3.19.4
// source: example.protopackage exampleimport (_ "github.com/envoyproxy/protoc-gen-validate/validate"protoreflect "google.golang.org/protobuf/reflect/protoreflect"protoimpl "google.golang.org/protobuf/runtime/protoimpl"reflect "reflect"sync "sync"
)const (// Verify that this generated code is sufficiently up-to-date._ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)// Verify that runtime/protoimpl is sufficiently up-to-date._ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)type Person struct {state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsId    uint64           `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`Email string           `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`Name  string           `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`Home  *Person_Location `protobuf:"bytes,4,opt,name=home,proto3" json:"home,omitempty"`// 参数必须大于 0Ids int64 `protobuf:"varint,5,opt,name=ids,proto3" json:"ids,omitempty"`// 参数必须在 0 到 120 之间Age int32 `protobuf:"varint,6,opt,name=age,proto3" json:"age,omitempty"`// 参数是 1 或 2 或 3Code uint32 `protobuf:"varint,7,opt,name=code,proto3" json:"code,omitempty"`// 参数不能是 0 或 99.99Score float32 `protobuf:"fixed32,8,opt,name=score,proto3" json:"score,omitempty"`
}func (x *Person) Reset() {*x = Person{}if protoimpl.UnsafeEnabled {mi := &file_example_proto_msgTypes[0]ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))ms.StoreMessageInfo(mi)}
}func (x *Person) String() string {return protoimpl.X.MessageStringOf(x)
}func (*Person) ProtoMessage() {}func (x *Person) ProtoReflect() protoreflect.Message {mi := &file_example_proto_msgTypes[0]if protoimpl.UnsafeEnabled && x != nil {ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))if ms.LoadMessageInfo() == nil {ms.StoreMessageInfo(mi)}return ms}return mi.MessageOf(x)
}// Deprecated: Use Person.ProtoReflect.Descriptor instead.
func (*Person) Descriptor() ([]byte, []int) {return file_example_proto_rawDescGZIP(), []int{0}
}func (x *Person) GetId() uint64 {if x != nil {return x.Id}return 0
}func (x *Person) GetEmail() string {if x != nil {return x.Email}return ""
}func (x *Person) GetName() string {if x != nil {return x.Name}return ""
}func (x *Person) GetHome() *Person_Location {if x != nil {return x.Home}return nil
}func (x *Person) GetIds() int64 {if x != nil {return x.Ids}return 0
}func (x *Person) GetAge() int32 {if x != nil {return x.Age}return 0
}func (x *Person) GetCode() uint32 {if x != nil {return x.Code}return 0
}func (x *Person) GetScore() float32 {if x != nil {return x.Score}return 0
}type Person_Location struct {state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsLat float64 `protobuf:"fixed64,1,opt,name=lat,proto3" json:"lat,omitempty"`Lng float64 `protobuf:"fixed64,2,opt,name=lng,proto3" json:"lng,omitempty"`
}func (x *Person_Location) Reset() {*x = Person_Location{}if protoimpl.UnsafeEnabled {mi := &file_example_proto_msgTypes[1]ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))ms.StoreMessageInfo(mi)}
}func (x *Person_Location) String() string {return protoimpl.X.MessageStringOf(x)
}func (*Person_Location) ProtoMessage() {}func (x *Person_Location) ProtoReflect() protoreflect.Message {mi := &file_example_proto_msgTypes[1]if protoimpl.UnsafeEnabled && x != nil {ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))if ms.LoadMessageInfo() == nil {ms.StoreMessageInfo(mi)}return ms}return mi.MessageOf(x)
}// Deprecated: Use Person_Location.ProtoReflect.Descriptor instead.
func (*Person_Location) Descriptor() ([]byte, []int) {return file_example_proto_rawDescGZIP(), []int{0, 0}
}func (x *Person_Location) GetLat() float64 {if x != nil {return x.Lat}return 0
}func (x *Person_Location) GetLng() float64 {if x != nil {return x.Lng}return 0
}var File_example_proto protoreflect.FileDescriptorvar file_example_proto_rawDesc = []byte{0x0a, 0x0d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,0x09, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69,0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72,0x6f, 0x74, 0x6f, 0x22, 0xb5, 0x03, 0x0a, 0x06, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x1a,0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x0a, 0xba, 0xe9, 0xc0, 0x03,0x05, 0x32, 0x03, 0x20, 0xe7, 0x07, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x05, 0x65, 0x6d,0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xba, 0xe9, 0xc0, 0x03, 0x04,0x72, 0x02, 0x60, 0x01, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x44, 0x0a, 0x04, 0x6e,0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x30, 0xba, 0xe9, 0xc0, 0x03, 0x2b,0x72, 0x29, 0x28, 0x80, 0x02, 0x32, 0x24, 0x5e, 0x5b, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x41,0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x5d, 0x2b, 0x28, 0x20, 0x5b, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x5d,0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x5d, 0x2b, 0x29, 0x2a, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d,0x65, 0x12, 0x3a, 0x0a, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,0x1a, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x2e, 0x50, 0x65, 0x72, 0x73,0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, 0xba, 0xe9, 0xc0,0x03, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x12, 0x1b, 0x0a,0x03, 0x69, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x09, 0xba, 0xe9, 0xc0, 0x03,0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x03, 0x61, 0x67,0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0b, 0xba, 0xe9, 0xc0, 0x03, 0x06, 0x1a, 0x04,0x18, 0x78, 0x20, 0x00, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x63, 0x6f, 0x64,0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0d, 0xba, 0xe9, 0xc0, 0x03, 0x08, 0x2a, 0x06,0x30, 0x01, 0x30, 0x02, 0x30, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x05,0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x02, 0x42, 0x11, 0xba, 0xe9, 0xc0,0x03, 0x0c, 0x0a, 0x0a, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xe1, 0xfa, 0xc7, 0x42, 0x52, 0x05,0x73, 0x63, 0x6f, 0x72, 0x65, 0x1a, 0x64, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,0x6e, 0x12, 0x2b, 0x0a, 0x03, 0x6c, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x42, 0x19,0xba, 0xe9, 0xc0, 0x03, 0x14, 0x12, 0x12, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x56, 0x40,0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x56, 0xc0, 0x52, 0x03, 0x6c, 0x61, 0x74, 0x12, 0x2b,0x0a, 0x03, 0x6c, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x42, 0x19, 0xba, 0xe9, 0xc0,0x03, 0x14, 0x12, 0x12, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x66, 0x40, 0x29, 0x00, 0x00,0x00, 0x00, 0x00, 0x80, 0x66, 0xc0, 0x52, 0x03, 0x6c, 0x6e, 0x67, 0x42, 0x0b, 0x5a, 0x09, 0x2e,0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}var (file_example_proto_rawDescOnce sync.Oncefile_example_proto_rawDescData = file_example_proto_rawDesc
)func file_example_proto_rawDescGZIP() []byte {file_example_proto_rawDescOnce.Do(func() {file_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_proto_rawDescData)})return file_example_proto_rawDescData
}var file_example_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_example_proto_goTypes = []interface{}{(*Person)(nil),          // 0: examplepb.Person(*Person_Location)(nil), // 1: examplepb.Person.Location
}
var file_example_proto_depIdxs = []int32{1, // 0: examplepb.Person.home:type_name -> examplepb.Person.Location1, // [1:1] is the sub-list for method output_type1, // [1:1] is the sub-list for method input_type1, // [1:1] is the sub-list for extension type_name1, // [1:1] is the sub-list for extension extendee0, // [0:1] is the sub-list for field type_name
}func init() { file_example_proto_init() }
func file_example_proto_init() {if File_example_proto != nil {return}if !protoimpl.UnsafeEnabled {file_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {switch v := v.(*Person); i {case 0:return &v.statecase 1:return &v.sizeCachecase 2:return &v.unknownFieldsdefault:return nil}}file_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {switch v := v.(*Person_Location); i {case 0:return &v.statecase 1:return &v.sizeCachecase 2:return &v.unknownFieldsdefault:return nil}}}type x struct{}out := protoimpl.TypeBuilder{File: protoimpl.DescBuilder{GoPackagePath: reflect.TypeOf(x{}).PkgPath(),RawDescriptor: file_example_proto_rawDesc,NumEnums:      0,NumMessages:   2,NumExtensions: 0,NumServices:   0,},GoTypes:           file_example_proto_goTypes,DependencyIndexes: file_example_proto_depIdxs,MessageInfos:      file_example_proto_msgTypes,}.Build()File_example_proto = out.Filefile_example_proto_rawDesc = nilfile_example_proto_goTypes = nilfile_example_proto_depIdxs = nil
}

learn/pgv/generated/example/example.pb.validate.go

// Code generated by protoc-gen-validate. DO NOT EDIT.
// source: example.protopackage exampleimport ("bytes""errors""fmt""net""net/mail""net/url""regexp""strings""time""unicode/utf8""github.com/golang/protobuf/ptypes"
)// ensure the imports are used
var (_ = bytes.MinRead_ = errors.New("")_ = fmt.Print_ = utf8.UTFMax_ = (*regexp.Regexp)(nil)_ = (*strings.Reader)(nil)_ = net.IPv4len_ = time.Duration(0)_ = (*url.URL)(nil)_ = (*mail.Address)(nil)_ = ptypes.DynamicAny{}
)// Validate checks the field values on Person with the rules defined in the
// proto definition for this message. If any rules are violated, an error is returned.
func (m *Person) Validate() error {if m == nil {return nil}if m.GetId() <= 999 {return PersonValidationError{field:  "Id",reason: "value must be greater than 999",}}if err := m._validateEmail(m.GetEmail()); err != nil {return PersonValidationError{field:  "Email",reason: "value must be a valid email address",cause:  err,}}if len(m.GetName()) > 256 {return PersonValidationError{field:  "Name",reason: "value length must be at most 256 bytes",}}if !_Person_Name_Pattern.MatchString(m.GetName()) {return PersonValidationError{field:  "Name",reason: "value does not match regex pattern \"^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$\"",}}if m.GetHome() == nil {return PersonValidationError{field:  "Home",reason: "value is required",}}if v, ok := interface{}(m.GetHome()).(interface{ Validate() error }); ok {if err := v.Validate(); err != nil {return PersonValidationError{field:  "Home",reason: "embedded message failed validation",cause:  err,}}}if m.GetIds() <= 0 {return PersonValidationError{field:  "Ids",reason: "value must be greater than 0",}}if val := m.GetAge(); val <= 0 || val > 120 {return PersonValidationError{field:  "Age",reason: "value must be inside range (0, 120]",}}if _, ok := _Person_Code_InLookup[m.GetCode()]; !ok {return PersonValidationError{field:  "Code",reason: "value must be in list [1 2 3]",}}if _, ok := _Person_Score_NotInLookup[m.GetScore()]; ok {return PersonValidationError{field:  "Score",reason: "value must not be in list [0 99.99]",}}return nil
}func (m *Person) _validateHostname(host string) error {s := strings.ToLower(strings.TrimSuffix(host, "."))if len(host) > 253 {return errors.New("hostname cannot exceed 253 characters")}for _, part := range strings.Split(s, ".") {if l := len(part); l == 0 || l > 63 {return errors.New("hostname part must be non-empty and cannot exceed 63 characters")}if part[0] == '-' {return errors.New("hostname parts cannot begin with hyphens")}if part[len(part)-1] == '-' {return errors.New("hostname parts cannot end with hyphens")}for _, r := range part {if (r < 'a' || r > 'z') && (r < '0' || r > '9') && r != '-' {return fmt.Errorf("hostname parts can only contain alphanumeric characters or hyphens, got %q", string(r))}}}return nil
}func (m *Person) _validateEmail(addr string) error {a, err := mail.ParseAddress(addr)if err != nil {return err}addr = a.Addressif len(addr) > 254 {return errors.New("email addresses cannot exceed 254 characters")}parts := strings.SplitN(addr, "@", 2)if len(parts[0]) > 64 {return errors.New("email address local phrase cannot exceed 64 characters")}return m._validateHostname(parts[1])
}// PersonValidationError is the validation error returned by Person.Validate if
// the designated constraints aren't met.
type PersonValidationError struct {field  stringreason stringcause  errorkey    bool
}// Field function returns field value.
func (e PersonValidationError) Field() string { return e.field }// Reason function returns reason value.
func (e PersonValidationError) Reason() string { return e.reason }// Cause function returns cause value.
func (e PersonValidationError) Cause() error { return e.cause }// Key function returns key value.
func (e PersonValidationError) Key() bool { return e.key }// ErrorName returns error name.
func (e PersonValidationError) ErrorName() string { return "PersonValidationError" }// Error satisfies the builtin error interface
func (e PersonValidationError) Error() string {cause := ""if e.cause != nil {cause = fmt.Sprintf(" | caused by: %v", e.cause)}key := ""if e.key {key = "key for "}return fmt.Sprintf("invalid %sPerson.%s: %s%s",key,e.field,e.reason,cause)
}var _ error = PersonValidationError{}var _ interface {Field() stringReason() stringKey() boolCause() errorErrorName() string
} = PersonValidationError{}var _Person_Name_Pattern = regexp.MustCompile("^[^[0-9]A-Za-z]+( [^[0-9]A-Za-z]+)*$")var _Person_Code_InLookup = map[uint32]struct{}{1: {},2: {},3: {},
}var _Person_Score_NotInLookup = map[float32]struct{}{0:     {},99.99: {},
}// Validate checks the field values on Person_Location with the rules defined
// in the proto definition for this message. If any rules are violated, an
// error is returned.
func (m *Person_Location) Validate() error {if m == nil {return nil}if val := m.GetLat(); val < -90 || val > 90 {return Person_LocationValidationError{field:  "Lat",reason: "value must be inside range [-90, 90]",}}if val := m.GetLng(); val < -180 || val > 180 {return Person_LocationValidationError{field:  "Lng",reason: "value must be inside range [-180, 180]",}}return nil
}// Person_LocationValidationError is the validation error returned by
// Person_Location.Validate if the designated constraints aren't met.
type Person_LocationValidationError struct {field  stringreason stringcause  errorkey    bool
}// Field function returns field value.
func (e Person_LocationValidationError) Field() string { return e.field }// Reason function returns reason value.
func (e Person_LocationValidationError) Reason() string { return e.reason }// Cause function returns cause value.
func (e Person_LocationValidationError) Cause() error { return e.cause }// Key function returns key value.
func (e Person_LocationValidationError) Key() bool { return e.key }// ErrorName returns error name.
func (e Person_LocationValidationError) ErrorName() string { return "Person_LocationValidationError" }// Error satisfies the builtin error interface
func (e Person_LocationValidationError) Error() string {cause := ""if e.cause != nil {cause = fmt.Sprintf(" | caused by: %v", e.cause)}key := ""if e.key {key = "key for "}return fmt.Sprintf("invalid %sPerson_Location.%s: %s%s",key,e.field,e.reason,cause)
}var _ error = Person_LocationValidationError{}var _ interface {Field() stringReason() stringKey() boolCause() errorErrorName() string
} = Person_LocationValidationError{}

使用示例

然后我们就可以通过Validate方法来进行验证

结合之前博客介绍的拦截器进行校验:

// ValidateAll 对应 protoc-gen-validate 生成的 *.pb.validate.go 中的代码
type Validator interface {ValidateAll() error
}func ServerValidationUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {if r, ok := req.(Validator); ok {if err := r.ValidateAll(); err != nil {return nil, status.Error(codes.InvalidArgument, err.Error())}}return handler(ctx, req)
}

然后在拦截器中引入我们定义的插件:

s := grpc.NewServer(grpc.ChainUnaryInterceptor(grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(opentracing.GlobalTracer()),),ServerValidationUnaryInterceptor,),
)

相关文章:

grpc的验证器

简介 在使用grpc库时候 ,很多时候我们需要对反序列化的参数进行校验,代码中有很多参数校验的代码&#xff0c;如果手动实现&#xff0c;会非常繁琐&#xff0c;对于grpc来说&#xff0c;在定义proto的时候使用直接定义参数的限制规则是一种更合理、更优雅的方式&#xff0c;插…...

无法找到concrt140.dll怎么办?concrt140.dll丢失的5种解决方法

在我们使用计算机的时候&#xff0c;偶尔会遭遇一些技术问题&#xff0c;其中一个比较常见的问题就是出现了"丢失concrt140.dll文件"的提示。当我们的电脑告诉我们缺少了concrt140.dll文件时&#xff0c;常常是因为某些程序无法找到这个文件而导致了程序的运行异常。…...

Elasticsearch 分享

一、Elasticsearch 基础介绍 ElasticSearch 是分布式实时搜索、实时分析、实时存储引擎&#xff0c;简称&#xff08;ES)&#xff0c; 成立于2012年&#xff0c;是一家来自荷兰的、开源的大数据搜索、分析服务提供商&#xff0c;为企业提供实时搜索、数据分析服务&#xff0c;…...

cpu masks的初始化

在内核中&#xff0c;有几个位图变量是用作标识cpu数量和状态的&#xff0c;它们分别是&#xff1a; 变量名称用途循环所使用的宏cpu_possible_mask系统中有多少个可以运行的cpu核for_each_possible_cpucpu_present_mask系统中有多少个可处于运行状态的cpu核for_each_present_…...

【软件测试面试】银行项目测试面试题+答案(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试题&#xff1…...

视频极速切割无损工具免费版,亲测好用!

问题描述 最近想兼职做自媒体&#xff0c;最初想法是想把视频资源下载到本地&#xff0c;本地做一些剪辑和图文配音发布到自媒体app上&#xff0c;但是要把视频(腾讯视频qlv转mp4看我上一篇文章&#xff0c;也是免费版)切割成一小段的片段用手机剪太费劲了&#xff0c;网上好多…...

LightDB ecpg 支持 exec sql execute ... end-exec【24.1】【oracle 兼容】

LightDB 从24.1 版本开始支持 oracle pro*c 中执行匿名块的语法&#xff08;之前可以通过do 语句执行匿名块&#xff09;&#xff1a; EXEC SQL EXECUTEanonymous block END-EXEC;因为匿名块不是SQL标准的一部分&#xff0c;所以此用法也不存在于SQL标准中。 示例 #include …...

菜品检测,基于YOLOV8

菜品检测&#xff0c;基于YOLOV8NANO&#xff0c;训练得到模型PT&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;支持C/PYTHON/ANDROID开发菜品检测&#xff0c;基于YOLOV8&#xff0c;能检测五种菜品&#xff0c;水豆腐、豆腐干、空心菜、豆芽菜、茄子...

前端面试练习24.3.5

webpack相关 项目使用webpack流程 进入一个初始化好的vue项目下载安装webpack相关依赖包/插件 npm install --save-dev webpack webpack-cli webpack-dev-server安装一些相关的loader,比如vue-loader&#xff0c;babel-loader&#xff0c;css-loader等创建webpack.config.js文…...

vim 编辑器

vim 编辑器是什么用途&#xff1f; vim 是一种强大而灵活的文本编辑器&#xff0c;广泛用于开发和系统管理任务。它可以在命令行界面中使用&#xff0c;并提供许多高级编辑功能和快捷键&#xff0c;使用户能够高效地编辑文本文件。 vim 编辑器适用于哪些语言&#xff1f; vim 编…...

docker安装MongoDB脚本

使用docker安装MongoDB只需要按以下步骤执行即可&#xff1a; 一、docker 运行 注意修改默认端口&#xff0c;防止被攻击&#xff1a; docker run -d --name mongo --restartalways -p 25066:28317 -v /usr/local/mongDb/configdb:/data/configdb -v /usr/local/mongDb/db:…...

资料下载-嵌入式 Linux 入门

学习的第一步是去下载资料。 1. 有哪些资料 所有资料分 4 类&#xff1a; ① 开发板配套资料(原理图、虚拟机的映像文件、烧写工具等)&#xff0c;放在百度网盘 ② 录制视频过程中编写的文档、源码、图片&#xff0c;放在 GIT 仓库 ③ u-boot、linux 内核、buildroot 等比较大…...

错误票据 刷题笔记

开数组 读入数据 记录最小值和最大值 每次读入x; 让a[x]; 从最小值开始 向上扫 当扫到a[x]0时候为断号 扫到a[x]>1为重号&#xff1b; 该题的小技巧 未知长度的数据的读入方式 1.首先在头文件敲上 #include<sstream> #include<string> #include<…...

十堰网站建设公司华想科技具有10年的网站制作经验

2018年已经结束了。 华翔科技收到了很多客户的咨询&#xff0c;他们都有一个共同的问题&#xff1a;建一个网站需要多少钱&#xff1f; 但是&#xff0c;我们都会问&#xff1a;您有什么具体需求吗&#xff1f; 大多数人的答案是否定的&#xff0c;他们只是想打听一下价格。 十…...

站库分离技术--反向代理技术-雷池云WAF-给自己搭建一个安全点的网站

文章目录 概要整体架构流程技术名词解释技术细节ssh-ubuntu服务器docker-映射-链接-通信nginx反代mysql设置数据库新密码 小结我的mysql映射目录我的wordpress映射目录 成果展示 概要 新买了一个云服务器&#xff0c;想搭建一个站库分离的wordpress为主的网站&#xff0c;采用d…...

MySQL常见的索引类型介绍

我将为您详细讲解 MySQL 中常见的索引类型&#xff0c;以及它们的使用场景、特点、区别和优势。索引是提高数据库查询性能的关键工具&#xff0c;它可以加速数据检索速度&#xff0c;减少服务器的负担。在 MySQL 中&#xff0c;索引类型主要包括 B-Tree 索引、哈希索引、全文索…...

iOS——【自动引用计数】ARC规则及实现

1.3.3所有权修饰符 所有权修饰符一共有四种&#xff1a; __strong 修饰符__weak 修饰符__undafe_unretained 修饰符__autoreleasing 修饰符 __strong修饰符 _strong修饰符表示对对象的强引用&#xff0c;持有强引用的变量在超出其作用域的时候会被废弃&#xff0c;随着强引…...

智慧城市的前景:数字孪生技术在智慧城市中的应用前景

目录 一、引言 二、数字孪生技术及其在智慧城市中的应用概述 三、数字孪生技术在智慧城市中的应用前景 1、城市规划与仿真模拟 2、智能交通与出行服务 3、智慧环保与可持续发展 4、智慧公共服务与社会治理 5、智慧能源与绿色建筑 四、数字孪生技术在智慧城市中的挑战与…...

Everything:文件查找工具,一搜即得

名人说&#xff1a;东边日出西边雨&#xff0c;道是无晴却有晴。——刘禹锡 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍①Everything②核心功能③原理 二、下载安装①下载②安装 三、使用方法①文…...

【数据结构:树与堆】向上/下调整算法和复杂度的分析、堆排序以及topk问题

文章目录 1.树的概念1.1树的相关概念1.2树的表示 2.二叉树2.1概念2.2特殊二叉树2.3二叉树的存储 3.堆3.1堆的插入&#xff08;向上调整&#xff09;3.2堆的删除&#xff08;向下调整&#xff09;3.3堆的创建3.3.1使用向上调整3.3.2使用向下调整3.3.3两种建堆方式的比较 3.4堆排…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用

中达瑞和自2005年成立以来&#xff0c;一直在光谱成像领域深度钻研和发展&#xff0c;始终致力于研发高性能、高可靠性的光谱成像相机&#xff0c;为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...