欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

1、背景

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

2、方案簡(jiǎn)介

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

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

每個(gè)校驗(yàn)規(guī)則的protobuf定義如下:

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

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

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

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

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

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


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

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

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

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

有了上述接口字段定義后,需要校驗(yàn)的字段都已經(jīng)帶上了validator.Rule屬性,其中已包含了對(duì)應(yīng)字段的校驗(yàn)規(guī)則,接下來(lái)需要實(shí)現(xiàn)一個(gè)參數(shù)自動(dòng)校驗(yàn)算法, 基本思路就是通過(guò)反射逐個(gè)獲取待校驗(yàn)Message結(jié)構(gòu)體中各個(gè)字段值及其字段屬性中校驗(yàn)規(guī)則validator.Rule,然后逐一匹配字段值是否滿足每一項(xiàng)規(guī)則定義,不滿足則返回FALSE;對(duì)于嵌套結(jié)構(gòu)體類(lèi)型則做遞歸校驗(yàn),算法流程及實(shí)現(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對(duì)ValidateRules中float和double兩個(gè)字段生成的字段名會(huì)加個(gè)后綴_(其他字段沒(méi)有), 為了在宏里面統(tǒng)一處理加了下面兩個(gè)定義 */
typedef float float_;
typedef double double_;

/**
 * 數(shù)值校驗(yàn)器(適用于int32、int64、uint32、uint64、float、double)
 * 支持大于、大于等于、小于、小于等于、in、not_in校驗(yàn)
*/
#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;                                                                                     \
    }

/**
 * 字符串校驗(yàn)器(string)
 * 支持字符串非空校驗(yàn)、最短(最長(zhǎng))長(zhǎng)度校驗(yàn)、正則匹配校驗(yàn)
*/
#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;                                                                                     \
    }

/**
 * 枚舉校驗(yàn)器(enum)
 * 僅支持in校驗(yàn)
*/
#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ù)組校驗(yàn)器(array)
 * 支持?jǐn)?shù)組非空校驗(yàn)、最短(最長(zhǎng))長(zhǎng)度校驗(yàn)以及Message結(jié)構(gòu)體元素遞歸校驗(yàn)
*/
#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)體類(lèi)型,遞歸校驗(yàn)每個(gè)元素 */                   \
    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)體校驗(yàn)器(Message)
 * (遞歸校驗(yàn))
*/
#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ù)組類(lèi)型校驗(yàn)
                ArrayValidator();
            } else {
                // 非數(shù)組類(lèi)型,直接調(diào)用對(duì)應(yīng)類(lèi)型校驗(yàn)器
                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、 使用

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

4、測(cè)試

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

相關(guān)文章

  • C語(yǔ)言函數(shù)棧幀詳解

    C語(yǔ)言函數(shù)棧幀詳解

    下面小編就為大家?guī)?lái)一篇淺談C語(yǔ)言函數(shù)調(diào)用參數(shù)壓棧的相關(guān)問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2021-10-10
  • 10個(gè)步驟Opencv輕松檢測(cè)出圖片中條形碼

    10個(gè)步驟Opencv輕松檢測(cè)出圖片中條形碼

    這篇文章主要為大家詳細(xì)介紹了Opencv輕松檢測(cè)出圖片中條形碼的10個(gè)步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C語(yǔ)言實(shí)現(xiàn)九大排序算法的實(shí)例代碼

    C語(yǔ)言實(shí)現(xiàn)九大排序算法的實(shí)例代碼

    這篇文章主要給大家介紹了關(guān)于C語(yǔ)言實(shí)現(xiàn)九大排序算法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Qt實(shí)現(xiàn)帶字?jǐn)?shù)限制的文字輸入框

    Qt實(shí)現(xiàn)帶字?jǐn)?shù)限制的文字輸入框

    這篇文章介紹了Qt實(shí)現(xiàn)帶字?jǐn)?shù)限制文字輸入框的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • C語(yǔ)言解字符串逆序和單向鏈表逆序問(wèn)題的代碼示例

    C語(yǔ)言解字符串逆序和單向鏈表逆序問(wèn)題的代碼示例

    這篇文章主要介紹了C語(yǔ)言解字符串逆序和單向鏈表逆序問(wèn)題的代碼示例,求逆序也是考研和面試中的基礎(chǔ)算法題類(lèi)型,需要的朋友可以參考下
    2016-06-06
  • C字符串操作函數(shù)的實(shí)現(xiàn)詳細(xì)解析

    C字符串操作函數(shù)的實(shí)現(xiàn)詳細(xì)解析

    以下是對(duì)C語(yǔ)言中字符串操作函數(shù)的實(shí)現(xiàn)進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下
    2013-08-08
  • C++解決合并兩個(gè)排序的鏈表問(wèn)題

    C++解決合并兩個(gè)排序的鏈表問(wèn)題

    本文主要介紹了通過(guò)C++解決合并兩個(gè)排序的鏈表并使新鏈表中的節(jié)點(diǎn)仍然是遞增排序的。文中代碼講解詳細(xì),有需要的朋友可以參考一下
    2021-12-12
  • C語(yǔ)言手寫(xiě)集合List的示例代碼

    C語(yǔ)言手寫(xiě)集合List的示例代碼

    數(shù)組長(zhǎng)度是固定的,那么在很多時(shí)候我們并不知道到底有多少數(shù)據(jù)需要存儲(chǔ),這時(shí)候我么就需要一個(gè)可變長(zhǎng)度的數(shù)組來(lái)進(jìn)行存儲(chǔ),在C語(yǔ)言中需要我們自己進(jìn)行定義,我們稱(chēng)為集合。本文將用C語(yǔ)言實(shí)現(xiàn)手寫(xiě)集合,需要的可以參考一下
    2022-08-08
  • C語(yǔ)言實(shí)現(xiàn)代碼雨效果

    C語(yǔ)言實(shí)現(xiàn)代碼雨效果

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)代碼雨效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C++連接mysql數(shù)據(jù)庫(kù)并讀取數(shù)據(jù)的具體步驟

    C++連接mysql數(shù)據(jù)庫(kù)并讀取數(shù)據(jù)的具體步驟

    在實(shí)際開(kāi)發(fā)中我們經(jīng)常需要對(duì)數(shù)據(jù)庫(kù)進(jìn)行訪問(wèn),針對(duì)不同類(lèi)型的數(shù)據(jù)庫(kù)(如MySQL、sqLite、Access、Excel等),如果采用不同的方法進(jìn)行連接,會(huì)把我們搞崩潰,下面這篇文章主要給大家介紹了關(guān)于C++連接mysql數(shù)據(jù)庫(kù)并讀取數(shù)據(jù)的具體步驟,需要的朋友可以參考下
    2023-04-04

最新評(píng)論