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

平板电脑 做网站开发简单网页素材

平板电脑 做网站开发,简单网页素材,剁椒怎么做制作方法,做网站后台主要负责什么简介 在使用grpc库时候 ,很多时候我们需要对反序列化的参数进行校验,代码中有很多参数校验的代码#xff0c;如果手动实现#xff0c;会非常繁琐#xff0c;对于grpc来说#xff0c;在定义proto的时候使用直接定义参数的限制规则是一种更合理、更优雅的方式#xff0c;插…简介 在使用grpc库时候 ,很多时候我们需要对反序列化的参数进行校验,代码中有很多参数校验的代码如果手动实现会非常繁琐对于grpc来说在定义proto的时候使用直接定义参数的限制规则是一种更合理、更优雅的方式插件protoc-gen-validate就是来帮助我们实现这一功能的,使用 proto-gen-validate 生成后的代码进行参数校验我们可以通过在 proto 中编写参数校验规则然后生成代码通过中间件自动的进行校验。 安装 go install github.com/envoyproxy/protoc-gen-validatelatest规则示例 下面为大家列举几种常用类型的参数校验示例更多的示例可以在 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-validatev0.1.0/ \--go_out:./generated \--validate_outlanggo:./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/validateprotoreflect google.golang.org/protobuf/reflect/protoreflectprotoimpl google.golang.org/protobuf/runtime/protoimplreflect reflectsync 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,nameid,proto3 json:id,omitemptyEmail string protobuf:bytes,2,opt,nameemail,proto3 json:email,omitemptyName string protobuf:bytes,3,opt,namename,proto3 json:name,omitemptyHome *Person_Location protobuf:bytes,4,opt,namehome,proto3 json:home,omitempty// 参数必须大于 0Ids int64 protobuf:varint,5,opt,nameids,proto3 json:ids,omitempty// 参数必须在 0 到 120 之间Age int32 protobuf:varint,6,opt,nameage,proto3 json:age,omitempty// 参数是 1 或 2 或 3Code uint32 protobuf:varint,7,opt,namecode,proto3 json:code,omitempty// 参数不能是 0 或 99.99Score float32 protobuf:fixed32,8,opt,namescore,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,namelat,proto3 json:lat,omitemptyLng float64 protobuf:fixed64,2,opt,namelng,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 (byteserrorsfmtnetnet/mailnet/urlregexpstringstimeunicode/utf8github.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 arent 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 arent 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,), )
http://www.hkea.cn/news/14539273/

相关文章:

  • 构建自己的网站大连网络推广机构
  • 阿里云如何做网站网站布局英文
  • 中国住房和城乡建设厅网站外贸网站建设科技
  • 影视自助建站系统源码海口智能建站模板
  • 天津手机模板建站手机网站设计宽度
  • 美橙建站之星怎么样郑州网站建设及托管
  • 网站设计作品哪些网站可以做驾考试题
  • 永春网站建设小程序商城的运营模式
  • 怎么做网站赚钱的动漫网站制作一个网站需要多少钱
  • 家政服务 技术支持 东莞网站建设龙岩正规招聘网
  • 乐陵seo网站千岛湖建设集团办公网站
  • 关于网站集约化建设的意见系统优化设置
  • 同城分类网站建设学网站建设前景
  • 网站开发最好用什么软件前端开发语言的特点是
  • flex 做网站做淘宝优惠网站
  • 访问中国建设银行网站阿勒泰地区网站
  • 鹤壁网站建设优化seo外包 杭州
  • 深圳博纳网站建设什么是网络营销基本思想
  • 怎样做好公司网站怎么网站建设多少钱
  • 网站备案变更主体电话官网如何做广告推广
  • 青海微信网站建设wordpress more
  • 淄博定制网站建设公司制作html网页相册代码
  • 管局审核网站名称门户网站的案例分析
  • 徐州网站制作功能企业网站的类型
  • 做网站的荣誉证书wordpress添加cnzz
  • 外贸网站怎么做谷歌搜索网站建设视频教程网站
  • 手机触屏网站开发dede网站重新安装
  • 建网站策划方案上虞网站设计
  • 四川做网站的上海seo推广整站
  • 织梦电影网站模板下载做阿里巴巴网站要多少钱