proto3语法详解

news2024/11/23 19:38:05

proto3语法详解

    • 字段规则
    • 消息类型的定义与使⽤
      • 定义
      • 使用
    • enum类型定义与使用
      • 定义规则
      • 定义时注意事项
    • Any类型
      • Any类型介绍
      • Any类型使用
    • oneof类型
      • oneof类型的介绍
      • oneof类型的使用
    • map类型
      • map类型的介绍
      • map类型的使用
    • 默认值
    • 更新消息
      • 更新规则
      • 保留字段reserved
    • 未知字段
      • 未知字段获取方法
    • 前后兼容
    • 选项option
      • 选项分类
      • 常用选项列举
      • 设置自定义选项

字段规则

消息的字段可以⽤下⾯⼏种规则来修饰:

  • singular :消息中可以包含该字段零次或⼀次(不超过⼀次),proto3 语法中,字段默认使⽤该规则

  • repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组

表⽰⼀个联系⼈有多个号码,可将其设置为 repeated,写法如下:

message People{
	repeated string phone_numbers = 1; //表示string类型的数组
}

消息类型的定义与使⽤

定义

在单个 .proto ⽂件中可以定义多个消息体,且⽀持定义嵌套类型的消息(任意多层)。每个消息体中的字段编号可以重复

//嵌套写法
message PeopleInfo{
	string name = 1; //姓名
	int32 age = 2; //年龄
	message Phone{
		string number = 1; //电话号码
	}
}

//非嵌套写法
message Phone {
	string number = 1;
}
message PeopleInfo {
    string name = 1;
    int32 age = 2;
}

使用

①消息类型可作为字段类型使用

message PeopleInfo{
	string name = 1; //姓名
	int32 age = 2; //年龄
	message Phone{
		string number = 1; //电话号码
	}
	repeated Phone phone = 3; //表示string类型的数组
}

②可导入其他.proto文件的消息并使用

  • 在phone.proto文件中定义Phone消息

    syntax = "proto3";
    package phone;
    
    message Phone{
    	string number = 1;
    }
    
  • 在contacts.proto文件中的People使用Phone消息

    syntac = "proto3";
    package contacts;
    import "phone.proto"  // 使⽤ import 将 phone.proto ⽂件导⼊进来
    
    message PeopleInfo{
    	string name = 1;
    	int32 age = 2;
    	
    	// 引⼊的⽂件声明了package,使⽤消息时,需要⽤ ‘命名空间.消息类型’ 格式
    	repeated phone.Phone phone = 3;
    }
    //通讯录
    message Contacts{
    	repeated PeopleInfo contacts = 1;
    }
    

    补充:在 proto3 ⽂件中可以导⼊ proto2 消息类型并使⽤它们,反之亦然

编译contacts.proto文件后得到的头文件内容:

// 新增了 PeopleInfo_Phone 类
class PeopleInfo_Phone final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
    using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
    void CopyFrom(const PeopleInfo_Phone& from);
    using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
    void MergeFrom( const PeopleInfo_Phone& from) {
    	PeopleInfo_Phone::MergeImpl(*this, from);
}
	static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
		return "PeopleInfo.Phone";
}
    // string number = 1;
    void clear_number();  //number字段数值清空
    const std::string& number() const;  //获取number字段的值
    template <typename ArgT0 = const std::string&, typename... ArgT>
    void set_number(ArgT0&& arg0, ArgT... args); //设置number字段
    std::string* mutable_number(); 
    PROTOBUF_NODISCARD std::string* release_number();
    void set_allocated_number(std::string* number);
};

// 更新了 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
    using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
    void CopyFrom(const PeopleInfo& from);
    using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
    void MergeFrom( const PeopleInfo& from) {
    	PeopleInfo::MergeImpl(*this, from);
    }
    static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
    	return "PeopleInfo";
}
    typedef PeopleInfo_Phone Phone;
    // repeated .PeopleInfo.Phone phone = 3;
    int phone_size() const;  //获取元素个数
    void clear_phone();		//清空
    ::PeopleInfo_Phone* mutable_phone(int index);
    ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::PeopleInfo_Phone >*
    mutable_phone();
    const ::PeopleInfo_Phone& phone(int index) const; //获取phone数组
    ::PeopleInfo_Phone* add_phone(); //增加phone数组中插入元素
    const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::PeopleInfo_Phone >&
    phone() const;
};


// 新增了 Contacts 类
class Contacts final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
    using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
    void CopyFrom(const Contacts& from);
    using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
    void MergeFrom( const Contacts& from) {
    	Contacts::MergeImpl(*this, from);
    }
    static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
    	return "Contacts";
}
    // repeated .PeopleInfo contacts = 1;
    int contacts_size() const;  //获取元素个数
    void clear_contacts();		//清空该数组
    ::PeopleInfo* mutable_contacts(int index);
    ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::PeopleInfo >*
    mutable_contacts();
    const ::PeopleInfo& contacts(int index) const; //获取该数组
    ::PeopleInfo* add_contacts(); //向数组中添加元素
    const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::PeopleInfo >&
    contacts() const;
};

上述的例⼦中:

  • 每个字段都有⼀个 clear_ ⽅法,可以将字段重新设置回 empty 状态
  • 每个字段都有设置和获取的⽅法, 获取⽅法的⽅法名称与⼩写字段名称完全相同。但如果是消息类型的字段,其设置⽅法为 mutable_ ⽅法,返回值为消息类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改
  • 对于使⽤ repeated 修饰的字段,也就是数组类型,pb 为我们提供了 add_ ⽅法来新增⼀个值,并且提供了 _size ⽅法来判断数组存放元素的个数

enum类型定义与使用

定义规则

语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为

  • 枚举类型名称:使⽤驼峰命名法,⾸字⺟⼤写。 例如: MyEnum

  • 常量值名称: 全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0

定义一个名为PhoneType的枚举类型:

enum PhoneType{
	MP = 0;  //移动电话
	TEL = 1; //固定电话
}

要注意枚举类型的定义有以下⼏种规则:

  1. 0值常量必须存在,且要作为第一个元素,这是为了与proto2的语义兼容,第一个元素作为默认值,且值为0
  2. 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)
  3. 枚举的常量值在32位整数的范围内,但因赋值无效因而不建议使用(与编码规则有关)

定义时注意事项

将两个 ‘具有相同枚举值名称’ 的枚举类型放在单个 .proto ⽂件下测试时,编译后会报错:某某某常量已经被定义!所以这⾥要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名
  • 单个 .proto ⽂件下,最外层枚举类型和嵌套枚举类型,不算同级
  • 多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都未声明 package,每个 proto ⽂件中的枚举类型都在最外层,算同级
  • 多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都声明了 package,不算同级
// ---------------------- 情况1:同级枚举类型包含相同枚举值名称--------------------
enum PhoneType {
    MP = 0; // 移动电话
    TEL = 1; // 固定电话
}
enum PhoneTypeCopy {
	MP = 0; // 移动电话 // 编译后报错:MP 已经定义
}

// ---------------------- 情况2:不同级枚举类型包含相同枚举值名称-------------------
enum PhoneTypeCopy {
	MP = 0; // 移动电话 // ⽤法正确
}
message Phone {
    string number = 1; // 电话号码
    enum PhoneType {
        MP = 0; // 移动电话
        TEL = 1; // 固定电话
    }
}

//---------------------- 情况3:多文件下都未声明package-------------------
//phone.proto文件
enum PhoneType{
	MP = 0;
	TEL = 0;
}
//phone2.proto文件
import "phone.proto"
enum PhoneTypeCopy{
	MP = 0; // 编译后报错:MP 已经定义
}

// ---------------------- 情况4:多⽂件下都声明了package--------------------
// phone1.proto
import "phone1.proto"
package phone1;
enum PhoneType {
    MP = 0; // 移动电话 // ⽤法正确
    TEL = 1; // 固定电话
}
// phone2.proto
package phone2;
enum PhoneTypeCopy {
	MP = 0; // 移动电话
}

phone.proto文件:

syntax = "proto3";
package phone;

message{
	message Phone {
    	string number = 1; // 电话号码
    	enum PhoneType {
            MP = 0; // 移动电话
            TEL = 1; // 固定电话
    	}
    	PhoneType type = 2; // 类型
    }
}

编译phone.proto文件生成的头文件内容:

// 新⽣成的 PeopleInfo_Phone_PhoneType 枚举类
enum PeopleInfo_Phone_PhoneType : int {
    PeopleInfo_Phone_PhoneType_MP = 0, //MP字段类型
    PeopleInfo_Phone_PhoneType_TEL = 1, //TEL字段类型
    PeopleInfo_Phone_PhoneType_PeopleInfo_Phone_PhoneType_INT_MIN_SENTINEL_DO_NOT_U
    SE_ = std::numeric_limits<int32_t>::min(),
    PeopleInfo_Phone_PhoneType_PeopleInfo_Phone_PhoneType_INT_MAX_SENTINEL_DO_NOT_U
    SE_ = std::numeric_limits<int32_t>::max()
}

 PeopleInfo_Phone类中新增的函数
class PeopleInfo_Phone final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
    typedef PeopleInfo_Phone_PhoneType PhoneType;
    static inline bool PhoneType_IsValid(int value) {   //校验枚举值是否有效的⽅法 _IsValid
    	return PeopleInfo_Phone_PhoneType_IsValid(value);
    }
    template<typename T>
    //获取枚举值名称的⽅法
    static inline const std::string& PhoneType_Name(T enum_t_value) {...}
    static inline bool PhoneType_Parse(
    ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, PhoneType* value) {...}
    // .contacts.PeopleInfo.Phone.PhoneType type = 2;
    void clear_type();  //清空字段的⽅法
    ::contacts::PeopleInfo_Phone_PhoneType type() const; //获取字段的⽅法
    void set_type(::contacts::PeopleInfo_Phone_PhoneType value); //设置字段的⽅法
};

上述的代码中:

  • 对于在.proto⽂件中定义的枚举类型,编译⽣成的代码中会含有与之对应的枚举类型、校验枚举值是否有效的⽅法 _IsValid、以及获取枚举值名称的⽅法 _Name
  • 对于使⽤了枚举类型的字段,包含设置和获取字段的⽅法,已经清空字段的⽅法clear_

Any类型

Any类型介绍

字段还可以声明为 Any 类型,可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。Any 类型的字段也⽤ repeated 来修饰,Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include ⽬录下查找所有google 已经定义好的 .proto ⽂件

Any.proto文件:

syntax = "proto3"
import "google/protobuf/any.proto";

// 地址
message Address{
    string home_address = 1; // 家庭地址
    string unit_address = 2; // 单位地址
}
// 联系⼈
message PeopleInfo {
    string name = 1; // 姓名
    int32 age = 2; // 年龄
    message Phone {
        string number = 1; // 电话号码
        enum PhoneType {
            MP = 0; // 移动电话
            TEL = 1; // 固定电话
        }
    PhoneType type = 2; // 类型
    }
    repeated Phone phone = 3; // 电话
    google.protobuf.Any data = 4;
}

// 通讯录
message Contacts {
    repeated PeopleInfo contacts = 1;
}

编译Any.proto文件后头文件中的内容:

// 新⽣成的 Address 类
class Address final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
    using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
    void CopyFrom(const Address& from);
    using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
    void MergeFrom( const Address& from) {
    	Address::MergeImpl(*this, from);
	}
    // string home_address = 1;
    void clear_home_address();
    const std::string& home_address() const;
    template <typename ArgT0 = const std::string&, typename... ArgT>
    void set_home_address(ArgT0&& arg0, ArgT... args);
    std::string* mutable_home_address();
    PROTOBUF_NODISCARD std::string* release_home_address();
    void set_allocated_home_address(std::string* home_address);
    // string unit_address = 2;
    void clear_unit_address();
    const std::string& unit_address() const;
    template <typename ArgT0 = const std::string&, typename... ArgT>
    void set_unit_address(ArgT0&& arg0, ArgT... args);
    std::string* mutable_unit_address();
    PROTOBUF_NODISCARD std::string* release_unit_address();
    void set_allocated_unit_address(std::string* unit_address);
}

//更新的PeopleInfo类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
	//.goole.protobuf.Any data = 4;
	bool has_data() const;
	void clear_data();
	const ::PROTOBUF_NAMESPACE_ID::Any& data() const;
	PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_data();
	::PROTOBUF_NAMESPACE_ID::Any* mutable_data();
	void set_allocated_data(::PROTOBUF_NAMESPACE_ID::Any* data);
};

上述的代码中,对于 Any 类型字段:

  • 设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法可以使⽤ mutable_ ⽅法,返回值为Any类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改

之前讲过,我们可以在 Any 字段中存储任意消息类型,这就要涉及到任意消息类型 和 Any 类型的互转。这部分代码就在 Google为我们写好的头⽂件 any.pb.h 中。对 any.pb.h 部分代码展⽰:

class PROTOBUF_EXPORT Any final : public ::PROTOBUF_NAMESPACE_ID::Message {
    bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message) {
    	...
    }
    bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const {
    	...
    }
    template<typename T> bool Is() const {
        return _impl_._any_metadata_.Is<T>();
    }
}

函数说明:

  • 使⽤ PackFrom() ⽅法可以将任意消息类型转为 Any 类型
  • 使⽤ UnpackTo() ⽅法可以将 Any 类型转回之前设置的任意消息类型
  • 使⽤Is()⽅法可以⽤来判断存放的消息类型是否为 typename T

Any类型使用

//定义Address类型
Address address;
cout << "请输⼊联系⼈家庭地址: ";
string home_address;
getline(cin, home_address);
address.set_home_address(home_address);
cout << "请输⼊联系⼈单位地址: ";
string unit_address;
getline(cin, unit_address);
address.set_unit_address(unit_address);

//从 people_info_ptr 中获取 data 字段
//使用 PackFrom 方法将一个 Address 消息实例打包到 data 字段中
google::protobuf::Any * data = people_info_ptr->mutable_data();
data->PackFrom(address);

//如果data字段被设置,且该字段为Address类型
if (people.has_data() && people.data().Is<Address>()) {
    Address address;
    //将data中存储的内容转成Address类型,并存入address变量中
    people.data().UnpackTo(&address);
    //打印address变量中的值
	if (!address.home_address().empty()) {
		cout << "家庭地址:" << address.home_address() << endl;
    }
	if (!address.unit_address().empty()) {
		cout << "单位地址:" << address.unit_address() << endl;
	}
}

oneof类型

oneof类型的介绍

如果消息中有很多可选字段, 并且将来同时只有⼀个字段会被设置, 那么就可以使⽤ oneof 加强这个⾏为,也能有节约内存的效果

oneof 字段定义的格式为: oneof 字段名 { 字段1; 字段2; … }

oneof other_contact{
	string qq = 5;  //qq
	string wechat = 6; //微信
}

注意点:

  • 可选字段中的字段编号,不能与⾮可选字段的编号冲突
  • 不能在 oneof 中使⽤ repeated 字段
  • 将来在设置 oneof 字段中值时,如果将 oneof 中的字段设置多个,那么只会保留最后⼀次设置的成员,之前设置的 oneof 成员会⾃动清除

Oneof.proto文件:

message PeopleInfo{
	oneof other_countact{
		string qq = 5;
		string weiwin = 6;
	}
}

编译Oneof.proto文件后得到的头文件:

// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
    enum OtherContactCase {
        kQq = 5,
        kWeixin = 6,
        OTHER_CONTACT_NOT_SET = 0, //表示未设置的状态
};
    // string qq = 5;
    bool has_qq() const;  //判断有没有被设置
    void clear_qq();    //清除
    const std::string& qq() const;  //获取该字段的值
    template <typename ArgT0 = const std::string&, typename... ArgT>
    void set_qq(ArgT0&& arg0, ArgT... args);
    std::string* mutable_qq();  //获取该字段的指针
    PROTOBUF_NODISCARD std::string* release_qq();
    void set_allocated_qq(std::string* qq); 
    
    // string weixin = 6;
    bool has_weixin() const; //判断有没有被设置
    void clear_weixin();	//清除
    const std::string& weixin() const;	//获取该字段的值
    template <typename ArgT0 = const std::string&, typename... ArgT>
    void set_weixin(ArgT0&& arg0, ArgT... args);
    std::string* mutable_weixin();	//获取该字段的指针
    PROTOBUF_NODISCARD std::string* release_weixin();
    void set_allocated_weixin(std::string* weixin);
    void clear_other_contact();
    OtherContactCase other_contact_case() const; //获取当前设置了哪个字段
}

上述的代码中,对于 oneof 字段:

  • 会将 oneof 中的多个字段定义为⼀个枚举类型
  • 设置和获取:对 oneof 内的字段进⾏常规的设置和获取即可,但要注意只能设置⼀个。如果设置多个,那么只会保留最后⼀次设置的成员
  • 清空oneof字段:clear_ ⽅法
  • 获取当前设置了哪个字段:_case ⽅法

oneof类型的使用

cout << "选择添加⼀个其他联系⽅式 (1、qq号 2、微信号) : ";
int other_contact;
cin >> other_contact;
cin.ignore(256, '\n'); //将输入的\n去除

if (1 == other_contact) {
    cout << "请输⼊qq号: ";
    string qq;
    getline(cin, qq);
    people_info_ptr->set_qq(qq);
} else if (2 == other_contact) {
    cout << "请输⼊微信号: ";
    string weixin;
	getline(cin, weixin);
	people_info_ptr->set_weixin(weixin);
} else {
	cout << "⾮法选择,该项设置失败!" << endl;
}

//获取被设置的字段
switch (people.other_contact_case()) {
    case PeopleInfo::OtherContactCase::kQq:
        cout << "qq号: " << people.qq() << endl;
        break;
    case PeopleInfo::OtherContactCase::kWeixin:
        cout << "微信号: " << people.weixin() << endl;
        break;
    case PeopleInfo::OtherContactCase::OTHER_CONTACT_NOT_SET:
    	break;
}

map类型

map类型的介绍

语法⽀持创建⼀个关联映射字段,也就是可以使⽤ map 类型去声明字段类型,格式为:

map<key_type, value_type> map_field = N

要注意的是:

  • key_type 是除了 float 和 bytes 类型以外的任意标量类型。 value_type 可以是任意类型
  • map 字段不可以⽤ repeated 修饰
  • map 中存⼊的元素是⽆序的

Map.proto文件内容:

message PeopleInfo{
	map<string,string> remark = 7; //备注
}

编译Map.proto文件后得到的头文件内容:

//更新的PeopleInfo类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
    // map<string, string> remark = 7;
    int remark_size() const; //获取元素个数
    void clear_remark();
    const ::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >&
        remark() const;  //获取该Map类型对象
    ::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >*
        mutable_remark(); //该方法帮助我们开辟好空间,返回Map类型指针
};

上述的代码中,对于Map类型的字段:

  • 清空map: clear_ remark⽅法
  • 设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法为 mutable_ remark⽅法,返回值为Map类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改

map类型的使用

//向map类型变量中插入数据
for(int i = 1; ; i++) {
    cout << "请输⼊备注" << i << "标题 (只输⼊回⻋完成备注新增): ";
    string remark_key;
    getline(cin, remark_key);
    if (remark_key.empty()) {
    	break;
	}
    cout << "请输⼊备注" << i << "内容: ";
    string remark_value;
    getline(cin, remark_value);
    people_info_ptr->mutable_remark()->insert({remark_key, remark_value});
}

//读取map类型变量中的数据
if (people.remark_size()) {
	cout << "备注信息: " << endl;
}
for (auto it = people.remark().cbegin(); it != people.remark().cend();++it) {
	cout << " " << it->first << ": " << it->second << endl;
}

默认值

反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:

类型默认值
字符串空字符串
字节空字节
布尔值false
数值类型0
枚举默认值是第⼀个定义的枚举值, 必须为 0
消息字段它的取值是依赖于语⾔
设置了 repeated 的字段空的( 通常是相应语⾔的⼀个空列表 )

对于 消息字段oneof字段 any字段 ,C++ 和 Java 语⾔中都有 has_ ⽅法来检测当前字段是否被设置

更新消息

更新规则

如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可:

  • 禁⽌修改任何已有字段的字段编号
  • 若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段
  • int32, uint32, int64, uint64 和 bool 是完全兼容的。可以从这些类型中的⼀个改为另⼀个,⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与 C++ ⼀致的处理⽅案(例如,若将 64 位整数当做 32 位进⾏读取,它将被截断为 32 位)
  • sint32 和 sint64 相互兼容但不与其他的整型兼容
  • string 和 bytes 在合法 UTF-8 字节前提下也是兼容的
  • bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的
  • fixed32 与 sfixed32 兼容, fixed64 与 sfixed64兼容
  • enum与int32,uint32,int64和uint64兼容(注意若值不匹配会被截断),但要注意(当反序列化消息时会根据语言采用不同的处理方案:例如,未识别的proto3枚举类型会被保存在消息中,但是当消息反序列化时如何表示是依赖于编程语言的,整形字段总是会保持其信息
  • oneof类型:
    • 将⼀个单独的值更改为 新 oneof 类型成员之⼀是安全和⼆进制兼容的
    • 若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可⾏的
    • 将任何字段移⼊已存在的 oneof 类型是不安全的

保留字段reserved

如果通过 删除 或 注释掉 字段来更新消息类型,未来的⽤⼾在添加新字段时,有可能会使⽤以前已经存在,但已经被删除或注释掉的字段编号。将来使⽤该 .proto 的旧版本时的程序会引发很多问题:数据损坏、隐私错误等等

确保不会发⽣这种情况的⼀种⽅法是:使⽤ reserved 将指定字段的编号或名称设置为保留项 。当我们再使⽤这些编号或名称时,protocol buffer 的编译器将会警告这些编号或名称不可⽤。举个例⼦:

message Message {
    // 设置保留项
    reserved 100, 101, 200 to 299;
    reserved "field3", "field4";
    // 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
    // reserved 102, "field5";
    // 设置保留项之后,下⾯代码会告警
    int32 field1 = 100; //告警:Field 'field1' uses reserved number 100
    int32 field2 = 101; //告警:Field 'field2' uses reserved number 101
    int32 field3 = 102; //告警:Field name 'field3' is reserved
    int32 field4 = 103; //告警:Field name 'field4' is reserved
}

未知字段

解析结构良好的 protocol buffer 已序列化数据中的未识别字段的表⽰⽅式。例如,当旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段

本来,proto3 在解析消息时总是会丢弃未知字段,但在 3.5 版本中重新引⼊了对未知字段的保留机制。所以在 3.5 或更⾼版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果中

未知字段获取方法

①MessageLite类的简单介绍

MessageLite 从名字看是轻量级的 message,仅仅提供序列化、反序列化功能。 类定义在 google 提供的 message_lite.h 中

②Message类的简单介绍

  • 我们⾃定义的message类,都是继承⾃Message ,Message 最重要的两个接⼝GetDescriptorGetReflection,可以获取该类型对应的Descriptor对象指针 和 Reflection 对象指针
  • 类定义在 google 提供的 message.h 中
//google::protobuf::Message 部分代码展⽰
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;

③了解Message和MessageLite类的关系图:
在这里插入图片描述
④Descriptor 类介绍

  • Descriptor:是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto⽂件内容等
  • 类定义在 google 提供的 descriptor.h 中
// 部分代码展⽰
class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
    string& name () const
    int field_count() const;
    const FieldDescriptor* field(int index) const;
    const FieldDescriptor* FindFieldByNumber(int number) const;
    const FieldDescriptor* FindFieldByName(const std::string& name) const;
    const FieldDescriptor* FindFieldByLowercaseName(
    const std::string& lowercase_name) const;
    const FieldDescriptor* FindFieldByCamelcaseName(
    const std::string& camelcase_name) const;
    int enum_type_count() const;
    const EnumDescriptor* enum_type(int index) const;
    const EnumDescriptor* FindEnumTypeByName(const std::string& name) const;
    const EnumValueDescriptor* FindEnumValueByName(const std::string& name)const;
}

⑤Reflection 类介绍

  • Reflection接⼝类,主要提供了动态读写消息字段的接口,对消息对象的⾃动读写主要通过该类完成

  • 提供方法来动态访问/修改message中的字段,对每种类型,Reflection都提供了⼀个单独的接口用于读写字段对应的值

    • 针对所有不同的field类型 FieldDescriptor::TYPE_* ,需要使用不同的 Get*()/Set*()/Add*() 接⼝
    • repeated类型需要使用GetRepeated*()/SetRepeated*() 接口,不可以和非repeated类型接⼝混用
    • message对象只可以被由它⾃⾝的 reflection(message.GetReflection()) 来操作
  • 类中还包含了访问/修改未知字段的⽅法

  • 类定义在 google 提供的 message.h 中

// 部分代码展⽰
class PROTOBUF_EXPORT Reflection final {
const UnknownFieldSet& GetUnknownFields(const Message& message) const;
UnknownFieldSet* MutableUnknownFields(Message* message) const;
bool HasField(const Message& message, const FieldDescriptor* field) const;
int FieldSize(const Message& message, const FieldDescriptor* field) const;
void ClearField(Message* message, const FieldDescriptor* field) const;
bool HasOneof(const Message& message,const OneofDescriptor* oneof_descriptor) const;
void ClearOneof(Message* message,const OneofDescriptor* oneof_descriptor) const;
const FieldDescriptor* GetOneofFieldDescriptor(
const Message& message, const OneofDescriptor* oneof_descriptor) const;
// Singular field getters ------------------------------------------
// These get the value of a non-repeated field. They return the default
// value for fields that aren't set.
int32_t GetInt32(const Message& message, const FieldDescriptor* field) const;
int64_t GetInt64(const Message& message, const FieldDescriptor* field) const;
uint32_t GetUInt32(const Message& message,const FieldDescriptor* field) const;
uint64_t GetUInt64(const Message& message,const FieldDescriptor* field) const;
float GetFloat(const Message& message, const FieldDescriptor* field) const;
double GetDouble(const Message& message, const FieldDescriptor* field) const;
bool GetBool(const Message& message, const FieldDescriptor* field) const;
std::string GetString(const Message& message,
const FieldDescriptor* field) const;
const EnumValueDescriptor* GetEnum(const Message& message,
const FieldDescriptor* field) const;
int GetEnumValue(const Message& message, const FieldDescriptor* field) const;
const Message& GetMessage(const Message& message,
const FieldDescriptor* field,MessageFactory* factory = nullptr) const;
// Singular field mutators -----------------------------------------
// These mutate the value of a non-repeated field.
void SetInt32(Message* message, const FieldDescriptor* field,int32_t value) const;
void SetInt64(Message* message, const FieldDescriptor* field,int64_t value) const;
void SetUInt32(Message* message, const FieldDescriptor* field,uint32_t value) const;
void SetUInt64(Message* message, const FieldDescriptor* field,uint64_t value) const;
void SetFloat(Message* message, const FieldDescriptor* field,float value) const;
void SetDouble(Message* message, const FieldDescriptor* field,double value) const;
void SetBool(Message* message, const FieldDescriptor* field,
bool value) const;
void SetString(Message* message, const FieldDescriptor* field,
std::string value) const;
void SetEnum(Message* message, const FieldDescriptor* field,
const EnumValueDescriptor* value) const;
void SetEnumValue(Message* message, const FieldDescriptor* field,
int value) const;
Message* MutableMessage(Message* message, const FieldDescriptor* field,
MessageFactory* factory = nullptr) const;
PROTOBUF_NODISCARD Message* ReleaseMessage(
Message* message, const FieldDescriptor* field,
MessageFactory* factory = nullptr) const;
// Repeated field getters ------------------------------------------
// These get the value of one element of a repeated field.
int32_t GetRepeatedInt32(const Message& message, const FieldDescriptor*field,int index) const;
int64_t GetRepeatedInt64(const Message& message, const FieldDescriptor*field,int index) const;
uint32_t GetRepeatedUInt32(const Message& message,
const FieldDescriptor* field, int index) const;
uint64_t GetRepeatedUInt64(const Message& message,
const FieldDescriptor* field, int index) const;
float GetRepeatedFloat(const Message& message, const FieldDescriptor* field,int index) const;
double GetRepeatedDouble(const Message& message, const FieldDescriptor*field,int index) const;
bool GetRepeatedBool(const Message& message, const FieldDescriptor* field,int index) const;
std::string GetRepeatedString(const Message& message,
const FieldDescriptor* field, int index) const;
const EnumValueDescriptor* GetRepeatedEnum(const Message& message,
const FieldDescriptor* field,int index) const;
int GetRepeatedEnumValue(const Message& message, const FieldDescriptor*field,int index) const;
const Message& GetRepeatedMessage(const Message& message,
const FieldDescriptor* field,int index) const;
const std::string& GetRepeatedStringReference(const Message& message,const FieldDescriptor* field,
int index,std::string* scratch) const;
/ Repeated field mutators -----------------------------------------
// These mutate the value of one element of a repeated field.
void SetRepeatedInt32(Message* message, const FieldDescriptor* field,int index, int32_t value) const;
void SetRepeatedInt64(Message* message, const FieldDescriptor* field,int index, int64_t value) const;
void SetRepeatedUInt32(Message* message, const FieldDescriptor* field,int index, uint32_t value) const;
void SetRepeatedUInt64(Message* message, const FieldDescriptor* field,int index, uint64_t value) const;
void SetRepeatedFloat(Message* message, const FieldDescriptor* field,int index, float value) const;
void SetRepeatedDouble(Message* message, const FieldDescriptor* field,int index, double value) const;
void SetRepeatedBool(Message* message, const FieldDescriptor* field,int index, bool value) const;
void SetRepeatedString(Message* message, const FieldDescriptor* field,int index, std::string value) const;
void SetRepeatedEnum(Message* message, const FieldDescriptor* field,int index, const EnumValueDescriptor* value) const;
void SetRepeatedEnumValue(Message* message, const FieldDescriptor* field,int index, int value) const;
Message* MutableRepeatedMessage(Message* message,const FieldDescriptor* field,int index) const;
// Repeated field adders -------------------------------------------
// These add an element to a repeated field.
void AddInt32(Message* message, const FieldDescriptor* field,int32_t value) const;
void AddInt64(Message* message, const FieldDescriptor* field,int64_t value) const;
void AddUInt32(Message* message, const FieldDescriptor* field,uint32_t value) const;
void AddUInt64(Message* message, const FieldDescriptor* field,uint64_t value) const;
void AddFloat(Message* message, const FieldDescriptor* field,float value) const;
void AddDouble(Message* message, const FieldDescriptor* field,double value) const;
void AddBool(Message* message, const FieldDescriptor* field,bool value) const;
void AddString(Message* message, const FieldDescriptor* field,std::string value) const;
void AddEnum(Message* message, const FieldDescriptor* field,const EnumValueDescriptor* value) const;
void AddEnumValue(Message* message, const FieldDescriptor* field,int value) const;
Message* AddMessage(Message* message, const FieldDescriptor* field,MessageFactory* factory = nullptr) const;
const FieldDescriptor* FindKnownExtensionByName(const std::string& name) const;
const FieldDescriptor* FindKnownExtensionByNumber(int number) const;
bool SupportsUnknownEnumValues() const;
};

⑥UnknownFieldSet 类介绍

  • UnknownFieldSet 包含在分析消息时遇到但未由其类型定义的所有字段
  • 若要将 UnknownFieldSet 附加到任何消息,请调⽤ Reflection::GetUnknownFields()
  • 类定义在 unknown_field_set.h 中
class PROTOBUF_EXPORT UnknownFieldSet {
    inline void Clear();
    void ClearAndFreeMemory();
    inline bool empty() const;
    inline int field_count() const;
    inline const UnknownField& field(int index) const;
    inline UnknownField* mutable_field(int index);
    // Adding fields ---------------------------------------------------
    void AddVarint(int number, uint64_t value);
    void AddFixed32(int number, uint32_t value);
    void AddFixed64(int number, uint64_t value);
    void AddLengthDelimited(int number, const std::string& value);
    std::string* AddLengthDelimited(int number);
    UnknownFieldSet* AddGroup(int number);
    // Parsing helpers -------------------------------------------------
    // These work exactly like the similarly-named methods of Message.
    bool MergeFromCodedStream(io::CodedInputStream* input);
    bool ParseFromCodedStream(io::CodedInputStream* input);
    bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);
    bool ParseFromArray(const void* data, int size);
    inline bool ParseFromString(const std::string& data) {
    	return ParseFromArray(data.data(), static_cast<int>(data.size()));
    }
    // Serialization.
    bool SerializeToString(std::string* output) const;
    bool SerializeToCodedStream(io::CodedOutputStream* output) const;
    static const UnknownFieldSet& default_instance();
}

⑦UnknownField 类介绍

  • 表⽰未知字段集中的⼀个字段
  • 类定义在 unknown_field_set.h 中
class PROTOBUF_EXPORT UnknownField {
public:
    enum Type {
        TYPE_VARINT,
        TYPE_FIXED32,
        TYPE_FIXED64,
        TYPE_LENGTH_DELIMITED,
        TYPE_GROUP
	};
    inline int number() const;
    inline Type type() const;
    // Accessors -------------------------------------------------------
    // Each method works only for UnknownFields of the corresponding type.
    inline uint64_t varint() const;
    inline uint32_t fixed32() const;
    inline uint64_t fixed64() const;
    inline const std::string& length_delimited() const;
    inline const UnknownFieldSet& group() const;
    inline void set_varint(uint64_t value);
    inline void set_fixed32(uint32_t value);
    inline void set_fixed64(uint64_t value);
    inline void set_length_delimited(const std::string& value);
    inline std::string* mutable_length_delimited();
    inline UnknownFieldSet* mutable_group();
}

前后兼容

为了叙述⽅便,把增加了“⽣⽇”属性的 service 称为“新模块”;未做变动的 client 称为 “⽼模块”。

  • 向前兼容:老模块能够正确识别新模块⽣成或发出的协议。这时新增加的“⽣⽇”属性会被当作未知字段(pb 3.5版本及之后)。

  • 向后兼容:新模块也能够正确识别⽼模块⽣成或发出的协议

前后兼容的作⽤:当我们维护⼀个很庞⼤的分布式系统时,由于你⽆法同时 升级所有 模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼容

选项option

选项分类

选项的完整列表在google/protobuf/descriptor.proto中定义。部分代码:

syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
...

由此可⻅,选项分为 ⽂件级、消息级、字段级 等等, 但并没有⼀种选项能作⽤于所有的类型

常用选项列举

optimize_for : 该选项为⽂件选项,可以设置 protoc 编译器的优化级别,分别为 SPEED 、CODE_SIZE 、 LITE_RUNTIME 受该选项影响,设置不同的优化级别,编译 .proto ⽂件后⽣成的代码内容不同

  • SPEED: protoc 编译器将⽣成的代码是⾼度优化的,代码运⾏效率⾼,但是由此⽣成的代码编译后会占⽤更多的空间,SPEED 是默认选项
  • CODE_SIZE: proto 编译器将⽣成最少的类,会占⽤更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运⾏效率较低。这种⽅式适合⽤在包含⼤量的.proto⽂件,但并不盲⽬追求速度的应⽤中
  • LITE_RUNTIME: ⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供 encoding+序列化 功能,所以我们在链接 BP 库时仅需链接libprotobuf-lite,⽽⾮libprotobuf。这种模式通常⽤于资源有限的平台,例如移动⼿机平台中
option optimize_for = LITE_RUNTIME;

allow_alias : 允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。举个例⼦:

enum PhoneType {
    option allow_alias = true;
    MP = 0;
    TEL = 1;
    LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}

设置自定义选项

可参考官方文档:Protocol Buffers官方文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2246206.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32】在 STM32 USB 设备库添加新的设备类

说实话&#xff0c;我非常想吐槽 STM32 的 USB device library&#xff0c;总感觉很混乱。 USB Device library architecture 根据架构图&#xff1a; Adding a custom class 如果你想添加新的设备类&#xff0c;必须修改的文件有 usbd_desc.cusbd_conf.cusb_device.c 需要…

怎么编译OpenWrt镜像?-基于Widora开发板

1.准备相应的环境&#xff0c;我使用的环境是VMware16ubuntu20.04&#xff0c;如图1所示安装编译所需的依赖包&#xff1b; sudo apt-get install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch python3 python2.7 unzip zlib1g-…

神经网络问题之一:梯度消失(Vanishing Gradient)

梯度消失&#xff08;Vanishing Gradient&#xff09;问题是深度神经网络训练中的一个关键问题&#xff0c;它主要发生在反向传播过程中&#xff0c;导致靠近输入层的权重更新变得非常缓慢甚至几乎停滞&#xff0c;严重影响网络的训练效果和性能。 图1 在深度神经网络中容易出现…

JavaWeb之综合案例

前言 这一节讲一个案例 1. 环境搭建 然后就是把这些数据全部用到sql语句中执行 2.查询所有-后台&前台 我们先写后台代码 2.1 后台 2.2 Dao BrandMapper&#xff1a; 注意因为数据库里面的名称是下划线分割的&#xff0c;我们类里面是驼峰的&#xff0c;所以要映射 …

PLC与PLC跨网段通讯的几种方法:厂区组网实践

PLC通常通过以太网或其他工业网络协议&#xff08;如PROFINET、Modbus TCP等&#xff09;进行通信。当PLC位于不同的网段时&#xff0c;它们不能直接通信&#xff0c;需要特殊的配置或设备来实现通信&#xff0c;不同网段的PLC通讯变得尤为重要。 随着工业网络的发展和工业4.0概…

原生openGauss与Oracle数据库函数兼容性对比验证测试

———————————————— 一、测试目的 近期在进行调研去O,为此研究了下原生openGauss和Oracle数据库兼容性&#xff0c;特整理测试了Oracle常用函数和openGauss数据库的对比测试。 二、创建DUAL虚拟表 openGauss 数据库不提供DUAL虚拟表&#xff0c;需要通过如下方…

创建HTTPS网站

每天&#xff0c;我们都会听到网络上发生身份盗窃和数据侵权的案例&#xff0c;这导致用户对自己访问的网站更加怀疑。他们开始更加了解自己将个人信息放在哪里以及信任哪些类型的网站。了解如何使网站使用HTTPS变得比以往任何时候都更加重要。 解读缩略词&#xff1a;HTTP与HT…

Bokeh实现大规模数据可视化的最佳实践

目录 引言 一、Bokeh简介 二、安装Bokeh 三、数据准备 四、性能优化 五、创建图表 六、添加交互功能 七、应用案例 八、高级技巧 九、总结 引言 在数据科学领域,数据可视化是一个至关重要的环节。通过可视化,我们可以直观地理解数据的特征和趋势,为数据分析和决策…

数据结构-8.Java. 七大排序算法(上篇)

本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 上篇主要实现 前四种排序算法: 直接插入, 希尔, 选择, 堆排。 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 …

HARCT 2025 新增分论坛7:机器人和自动化的新趋势

会议名称&#xff1a;机电液一体化与先进机器人控制技术国际会议 会议简称&#xff1a;HARCT 2025 大会时间&#xff1a;2025年1月3日-6日 大会地点&#xff1a;中国桂林 主办单位&#xff1a;桂林航天工业学院、广西大学、桂林电子科技大学、桂林理工大学 协办单位&#…

海洋通信船舶组网工业4G路由器应用

船舶是浩瀚海洋中探索与贸易的载体&#xff0c;更是船员们生活与工作的家园。为了在广阔的水域中搭建起稳定、高效的网络桥梁&#xff0c;工业4G路由器以卓越的通信组网能力&#xff0c;为船舶组网提供网络支持。 工业4G路由器以其强大的信号发射能力&#xff0c;确保船舶内部…

《Vue零基础入门教程》第三课:起步案例

往期内容 《Vue零基础入门教程》第一课&#xff1a;Vue简介 《Vue零基础入门教程》第二课&#xff1a;搭建开发环境 做为第一个案例, 主要给大家介绍vue的最基本使用. vue使用的3步曲(重点) 引入vue.js编写页面(视图)创建App实例并挂载 1) 引入vue.js 在html的头部, 通过…

【unity小技巧】一些unity3D灯光的使用与渲染及性能优化方案

文章目录 天空盒反射配置太阳耀斑眩光烘培光照烘培光照时弹出错误&#xff0c;记得勾选模型下面的选择阴影项目配置光源模型模型shader的问题 全局光照混合光照模式混合照明模式减性照明模式Shadowmask照明模式间接烘焙照明模式 环境光遮罩灯光探针反射探针技术关闭反射探针可以…

【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录

背景 Jetbrain IDE 支持生成 Test 类&#xff0c;其中选择JUnit5 和 JUnit&#xff0c;但是感觉这不是标准的单元测试&#xff0c;因为接口命名吧。 差异对比 两者生成的单测API名称同原API&#xff0c;没加test前缀的。使用差异主要表现在&#xff1a; setUp &#xff06; …

Nuxt3:拉取项目模板失败问题解决方法

问题描述 使用官网提供的命令npx nuxilatest init <project-name> 创建Nuxt3项目时&#xff0c;遇到了拉取项目模板失败的问题&#xff0c;报错信息如下 先分析一下这行命令在做的事情&#xff0c;结合之前的经验来看&#xff0c;似乎是在尝试通过该网址返回的元数据信息…

在 Sui 区块链上创建、部署与测试自定义 move _coin合约的完整教程

系列文章目录&#x1f60a; Task1&#xff1a;hello_move&#x1f340; Task2&#xff1a;move_coin&#x1f340; Task3&#xff1a;move_nft&#x1f340; 目录 系列文章目录&#x1f60a;引言一、更新本地代码1、查看当前项目的远程仓库信息。2、将远程仓库的最新代码同步到…

三层交换机静态路由实验

1、前置知识 2、实验目的 3、实验器材&#xff1a; 3560-23PS交换机2台、主机4台、交叉线1根和直通网线4根。 4、实验规划及拓扑 实验要求&#xff1a; &#xff08;1&#xff09;在交换机A和交换机B上分别划分基于端口的VLAN&#xff1a; 交换机 VLAN 端口成员 交换机…

iOS构建版本以及Hbuilder打iOS的ipa包全流程

目录 Hbuilder打ipa包 打包之前进行应用配置 应用版本号设置 使用广告标识设置 iOS-云打包 下载并转移安装包 使用Transporter提交版本 应用简介 下载应用 账号登录 提交安装包到apple store connect 在apple开发者平台上确认 总结 本篇文章详细的介绍了使用Hbuil…

java学习-集合

为什么有集合&#xff1f; 自动扩容 数组&#xff1a;长度固定&#xff0c;可以存基本数据类型和引用数据类型 集合&#xff1a;长度可变&#xff0c;可以存引用数据类型&#xff0c;基本数据类型的话需要包装类 ArrayList public class studentTest {public static void m…

返回流类型接口的错误信息处理

返回流类型接口的错误信息处理 前言axios拦截器src/utils/request.ts对应接口 前言 返回流类型接口需要在响应成功回调里拦截&#xff0c;且该接口的status始终是200&#xff0c;尽管后端返回的code可能是非2xx&#xff0c;因此返回流类型的接口&#xff0c;其错误信息需要单独…