golang源码分析protocvalidate
业务代码中有很多参数校验的代码,如果手动实现,会非常繁琐,https:github。comgoplaygroundvalidator是一个非常不错的选择echo源码分析(validator),但是对于grpc来说,在定义proto的时候使用直接定义参数的限制规则是一种更合理、更优雅的方式,插件https:github。combufbuildprotocgenvalidate就是来帮助我们实现这一功能的。kratos框架也用到了这个插件。下面我们详细介绍下如何安装和使用。
首先,github上的安装方式并不好使,生成的代码里并没有校验规则,相反我们会得到下面的注释novalidationrulesforIdnovalidationrulesforEmail
这是因为,这个包的main分支是不稳定版本,按照官方的方式安装并不好使。我们可以安装稳定版本goinstallgithub。comenvoyproxyprotocgenvalidatev0。1。0
然后我们可以在GOPATH看到这个插件lsGOPATHbinprotocgenvalidatexxxbinprotocgenvalidate
对应的,我们的protoc版本如下protocversionlibprotoc3。19。4
然后,可以定义我们的proto文件syntaxproto3;packageexamplepb;optiongopackage。example;importvalidatevalidate。proto;messagePerson{uint64id1〔(validate。rules)。uint64。gt999〕;stringemail2〔(validate。rules)。string。emailtrue〕;stringname3〔(validate。rules)。string{pattern:〔〔09〕AZaz〕(〔〔09〕AZaz〕)34;,maxbytes:256,}〕;Locationhome4〔(validate。rules)。message。requiredtrue〕;参数必须大于0int64ids5〔(validate。rules)。int64{gt:0}〕;参数必须在0到120之间int32age6〔(validate。rules)。int32{gt:0,lte:120}〕;参数是1或2或3uint32code7〔(validate。rules)。uint32{in:〔1,2,3〕}〕;参数不能是0或99。99floatscore8〔(validate。rules)。float{notin:〔0,99。99〕}〕;messageLocation{doublelat1〔(validate。rules)。double{gte:90,lte:90}〕;doublelng2〔(validate。rules)。double{gte:180,lte:180}〕;}}
使用命令生成go文件protocI。pluginGOPATHbinprotocgenvalidateI{GOPATH}pkgmodgithub。comenvoyproxyprotocgenvalidatev0。1。0goout:。generatedvalidateoutlanggo:。generatedexample。proto
相应的,我们得到了两个文件
learnpgvgeneratedexampleexample。pb。goCodegeneratedbyprotocgengo。DONOTEDIT。versions:protocgengov1。28。1protocv3。19。4source:example。protopackageexampleimport(github。comenvoyproxyprotocgenvalidatevalidateprotoreflectgoogle。golang。orgprotobufreflectprotoreflectprotoimplgoogle。golang。orgprotobufruntimeprotoimplreflectreflectsyncsync)const(Verifythatthisgeneratedcodeissufficientlyuptodate。protoimpl。EnforceVersion(20protoimpl。MinVersion)Verifythatruntimeprotoimplissufficientlyuptodate。protoimpl。EnforceVersion(protoimpl。MaxVersion20))typePersonstruct{stateprotoimpl。MessageStatesizeCacheprotoimpl。SizeCacheunknownFieldsprotoimpl。UnknownFieldsIduint64protobuf:varint,1,opt,nameid,proto3json:id,omitemptyEmailstringprotobuf:bytes,2,opt,nameemail,proto3json:email,omitemptyNamestringprotobuf:bytes,3,opt,namename,proto3json:name,omitemptyHomePersonLocationprotobuf:bytes,4,opt,namehome,proto3json:home,omitempty参数必须大于0Idsint64protobuf:varint,5,opt,nameids,proto3json:ids,omitempty参数必须在0到120之间Ageint32protobuf:varint,6,opt,nameage,proto3json:age,omitempty参数是1或2或3Codeuint32protobuf:varint,7,opt,namecode,proto3json:code,omitempty参数不能是0或99。99Scorefloat32protobuf:fixed32,8,opt,namescore,proto3json:score,omitempty}func(xPerson)Reset(){xPerson{}ifprotoimpl。UnsafeEnabled{mi:fileexampleprotomsgTypes〔0〕ms:protoimpl。X。MessageStateOf(protoimpl。Pointer(x))ms。StoreMessageInfo(mi)}}func(xPerson)String()string{returnprotoimpl。X。MessageStringOf(x)}func(Person)ProtoMessage(){}func(xPerson)ProtoReflect()protoreflect。Message{mi:fileexampleprotomsgTypes〔0〕ifprotoimpl。UnsafeEnabledx!nil{ms:protoimpl。X。MessageStateOf(protoimpl。Pointer(x))ifms。LoadMessageInfo()nil{ms。StoreMessageInfo(mi)}returnms}returnmi。MessageOf(x)}Deprecated:UsePerson。ProtoReflect。Descriptorinstead。func(Person)Descriptor()(〔〕byte,〔〕int){returnfileexampleprotorawDescGZIP(),〔〕int{0}}func(xPerson)GetId()uint64{ifx!nil{returnx。Id}return0}func(xPerson)GetEmail()string{ifx!nil{returnx。Email}return}func(xPerson)GetName()string{ifx!nil{returnx。Name}return}func(xPerson)GetHome()PersonLocation{ifx!nil{returnx。Home}returnnil}func(xPerson)GetIds()int64{ifx!nil{returnx。Ids}return0}func(xPerson)GetAge()int32{ifx!nil{returnx。Age}return0}func(xPerson)GetCode()uint32{ifx!nil{returnx。Code}return0}func(xPerson)GetScore()float32{ifx!nil{returnx。Score}return0}typePersonLocationstruct{stateprotoimpl。MessageStatesizeCacheprotoimpl。SizeCacheunknownFieldsprotoimpl。UnknownFieldsLatfloat64protobuf:fixed64,1,opt,namelat,proto3json:lat,omitemptyLngfloat64protobuf:fixed64,2,opt,namelng,proto3json:lng,omitempty}func(xPersonLocation)Reset(){xPersonLocation{}ifprotoimpl。UnsafeEnabled{mi:fileexampleprotomsgTypes〔1〕ms:protoimpl。X。MessageStateOf(protoimpl。Pointer(x))ms。StoreMessageInfo(mi)}}func(xPersonLocation)String()string{returnprotoimpl。X。MessageStringOf(x)}func(PersonLocation)ProtoMessage(){}func(xPersonLocation)ProtoReflect()protoreflect。Message{mi:fileexampleprotomsgTypes〔1〕ifprotoimpl。UnsafeEnabledx!nil{ms:protoimpl。X。MessageStateOf(protoimpl。Pointer(x))ifms。LoadMessageInfo()nil{ms。StoreMessageInfo(mi)}returnms}returnmi。MessageOf(x)}Deprecated:UsePersonLocation。ProtoReflect。Descriptorinstead。func(PersonLocation)Descriptor()(〔〕byte,〔〕int){returnfileexampleprotorawDescGZIP(),〔〕int{0,0}}func(xPersonLocation)GetLat()float64{ifx!nil{returnx。Lat}return0}func(xPersonLocation)GetLng()float64{ifx!nil{returnx。Lng}return0}varFileexampleprotoprotoreflect。FileDescriptorvarfileexampleprotorawDesc〔〕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(fileexampleprotorawDescOncesync。OncefileexampleprotorawDescDatafileexampleprotorawDesc)funcfileexampleprotorawDescGZIP()〔〕byte{fileexampleprotorawDescOnce。Do(func(){fileexampleprotorawDescDataprotoimpl。X。CompressGZIP(fileexampleprotorawDescData)})returnfileexampleprotorawDescData}varfileexampleprotomsgTypesmake(〔〕protoimpl。MessageInfo,2)varfileexampleprotogoTypes〔〕interface{}{(Person)(nil),0:examplepb。Person(PersonLocation)(nil),1:examplepb。Person。Location}varfileexampleprotodepIdxs〔〕int32{1,0:examplepb。Person。home:typenameexamplepb。Person。Location1,〔1:1〕isthesublistformethodoutputtype1,〔1:1〕isthesublistformethodinputtype1,〔1:1〕isthesublistforextensiontypename1,〔1:1〕isthesublistforextensionextendee0,〔0:1〕isthesublistforfieldtypename}funcinit(){fileexampleprotoinit()}funcfileexampleprotoinit(){ifFileexampleproto!nil{return}if!protoimpl。UnsafeEnabled{fileexampleprotomsgTypes〔0〕。Exporterfunc(vinterface{},iint)interface{}{switchv:v。(Person);i{case0:returnv。statecase1:returnv。sizeCachecase2:returnv。unknownFieldsdefault:returnnil}}fileexampleprotomsgTypes〔1〕。Exporterfunc(vinterface{},iint)interface{}{switchv:v。(PersonLocation);i{case0:returnv。statecase1:returnv。sizeCachecase2:returnv。unknownFieldsdefault:returnnil}}}typexstruct{}out:protoimpl。TypeBuilder{File:protoimpl。DescBuilder{GoPackagePath:reflect。TypeOf(x{})。PkgPath(),RawDescriptor:fileexampleprotorawDesc,NumEnums:0,NumMessages:2,NumExtensions:0,NumServices:0,},GoTypes:fileexampleprotogoTypes,DependencyIndexes:fileexampleprotodepIdxs,MessageInfos:fileexampleprotomsgTypes,}。Build()Fileexampleprotoout。FilefileexampleprotorawDescnilfileexampleprotogoTypesnilfileexampleprotodepIdxsnil}
learnpgvgeneratedexampleexample。pb。validate。goCodegeneratedbyprotocgenvalidate。DONOTEDIT。source:example。protopackageexampleimport(byteserrorsfmtnetnetmailneturlregexpstringstimeunicodeutf8github。comgolangprotobufptypes)ensuretheimportsareusedvar(bytes。MinReaderrors。New()fmt。Printutf8。UTFMax(regexp。Regexp)(nil)(strings。Reader)(nil)net。IPv4lentime。Duration(0)(url。URL)(nil)(mail。Address)(nil)ptypes。DynamicAny{})ValidatechecksthefieldvaluesonPersonwiththerulesdefinedintheprotodefinitionforthismessage。Ifanyrulesareviolated,anerrorisreturned。func(mPerson)Validate()error{ifmnil{returnnil}ifm。GetId()999{returnPersonValidationError{field:Id,reason:valuemustbegreaterthan999,}}iferr:m。validateEmail(m。GetEmail());err!nil{returnPersonValidationError{field:Email,reason:valuemustbeavalidemailaddress,cause:err,}}iflen(m。GetName())256{returnPersonValidationError{field:Name,reason:valuelengthmustbeatmost256bytes,}}if!PersonNamePattern。MatchString(m。GetName()){returnPersonValidationError{field:Name,reason:valuedoesnotmatchregexpattern〔〔09〕AZaz〕(〔〔09〕AZaz〕),}}ifm。GetHome()nil{returnPersonValidationError{field:Home,reason:valueisrequired,}}ifv,ok:interface{}(m。GetHome())。(interface{Validate()error});ok{iferr:v。Validate();err!nil{returnPersonValidationError{field:Home,reason:embeddedmessagefailedvalidation,cause:err,}}}ifm。GetIds()0{returnPersonValidationError{field:Ids,reason:valuemustbegreaterthan0,}}ifval:m。GetAge();val0val120{returnPersonValidationError{field:Age,reason:valuemustbeinsiderange(0,120〕,}}if,ok:PersonCodeInLookup〔m。GetCode()〕;!ok{returnPersonValidationError{field:Code,reason:valuemustbeinlist〔123〕,}}if,ok:PersonScoreNotInLookup〔m。GetScore()〕;ok{returnPersonValidationError{field:Score,reason:valuemustnotbeinlist〔099。99〕,}}returnnil}func(mPerson)validateHostname(hoststring)error{s:strings。ToLower(strings。TrimSuffix(host,。))iflen(host)253{returnerrors。New(hostnamecannotexceed253characters)}for,part:rangestrings。Split(s,。){ifl:len(part);l0l63{returnerrors。New(hostnamepartmustbenonemptyandcannotexceed63characters)}ifpart〔0〕{returnerrors。New(hostnamepartscannotbeginwithhyphens)}ifpart〔len(part)1〕{returnerrors。New(hostnamepartscannotendwithhyphens)}for,r:rangepart{if(rarz)(r0r9)r!{returnfmt。Errorf(hostnamepartscanonlycontainalphanumericcharactersorhyphens,gotq,string(r))}}}returnnil}func(mPerson)validateEmail(addrstring)error{a,err:mail。ParseAddress(addr)iferr!nil{returnerr}addra。Addressiflen(addr)254{returnerrors。New(emailaddressescannotexceed254characters)}parts:strings。SplitN(addr,,2)iflen(parts〔0〕)64{returnerrors。New(emailaddresslocalphrasecannotexceed64characters)}returnm。validateHostname(parts〔1〕)}PersonValidationErroristhevalidationerrorreturnedbyPerson。Validateifthedesignatedconstraintsarentmet。typePersonValidationErrorstruct{fieldstringreasonstringcauseerrorkeybool}Fieldfunctionreturnsfieldvalue。func(ePersonValidationError)Field()string{returne。field}Reasonfunctionreturnsreasonvalue。func(ePersonValidationError)Reason()string{returne。reason}Causefunctionreturnscausevalue。func(ePersonValidationError)Cause()error{returne。cause}Keyfunctionreturnskeyvalue。func(ePersonValidationError)Key()bool{returne。key}ErrorNamereturnserrorname。func(ePersonValidationError)ErrorName()string{returnPersonValidationError}Errorsatisfiesthebuiltinerrorinterfacefunc(ePersonValidationError)Error()string{cause:ife。cause!nil{causefmt。Sprintf(causedby:v,e。cause)}key:ife。key{keykeyfor}returnfmt。Sprintf(invalidsPerson。s:ss,key,e。field,e。reason,cause)}varerrorPersonValidationError{}varinterface{Field()stringReason()stringKey()boolCause()errorErrorName()string}PersonValidationError{}varPersonNamePatternregexp。MustCompile(〔〔09〕AZaz〕(〔〔09〕AZaz〕)34;)varPersonCodeInLookupmap〔uint32〕struct{}{1:{},2:{},3:{},}varPersonScoreNotInLookupmap〔float32〕struct{}{0:{},99。99:{},}ValidatechecksthefieldvaluesonPersonLocationwiththerulesdefinedintheprotodefinitionforthismessage。Ifanyrulesareviolated,anerrorisreturned。func(mPersonLocation)Validate()error{ifmnil{returnnil}ifval:m。GetLat();val90val90{returnPersonLocationValidationError{field:Lat,reason:valuemustbeinsiderange〔90,90〕,}}ifval:m。GetLng();val180val180{returnPersonLocationValidationError{field:Lng,reason:valuemustbeinsiderange〔180,180〕,}}returnnil}PersonLocationValidationErroristhevalidationerrorreturnedbyPersonLocation。Validateifthedesignatedconstraintsarentmet。typePersonLocationValidationErrorstruct{fieldstringreasonstringcauseerrorkeybool}Fieldfunctionreturnsfieldvalue。func(ePersonLocationValidationError)Field()string{returne。field}Reasonfunctionreturnsreasonvalue。func(ePersonLocationValidationError)Reason()string{returne。reason}Causefunctionreturnscausevalue。func(ePersonLocationValidationError)Cause()error{returne。cause}Keyfunctionreturnskeyvalue。func(ePersonLocationValidationError)Key()bool{returne。key}ErrorNamereturnserrorname。func(ePersonLocationValidationError)ErrorName()string{returnPersonLocationValidationError}Errorsatisfiesthebuiltinerrorinterfacefunc(ePersonLocationValidationError)Error()string{cause:ife。cause!nil{causefmt。Sprintf(causedby:v,e。cause)}key:ife。key{keykeyfor}returnfmt。Sprintf(invalidsPersonLocation。s:ss,key,e。field,e。reason,cause)}varerrorPersonLocationValidationError{}varinterface{Field()stringReason()stringKey()boolCause()errorErrorName()string}PersonLocationValidationError{}
然后我们就可以通过Validate方法来进行验证packagemainimport(fmt。learnpgvgeneratedexample)funcmain(){p:new(Person)err:p。Validate()err:Idmustbegreaterthan999fmt。Println(err)p。Id1000errp。Validate()err:Emailmustbeavalidemailaddressp。Emailexamplebufbuild。comerrp。Validate()err:Namemustmatchpattern〔ds〕(〔ds〕)39;p。NameProtocolBuffererrp。Validate()err:Homeisrequiredp。HomePersonLocation{Lat:37。7,Lng:999}errp。Validate()err:Home。Lngmustbewithin〔180,180〕p。Home。Lng122。4errp。Validate()err:nil}
运行效果如下gorunmain。goinvalidPerson。Id:valuemustbegreaterthan999
通过proto的注解扩展,配合这个插件,我们可以非常方便地实现参数校验能力,真正把idl当作交流沟通的完备工具,有效提升开发效率。〔(validate。rules)。uint32{in:〔1,2,3〕}〕;
2022年开始,手握大量现金的人会睡不着?3点原因很现实有钱也会难熬睡不着觉吗?这是大部分普通人的疑问,实际上对于有钱人来说,在面临资产缩水,现金贬值的情况下,还真的会很难熬会睡不着觉的,咱们不妨来分析一下,2022年开始,手握大量现金
文案丨很现实很讽刺的句子1。这是个现实的世界感情不能当饭吃贫穷夫妻百事哀不要相信电影里的故事情节那只是个供许多人喧嚣感情的场所2。女孩子不读书将来只有逛不完的菜市场和买不完的地摊货这句话不好听但真实。3。
荣耀新机发布居中打孔屏10GB256GB,高版本仅1599元随着荣耀品牌的独立,新机的发布量一直处于增长,基本每个月都有新机发布,从旗舰机到低端机全面覆盖市场的需求。从荣耀所发布的机型中,可以看到荣耀自身所拥有的技术并不多,毕竟核心技术仍是
京东裁员把刘强东都裁了?你见过哪家公司裁员能把自己的老板裁掉的骚操作吗?就在前几日,嘴哥还发表过互联网公司京东裁员的新闻。当时京东的裁员排号都排在了千名以外了。这不,今天的新闻就更奇葩了。京东老板刘强东直
外媒华为的天就要白了天黑黑自从2019年5月开始,华为的业务因为一系列的清单规则修改而处处受限。不仅是在重要零部件上供应链被切断,最主要的是芯片供应链也被截断了。大家都知道,芯片作为电子设备的运行核心
通州台湖和马驹桥的社保事项,正式移交北京经开区2022年4月6日,北京市人力资源和社会保障局发布公告从2022年4月25日,正式将通州区78公里范围内的社保事务移交北京经开区。而这个通州区78公里范围就是通州马驹桥镇和台湖镇的
央行发布重要公告每经编辑毕陆名据人民银行4月7日公告称,为维护银行体系流动性合理充裕,2022年4月7日人民银行以利率招标方式开展了100亿元逆回购操作,中标利率为2。1。由于今日有1500亿元逆
油价要下跌!今天4月7日调整后,全国加油站9295汽油最新售价成品油价预测跌幅扩大,快要大降了油价最新消息今天,2022年4月7日,星期四,国内成品油十个工作日计价周期即将过半。好消息是,在各品种油价过高的情况下,石油大国正在进行一场救市游戏
从零到行业一哥,罗永浩只用了两年,真还传喜迎大结局直播带货作为新兴的在线购物方式,发展速度是非常迅猛的,前些年,淘宝的出现颠覆了我们的购物方式,让线上购物深入人心。而短视频平台的兴起,再一次为线上购物注入了新的活力。直播带货与其他
油价调整消息今天4月7日,全国调价后9295号汽油价格今天是2022年4月7日,距离国内成品油调价窗口的开启越来越近。截至国内成品油价格调整的第4个工作日,参考原油油种周期内滚动均价为101。59美元桶,原油综合变化率11。20,预计
不要轻易放弃你手中的筹码不要轻易丢到手中的筹码,跌麻了吗。我手中的票今天也大幅缩水,说实话,真不怕。明天就能见到太阳。今天整体是一个普跌的情况,尤其是在昨天普涨的状况下。老的热点,地产,中药,数字经济同样