<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
用C++做業務發開的同學是否還在不厭其煩的編寫大量if-else模組來做介面引數校驗呢?當介面欄位數量多大幾十個,這樣的引數校驗程式碼都能多達上百行,甚至超過了介面業務邏輯的程式碼體量,而且隨著業務迭代,介面增加了新的欄位,又不得不再加幾個if-else,對於有Java、python等開發經歷的同學,對這種原始的引數校驗方法必定是嗤之以鼻。今天,我們就模擬Java裡面通過註解實現引數校驗的方式來針對C++ protobuf介面實現一個更加方便、快捷的引數校驗自動工具。
實現基本思路主要用到兩個核心技術點:protobuf欄位屬性擴充套件和反射機制。
首先針對常用的協定欄位資料型別(int32、int64、uint32、uint64、float、double、string、array、enum)定義了一套最常用的欄位校驗規則,如下表:
每個校驗規則的protobuf定義如下:
// int32型別校驗規則 message Int32Rule { oneof lt_rule { int32 lt = 1; } oneof lte_rule { int32 lte = 2; } oneof gt_rule { int32 gt = 3; } oneof gte_rule { int32 gte = 4; } repeated int32 in = 5; repeated int32 not_in = 6; } // int64型別校驗規則 message Int64Rule { oneof lt_rule { int64 lt = 1; } oneof lte_rule { int64 lte = 2; } oneof gt_rule { int64 gt = 3; } oneof gte_rule { int64 gte = 4; } repeated int64 in = 5; repeated int64 not_in = 6; } // uint32型別校驗規則 message UInt32Rule { oneof lt_rule { uint32 lt = 1; } oneof lte_rule { uint32 lte = 2; } oneof gt_rule { uint32 gt = 3; } oneof gte_rule { uint32 gte = 4; } repeated uint32 in = 5; repeated uint32 not_in = 6; } // uint64型別校驗規則 message UInt64Rule { oneof lt_rule { uint64 lt = 1; } oneof lte_rule { uint64 lte = 2; } oneof gt_rule { uint64 gt = 3; } oneof gte_rule { uint64 gte = 4; } repeated uint64 in = 5; repeated uint64 not_in = 6; } // float型別校驗規則 message FloatRule { oneof lt_rule { float lt = 1; } oneof lte_rule { float lte = 2; } oneof gt_rule { float gt = 3; } oneof gte_rule { float gte = 4; } repeated float in = 5; repeated float not_in = 6; } // double型別校驗規則 message DoubleRule { oneof lt_rule { double lt = 1; } oneof lte_rule { double lte = 2; } oneof gt_rule { double gt = 3; } oneof gte_rule { double gte = 4; } repeated double in = 5; repeated double not_in = 6; } // string型別校驗規則 message StringRule { bool not_empty = 1; oneof min_len_rule { uint32 min_len = 2; } oneof max_len_rule { uint32 max_len = 3; } string regex_pattern = 4; } // enum型別校驗規則 message EnumRule { repeated int32 in = 1; } // array(陣列)型別校驗規則 message ArrayRule { bool not_empty = 1; oneof min_len_rule { uint32 min_len = 2; } oneof max_len_rule { uint32 max_len = 3; } }
注意:校驗規則中一些欄位通過oneof關鍵字包裝了一層,主要是因為protobuf3中全部欄位都預設是optional的,即即使不顯示設定其值,protobuf也會給它一個預設值,如數值型別的一般預設值就是0,這樣當某個規則的值(如lt)為0的時候,我們無法確定是沒有設定值還是就是設定的0,加了oneof後可以通過oneof欄位的xxx_case方法來判斷對應值是否有人為設定。
上述規則被劃分為4大類:數值類規則(Int32Rule、Int64Rule、UInt32Rule、UInt64Rule、FloatRule、DoubleRule)、字串類規則(StringRule)、列舉類規則(EnumRule)、陣列類規則(ArrayRule), 每一類後續都會有一個對應的校驗器(引數校驗演演算法)。
然後,拓展protobuf欄位屬性(google.protobuf.FieldOptions),將欄位校驗規則拓展為欄位屬性之一。如下圖:擴充套件欄位屬性名為Rule, 其型別為ValidateRules,其具體校驗規則通過oneof關鍵字限定至多為上述9種校驗規則之一(針對某一個欄位,其型別唯一,從而其校驗規則也是確定的)。
// 校驗規則(oneof取上述欄位型別校驗規則之一) message ValidateRules { oneof rule { /* 基本型別規則 */ Int32Rule int32 = 1; Int64Rule int64 = 2; UInt32Rule uint32 = 3; UInt64Rule uint64 = 4; FloatRule float = 5; DoubleRule double = 6; StringRule string = 7; /* 複雜型別規則 */ EnumRule enum = 8; ArrayRule array = 9; } } // 拓展預設欄位屬性, 將ValidateRules設定為欄位屬性 extend google.protobuf.FieldOptions { ValidateRules Rule = 10000; }
上述校驗規則和欄位屬性擴充套件定義在validator.proto檔案中,使用時通過import匯入該proto檔案便可以使用上述擴充套件欄位屬性用於定義欄位,如:
說明: 上述介面定義中,通過擴充套件欄位屬性validator.Rule(其內容為上述定義9中型別校驗規則之一)限制了使用者年齡age欄位值必須小於等於(lte)150;名字name欄位不能為空且長度不能大於32;手機號欄位phone不能為空且必須滿足指定的手機號正規表示式規則;郵件欄位允許為空(預設)但如果有傳入值的話則必須滿足對應郵件正規表示式規則;others陣列欄位不允許為空,且長度不小於2。
有了上述介面欄位定義後,需要校驗的欄位都已經帶上了validator.Rule屬性,其中已包含了對應欄位的校驗規則,接下來需要實現一個引數自動校驗演演算法, 基本思路就是通過反射逐個獲取待校驗Message結構體中各個欄位值及其欄位屬性中校驗規則validator.Rule,然後逐一匹配欄位值是否滿足每一項規則定義,不滿足則返回FALSE;對於巢狀結構體型別則做遞迴校驗,演演算法流程及實現如下:
#pragma once #include <google/protobuf/message.h> #include <butil/logging.h> #include <regex> #include <algorithm> #include <sstream> #include "proto/validator.pb.h" namespace validator { using namespace google::protobuf; /** 不知道為什麼protobuf對ValidateRules中float和double兩個欄位生成的欄位名會加個字尾_(其他欄位沒有), 為了在宏裡面統一處理加了下面兩個定義 */ typedef float float_; typedef double double_; /** * 數值校驗器(適用於int32、int64、uint32、uint64、float、double) * 支援大於、大於等於、小於、小於等於、in、not_in校驗 */ #define NumericalValidator(pb_cpptype, method_type, value_type) case google::protobuf::FieldDescriptor::CPPTYPE_##pb_cpptype: { if (validate_rules.has_##value_type()) { const method_type##Rule& rule = validate_rules.value_type(); value_type value = reflection->Get##method_type(message, field); if ((rule.lt_rule_case() && value >= rule.lt()) || (rule.lte_rule_case() && value > rule.lte()) || (rule.gt_rule_case() && value <= rule.gt()) || (rule.gte_rule_case() && value < rule.gte())) { std::ostringstream os; os << field->full_name() << " value out of range."; return {false, os.str()}; } if ((!rule.in().empty() && std::find(rule.in().begin(), rule.in().end(), value) == rule.in().end()) || (!rule.not_in().empty() && std::find(rule.not_in().begin(), rule.not_in().end(), value) != rule.not_in().end())) { std::ostringstream os; os << field->full_name() << " value not allowed."; return {false, os.str()}; } } break; } /** * 字串校驗器(string) * 支援字串非空校驗、最短(最長)長度校驗、正則匹配校驗 */ #define StringValidator(pb_cpptype, method_type, value_type) case google::protobuf::FieldDescriptor::CPPTYPE_##pb_cpptype: { if (validate_rules.has_##value_type()) { const method_type##Rule& rule = validate_rules.value_type(); const value_type& value = reflection->Get##method_type(message, field); if (rule.not_empty() && value.empty()) { std::ostringstream os; os << field->full_name() << " can not be empty."; return {false, os.str()}; } if ((rule.min_len_rule_case() && value.length() < rule.min_len()) || (rule.max_len_rule_case() && value.length() > rule.max_len())) { std::ostringstream os; os << field->full_name() << " length out of range."; return {false, os.str()}; } if (!value.empty() && !rule.regex_pattern().empty()) { std::regex ex(rule.regex_pattern()); if (!regex_match(value, ex)) { std::ostringstream os; os << field->full_name() << " format invalid."; return {false, os.str()}; } } } break; } /** * 列舉校驗器(enum) * 僅支援in校驗 */ #define EnumValidator(pb_cpptype, method_type, value_type) case google::protobuf::FieldDescriptor::CPPTYPE_##pb_cpptype: { if (validate_rules.has_##value_type()) { const method_type##Rule& rule = validate_rules.value_type(); int value = reflection->Get##method_type(message, field)->number(); if (!rule.in().empty() && std::find(rule.in().begin(), rule.in().end(), value) == rule.in().end()) { std::ostringstream os; os << field->full_name() << " value not allowed."; return {false, os.str()}; } } break; } /** * 陣列校驗器(array) * 支援陣列非空校驗、最短(最長)長度校驗以及Message結構體元素遞迴校驗 */ #define ArrayValidator() uint32 arr_len = (uint32)reflection->FieldSize(message, field); if (validate_rules.has_array()) { const ArrayRule& rule = validate_rules.array(); if (rule.not_empty() && arr_len == 0) { std::ostringstream os; os << field->full_name() << " can not be empty."; return {false, os.str()}; } if ((rule.min_len() != 0 && arr_len < rule.min_len()) || (rule.max_len() != 0 && arr_len > rule.max_len())) { std::ostringstream os; os << field->full_name() << " length out of range."; return {false, os.str()}; } } /* 如果陣列元素是Message結構體型別,遞迴校驗每個元素 */ if (field_type == FieldDescriptor::CPPTYPE_MESSAGE) { for (uint32 i = 0; i < arr_len; i++) { const Message& sub_message = reflection->GetRepeatedMessage(message, field, i); ValidateResult&& result = Validate(sub_message); if (!result.is_valid) { return result; } } } /** * 結構體校驗器(Message) * (遞迴校驗) */ #define MessageValidator() case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { const Message& sub_message = reflection->GetMessage(message, field); ValidateResult&& result = Validate(sub_message); if (!result.is_valid) { return result; } break; } class ValidatorUtil { public: struct ValidateResult { bool is_valid; std::string msg; }; static ValidateResult Validate(const Message& message) { const Descriptor* descriptor = message.GetDescriptor(); const Reflection* reflection = message.GetReflection(); for (int i = 0; i < descriptor->field_count(); i++) { const FieldDescriptor* field = descriptor->field(i); FieldDescriptor::CppType field_type = field->cpp_type(); const ValidateRules& validate_rules = field->options().GetExtension(validator::Rule); if (field->is_repeated()) { // 陣列型別校驗 ArrayValidator(); } else { // 非陣列型別,直接呼叫對應型別校驗器 switch (field_type) { NumericalValidator(INT32, Int32, int32); NumericalValidator(INT64, Int64, int64); NumericalValidator(UINT32, UInt32, uint32); NumericalValidator(UINT64, UInt64, uint64); NumericalValidator(FLOAT, Float, float_); NumericalValidator(DOUBLE, Double, double_); StringValidator(STRING, String, string); EnumValidator(ENUM, Enum, enum_); MessageValidator(); default: break; } } } return {true, ""}; } }; } // namespace validator
整個演演算法實現相當輕量,規則定義不到200行,演演算法實現(也即規則解析)不到200行。使用方法也非常簡便,只需要在業務proto中import匯入validator.proto即可以使用規則定義,然後在業務介面程式碼中include<validator_util.h>即可使用規則校驗工具類對介面引數做自動校驗, 以後介面引數校驗只需要下面幾行就行了(終於不用再寫一大堆if_else了)如下:
以上就是C++ Protobuf實現介面引數自動校驗詳解的詳細內容,更多關於C++ Protobuf介面引數校驗的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45