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

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

Elasticsearch 分享
一、Elasticsearch 基础介绍 ElasticSearch 是分布式实时搜索、实时分析、实时存储引擎,简称(ES), 成立于2012年,是一家来自荷兰的、开源的大数据搜索、分析服务提供商,为企业提供实时搜索、数据分析服务,…...
cpu masks的初始化
在内核中,有几个位图变量是用作标识cpu数量和状态的,它们分别是: 变量名称用途循环所使用的宏cpu_possible_mask系统中有多少个可以运行的cpu核for_each_possible_cpucpu_present_mask系统中有多少个可处于运行状态的cpu核for_each_present_…...

【软件测试面试】银行项目测试面试题+答案(二)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 面试题࿱…...

视频极速切割无损工具免费版,亲测好用!
问题描述 最近想兼职做自媒体,最初想法是想把视频资源下载到本地,本地做一些剪辑和图文配音发布到自媒体app上,但是要把视频(腾讯视频qlv转mp4看我上一篇文章,也是免费版)切割成一小段的片段用手机剪太费劲了,网上好多…...
LightDB ecpg 支持 exec sql execute ... end-exec【24.1】【oracle 兼容】
LightDB 从24.1 版本开始支持 oracle pro*c 中执行匿名块的语法(之前可以通过do 语句执行匿名块): EXEC SQL EXECUTEanonymous block END-EXEC;因为匿名块不是SQL标准的一部分,所以此用法也不存在于SQL标准中。 示例 #include …...

菜品检测,基于YOLOV8
菜品检测,基于YOLOV8NANO,训练得到模型PT,然后转换成ONNX,OPENCV的DNN调用,支持C/PYTHON/ANDROID开发菜品检测,基于YOLOV8,能检测五种菜品,水豆腐、豆腐干、空心菜、豆芽菜、茄子...
前端面试练习24.3.5
webpack相关 项目使用webpack流程 进入一个初始化好的vue项目下载安装webpack相关依赖包/插件 npm install --save-dev webpack webpack-cli webpack-dev-server安装一些相关的loader,比如vue-loader,babel-loader,css-loader等创建webpack.config.js文…...
vim 编辑器
vim 编辑器是什么用途? vim 是一种强大而灵活的文本编辑器,广泛用于开发和系统管理任务。它可以在命令行界面中使用,并提供许多高级编辑功能和快捷键,使用户能够高效地编辑文本文件。 vim 编辑器适用于哪些语言? vim 编…...
docker安装MongoDB脚本
使用docker安装MongoDB只需要按以下步骤执行即可: 一、docker 运行 注意修改默认端口,防止被攻击: docker run -d --name mongo --restartalways -p 25066:28317 -v /usr/local/mongDb/configdb:/data/configdb -v /usr/local/mongDb/db:…...

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

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

十堰网站建设公司华想科技具有10年的网站制作经验
2018年已经结束了。 华翔科技收到了很多客户的咨询,他们都有一个共同的问题:建一个网站需要多少钱? 但是,我们都会问:您有什么具体需求吗? 大多数人的答案是否定的,他们只是想打听一下价格。 十…...

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

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

iOS——【自动引用计数】ARC规则及实现
1.3.3所有权修饰符 所有权修饰符一共有四种: __strong 修饰符__weak 修饰符__undafe_unretained 修饰符__autoreleasing 修饰符 __strong修饰符 _strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域的时候会被废弃,随着强引…...

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

Everything:文件查找工具,一搜即得
名人说:东边日出西边雨,道是无晴却有晴。——刘禹锡 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、软件介绍①Everything②核心功能③原理 二、下载安装①下载②安装 三、使用方法①文…...

【数据结构:树与堆】向上/下调整算法和复杂度的分析、堆排序以及topk问题
文章目录 1.树的概念1.1树的相关概念1.2树的表示 2.二叉树2.1概念2.2特殊二叉树2.3二叉树的存储 3.堆3.1堆的插入(向上调整)3.2堆的删除(向下调整)3.3堆的创建3.3.1使用向上调整3.3.2使用向下调整3.3.3两种建堆方式的比较 3.4堆排…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#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开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: 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的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...