最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++?Protobuf實現(xiàn)接口參數(shù)自動校驗詳解

 更新時間:2023年04月20日 11:27:52   作者:愛吃肉的魚  
用C++做業(yè)務(wù)發(fā)開的同學(xué)是否還在不厭其煩的編寫大量if-else模塊來做接口參數(shù)校驗?zāi)??今天,我們就模擬Java里面通過注解實現(xiàn)參數(shù)校驗的方式來針對C++?protobuf接口實現(xiàn)一個更加方便、快捷的參數(shù)校驗自動工具,希望對大家有所幫助

1、背景

用C++做業(yè)務(wù)發(fā)開的同學(xué)是否還在不厭其煩的編寫大量if-else模塊來做接口參數(shù)校驗?zāi)兀慨斀涌谧侄螖?shù)量多大幾十個,這樣的參數(shù)校驗代碼都能多達上百行,甚至超過了接口業(yè)務(wù)邏輯的代碼體量,而且隨著業(yè)務(wù)迭代,接口增加了新的字段,又不得不再加幾個if-else,對于有Java、python等開發(fā)經(jīng)歷的同學(xué),對這種原始的參數(shù)校驗方法必定是嗤之以鼻。今天,我們就模擬Java里面通過注解實現(xiàn)參數(shù)校驗的方式來針對C++ protobuf接口實現(xiàn)一個更加方便、快捷的參數(shù)校驗自動工具。

2、方案簡介

實現(xiàn)基本思路主要用到兩個核心技術(shù)點:protobuf字段屬性擴展和反射機制。

首先針對常用的協(xié)議字段數(shù)據(jù)類型(int32、int64、uint32、uint64、float、double、string、array、enum)定義了一套最常用的字段校驗規(guī)則,如下表:

每個校驗規(guī)則的protobuf定義如下:

// int32類型校驗規(guī)則
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類型校驗規(guī)則
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類型校驗規(guī)則
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類型校驗規(guī)則
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類型校驗規(guī)則
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類型校驗規(guī)則
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類型校驗規(guī)則
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類型校驗規(guī)則
message EnumRule {
    repeated int32 in = 1;
}

// array(數(shù)組)類型校驗規(guī)則
message ArrayRule {
    bool not_empty = 1;
    oneof min_len_rule {
        uint32 min_len = 2;
    }
    oneof max_len_rule {
        uint32 max_len = 3;
    }
}

注意:校驗規(guī)則中一些字段通過oneof關(guān)鍵字包裝了一層,主要是因為protobuf3中全部字段都默認是optional的,即即使不顯示設(shè)置其值,protobuf也會給它一個默認值,如數(shù)值類型的一般默認值就是0,這樣當某個規(guī)則的值(如lt)為0的時候,我們無法確定是沒有設(shè)置值還是就是設(shè)置的0,加了oneof后可以通過oneof字段的xxx_case方法來判斷對應(yīng)值是否有人為設(shè)定。

上述規(guī)則被劃分為4大類:數(shù)值類規(guī)則(Int32Rule、Int64Rule、UInt32Rule、UInt64Rule、FloatRule、DoubleRule)、字符串類規(guī)則(StringRule)、枚舉類規(guī)則(EnumRule)、數(shù)組類規(guī)則(ArrayRule), 每一類后續(xù)都會有一個對應(yīng)的校驗器(參數(shù)校驗算法)。

然后,拓展protobuf字段屬性(google.protobuf.FieldOptions),將字段校驗規(guī)則拓展為字段屬性之一。如下圖:擴展字段屬性名為Rule, 其類型為ValidateRules,其具體校驗規(guī)則通過oneof關(guān)鍵字限定至多為上述9種校驗規(guī)則之一(針對某一個字段,其類型唯一,從而其校驗規(guī)則也是確定的)。

// 校驗規(guī)則(oneof取上述字段類型校驗規(guī)則之一)
message ValidateRules {
    oneof rule {
        /* 基本類型規(guī)則 */
        Int32Rule int32  = 1;
        Int64Rule int64  = 2;
        UInt32Rule uint32  = 3;
        UInt64Rule uint64  = 4;
        FloatRule float = 5;
        DoubleRule double = 6;
        StringRule string = 7;


        /* 復(fù)雜類型規(guī)則 */
        EnumRule enum = 8;
        ArrayRule array = 9;
    }
}

// 拓展默認字段屬性, 將ValidateRules設(shè)置為字段屬性
extend google.protobuf.FieldOptions {
    ValidateRules Rule = 10000;
}

上述校驗規(guī)則和字段屬性擴展定義在validator.proto文件中,使用時通過import導(dǎo)入該proto文件便可以使用上述擴展字段屬性用于定義字段,如:

說明: 上述接口定義中,通過擴展字段屬性validator.Rule(其內(nèi)容為上述定義9中類型校驗規(guī)則之一)限制了用戶年齡age字段值必須小于等于(lte)150;名字name字段不能為空且長度不能大于32;手機號字段phone不能為空且必須滿足指定的手機號正則表達式規(guī)則;郵件字段允許為空(默認)但如果有傳入值的話則必須滿足對應(yīng)郵件正則表達式規(guī)則;others數(shù)組字段不允許為空,且長度不小于2。

有了上述接口字段定義后,需要校驗的字段都已經(jīng)帶上了validator.Rule屬性,其中已包含了對應(yīng)字段的校驗規(guī)則,接下來需要實現(xiàn)一個參數(shù)自動校驗算法, 基本思路就是通過反射逐個獲取待校驗Message結(jié)構(gòu)體中各個字段值及其字段屬性中校驗規(guī)則validator.Rule,然后逐一匹配字段值是否滿足每一項規(guī)則定義,不滿足則返回FALSE;對于嵌套結(jié)構(gòu)體類型則做遞歸校驗,算法流程及實現(xiàn)如下:

#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兩個字段生成的字段名會加個后綴_(其他字段沒有), 為了在宏里面統(tǒng)一處理加了下面兩個定義 */
typedef float float_;
typedef double double_;

/**
 * 數(shù)值校驗器(適用于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;                                                                                      \
    }

/**
 * 數(shù)組校驗器(array)
 * 支持數(shù)組非空校驗、最短(最長)長度校驗以及Message結(jié)構(gòu)體元素遞歸校驗
*/
#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()};                                                              \
        }                                                                                          \
    }                                                                                              \
                                                                                                   \
    /* 如果數(shù)組元素是Message結(jié)構(gòu)體類型,遞歸校驗每個元素 */                   \
    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;                                                                     \
            }                                                                                      \
        }                                                                                          \
    }

/**
 * 結(jié)構(gòu)體校驗器(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()) {
                // 數(shù)組類型校驗
                ArrayValidator();
            } else {
                // 非數(shù)組類型,直接調(diào)用對應(yīng)類型校驗器
                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

3、 使用

整個算法實現(xiàn)相當輕量,規(guī)則定義不到200行,算法實現(xiàn)(也即規(guī)則解析)不到200行。使用方法也非常簡便,只需要在業(yè)務(wù)proto中import導(dǎo)入validator.proto即可以使用規(guī)則定義,然后在業(yè)務(wù)接口代碼中include<validator_util.h>即可使用規(guī)則校驗工具類對接口參數(shù)做自動校驗, 以后接口參數(shù)校驗只需要下面幾行就行了(終于不用再寫一大堆if_else了)如下:

4、測試

以上就是C++ Protobuf實現(xiàn)接口參數(shù)自動校驗詳解的詳細內(nèi)容,更多關(guān)于C++ Protobuf接口參數(shù)校驗的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++實現(xiàn)字符串和整數(shù)的相互轉(zhuǎn)換

    C++實現(xiàn)字符串和整數(shù)的相互轉(zhuǎn)換

    這篇文章主要為大家詳細介紹了C++實現(xiàn)字符串和整數(shù)的相互轉(zhuǎn)換的方法,文中的示例代碼講解詳細,對我們學(xué)習(xí)C++有一定的幫助,需要的可以參考一下
    2023-01-01
  • C語言零基礎(chǔ)徹底掌握預(yù)處理下篇

    C語言零基礎(chǔ)徹底掌握預(yù)處理下篇

    在C語言的程序中包括各種以符號#開頭的編譯指令,這些指令稱為預(yù)處理命令。預(yù)處理命令屬于C語言編譯器,而不是C語言的組成部分,通過預(yù)處理命令可擴展C語言程序設(shè)計的環(huán)境
    2022-08-08
  • C語言細致講解線程同步的集中方式

    C語言細致講解線程同步的集中方式

    多線程中的線程同步可以使用,CreateThread,CreateMutex 互斥鎖實現(xiàn)線程同步,通過臨界區(qū)實現(xiàn)線程同步,Semaphore 基于信號實現(xiàn)線程同步,CreateEvent 事件對象的同步,以及線程函數(shù)傳遞單一參數(shù)與多個參數(shù)的實現(xiàn)方式
    2022-05-05
  • Qt之ui在程序中的使用-多繼承法介紹

    Qt之ui在程序中的使用-多繼承法介紹

    本文將介紹Qt之ui在程序中的使用-多繼承法,需要的朋友可以參考
    2012-11-11
  • C語言實現(xiàn)歌手大獎賽計分程序

    C語言實現(xiàn)歌手大獎賽計分程序

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)歌手大獎賽計分程序,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • C語言中的指針新手初階指南

    C語言中的指針新手初階指南

    指針是C語言的靈魂,精華之所在,指針強大而危險,用得好是一大利器,用得不好是一大潛在危害,下面這篇文章主要給大家介紹了C語言中指針的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2021-10-10
  • C語言實現(xiàn)設(shè)備管理系統(tǒng)

    C語言實現(xiàn)設(shè)備管理系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)設(shè)備管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • c++ using定義類型別名的具體使用

    c++ using定義類型別名的具體使用

    本文主要介紹了c++ using定義類型別名的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • VSCode 搭建 Arm 遠程調(diào)試環(huán)境的步驟詳解

    VSCode 搭建 Arm 遠程調(diào)試環(huán)境的步驟詳解

    這篇文章主要介紹了VSCode 搭建 Arm 遠程調(diào)試環(huán)境的步驟詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • C++11中移動構(gòu)造函數(shù)案例代碼

    C++11中移動構(gòu)造函數(shù)案例代碼

    C++11 標準中為了滿足用戶使用左值初始化同類對象時也通過移動構(gòu)造函數(shù)完成的需求,新引入了 std::move() 函數(shù),它可以將左值強制轉(zhuǎn)換成對應(yīng)的右值,由此便可以使用移動構(gòu)造函數(shù),對C++11移動構(gòu)造函數(shù)相關(guān)知識感興趣的朋友一起看看吧
    2023-01-01

最新評論

三明市| 汉寿县| 武平县| 伊吾县| 兴安盟| 犍为县| 德格县| 阿瓦提县| 革吉县| 乌鲁木齐市| 舒城县| 曲阜市| 禹州市| 平罗县| 鹤庆县| 裕民县| 清水河县| 柳河县| 富川| 青冈县| 任丘市| 札达县| 宜章县| 萍乡市| 中阳县| 盈江县| 古丈县| 雅安市| 连城县| 保亭| 南澳县| 苍南县| 永靖县| 三门峡市| 昌图县| 屯门区| 舞阳县| 和田县| 辉县市| 乐平市| 水城县|