当前位置: 首页 > article >正文

C++ Protobuf实现接口参数自动校验详解

用C做业务发开的同学是否还在不厌其烦的编写大量if-else模块来做接口参数校验呢当接口字段数量多大几十个这样的参数校验代码都能多达上百行甚至超过了接口业务逻辑的代码体量而且随着业务迭代接口增加了新的字段又不得不再加几个if-else对于有Java、python等开发经历的同学对这种原始的参数校验方法必定是嗤之以鼻。今天我们就模拟Java里面通过注解实现参数校验的方式来针对C protobuf接口实现一个更加方便、快捷的参数校验自动工具。2、方案简介实现基本思路主要用到两个核心技术点protobuf字段属性扩展和反射机制。首先针对常用的协议字段数据类型int32、int64、uint32、uint64、float、double、string、array、enum定义了一套最常用的字段校验规则如下表每个校验规则的protobuf定义如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135// 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 {floatlt 1;}oneof lte_rule {floatlte 2;}oneof gt_rule {floatgt 3;}oneof gte_rule {floatgte 4;}repeatedfloatin 5;repeatedfloatnot_in 6;}// double类型校验规则message DoubleRule {oneof lt_rule {doublelt 1;}oneof lte_rule {doublelte 2;}oneof gt_rule {doublegt 3;}oneof gte_rule {doublegte 4;}repeateddoublein 5;repeateddoublenot_in 6;}// string类型校验规则message StringRule {boolnot_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 {boolnot_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种校验规则之一(针对某一个字段其类型唯一从而其校验规则也是确定的)。1234567891011121314151617181920212223// 校验规则(oneof取上述字段类型校验规则之一)message ValidateRules {oneof rule {/* 基本类型规则 */Int32Rule int32 1;Int64Rule int64 2;UInt32Rule uint32 3;UInt64Rule uint64 4;FloatRulefloat 5;DoubleRuledouble 6;StringRule string 7;/* 复杂类型规则 */EnumRuleenum 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对于嵌套结构体类型则做递归校验算法流程及实现如下123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185#pragma once#include google/protobuf/message.h#include butil/logging.h#include regex#include algorithm#include sstream#include proto/validator.pb.hnamespacevalidator {usingnamespacegoogle::protobuf;/** 不知道为什么protobuf对ValidateRules中float和double两个字段生成的字段名会加个后缀_(其他字段没有), 为了在宏里面统一处理加了下面两个定义 */typedeffloatfloat_;typedefdoubledouble_;/*** 数值校验器(适用于int32、int64、uint32、uint64、float、double)* 支持大于、大于等于、小于、小于等于、in、not_in校验*/#define NumericalValidator(pb_cpptype, method_type, value_type) \casegoogle::protobuf::FieldDescriptor::CPPTYPE_##pb_cpptype: { \if(validate_rules.has_##value_type()) { \constmethod_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) \casegoogle::protobuf::FieldDescriptor::CPPTYPE_##pb_cpptype: { \if(validate_rules.has_##value_type()) { \constmethod_type##Rule rule validate_rules.value_type(); \constvalue_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) \casegoogle::protobuf::FieldDescriptor::CPPTYPE_##pb_cpptype: { \if(validate_rules.has_##value_type()) { \constmethod_type##Rule rule validate_rules.value_type(); \intvalue 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()) { \constArrayRule 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) { \constMessage sub_message reflection-GetRepeatedMessage(message, field, i); \ValidateResult result Validate(sub_message); \if(!result.is_valid) { \returnresult; \} \} \}/*** 结构体校验器(Message)* (递归校验)*/#define MessageValidator() \casegoogle::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { \constMessage sub_message reflection-GetMessage(message, field); \ValidateResult result Validate(sub_message); \if(!result.is_valid) { \returnresult; \} \break; \}classValidatorUtil {public:structValidateResult {boolis_valid;std::string msg;};staticValidateResult Validate(constMessage message) {constDescriptor* descriptor message.GetDescriptor();constReflection* reflection message.GetReflection();for(inti 0; i descriptor-field_count(); i) {constFieldDescriptor* field descriptor-field(i);FieldDescriptor::CppType field_type field-cpp_type();constValidateRules 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 validator3、 使用整个算法实现相当轻量规则定义不到200行算法实现也即规则解析不到200行。使用方法也非常简便只需要在业务proto中import导入validator.proto即可以使用规则定义然后在业务接口代码中includevalidator_util.h即可使用规则校验工具类对接口参数做自动校验 以后接口参数校验只需要下面几行就行了终于不用再写一大堆if_else了如下4、测试以上就是C Protobuf实现接口参数自动校验详解的详细内容

相关文章:

C++ Protobuf实现接口参数自动校验详解

用C做业务发开的同学是否还在不厌其烦的编写大量if-else模块来做接口参数校验呢?当接口字段数量多大几十个,这样的参数校验代码都能多达上百行,甚至超过了接口业务逻辑的代码体量,而且随着业务迭代,接口增加了新的字段…...

mysql如何快速判断两个数据库结构差异_使用mysqldiff工具.txt

动画系统必须用模板参数控制类型&#xff0c;支持Animation<vec4>和Animation<quat>共享插值逻辑与生命周期管理&#xff0c;要求类型提供static lerp或特化基础路径&#xff0c;播放状态与采样解耦&#xff0c;关键帧用连续内存存储&#xff0c;组合靠BlendAnimat…...

智能访客系统(线上访客预约、线下访客机),提供从访客预约、身份核验、现场登记到联动(闸机、门禁、梯控、车牌识别停车场等出入口)通行的一站式智能化管理解决方案,实现访客全程可追溯、通行更便捷、管理更高效

智能访客系统技术方案第一章 系统概述1.1 项目背景随着智慧楼宇、智慧园区、智慧社区建设的深入推进&#xff0c;传统的访客管理模式已难以满足现代安全管理与高效通行的双重需求。传统人工登记方式存在效率低、数据易丢失、安全隐患大等问题。本方案基于智能访客系统&#xff…...

c++ Protobuf解决数据传输瓶颈面试精讲

1. 什么是 Protobuf?Protobuf&#xff08;Protocol Buffers&#xff09; 是一种轻量级的数据序列化协议&#xff0c;由 Google 开发。它可以用于结构化数据的序列化和反序列化&#xff0c;使得数据在不同系统之间进行传输和存储更加高效。与 XML 和 JSON 等常见的数据交换格式…...

UEFI Setup界面开发避坑指南:grayoutif、suppressif条件控制与varstore变量存储的实战解析

UEFI Setup界面开发避坑指南&#xff1a;条件控制与变量存储的实战解析 在UEFI固件开发中&#xff0c;Setup界面作为用户与系统交互的重要桥梁&#xff0c;其开发质量直接影响用户体验和系统稳定性。本文将深入探讨如何避免UEFI Setup界面开发中的常见陷阱&#xff0c;特别是条…...

软件估算-代码行估算法

代码行技术是比较简单的定量估算软件规模的方法。这种方法根据以往开发的类似产品的经验和历史数据&#xff0c;估算实现一个功能需求的源程序行数。当有以往开发类似项目的历史数据可供参考时&#xff0c;用此方法估算出的历史数据还是比较准确的&#xff0c;把实现每个功能需…...

别再只背课文了!用《新概念英语》Lesson 39的‘鲁莽司机’故事,带你理解软件开发的‘风险无视’陷阱

从《新概念英语》Lesson 39看技术决策中的风险盲区&#xff1a;当工程师变成"鲁莽司机" Bruce的故事在技术圈里每天都在重演——那个对油表报警视若无睹、对路面裂缝毫不在意的司机&#xff0c;像极了我们身边那些对系统告警置之不理、对技术债视而不见的开发团队。当…...

软件规模-功能点分析法

功能点分析法是在20世纪70年代中期由IBM委托 Allan Albrecht 工程师和他的同事为解决代码行度量法所产生的问题和局限性而研究发布&#xff0c;发表于1979年&#xff0c;随后被国际功能点用户协会继承。该方法基于应用软件的外部&#xff0c;内部特性以及软件性能进行一系列间接…...

别再只盯着协议了!手把手教你用示波器实测MIPI D-PHY的HS/LP模式切换波形

示波器实战&#xff1a;深度解析MIPI D-PHY模式切换的波形捕获技巧 当你在调试一块搭载MIPI接口的摄像头模组时&#xff0c;是否遇到过图像传输不稳定、画面闪烁甚至完全无信号的问题&#xff1f;这些现象往往与D-PHY在高速模式(HS)和低功耗模式(LP)之间的切换时序异常有关。本…...

别再只用散点图了!用matplotlib的plt.contourf()给你的机器学习模型画个‘势力范围’

用等高线图解锁机器学习模型的决策奥秘 在机器学习的世界里&#xff0c;模型往往被视为一个"黑箱"——输入数据&#xff0c;输出结果&#xff0c;中间发生了什么却难以直观理解。这种不透明性让很多从业者感到困扰&#xff0c;尤其是在向非技术背景的利益相关者解释模…...

Claude Opus 4.7 来了,但普通人真正缺的不是新模型,是一个会选模型的入口

这不是一篇“谁最强”的测评。模型越更越快&#xff0c;真正稀缺的反而是比较能力。最近几天&#xff0c;如果你一直在看 AI&#xff0c;很容易被一种热闹裹挟&#xff1a;Anthropic 在推 Claude Opus 4.7&#xff0c;OpenAI 连着更新 Agents SDK 和 Codex&#xff0c;Google 也…...

从数据清洗到模型部署:一个完整VGG16乳腺超声分类项目的避坑指南与优化思考

从数据清洗到模型部署&#xff1a;VGG16乳腺超声分类全流程实战精要 医学影像分析正经历着从传统人工判读到AI辅助诊断的范式转移。当我们聚焦于乳腺癌筛查这一关键领域时&#xff0c;超声图像分类任务因其非侵入性和普及性优势&#xff0c;成为计算机视觉技术落地医疗的重要突…...

从“面包重量”到“用户停留时长”:产品经理/运营必懂的CDF与PDF实战解读

从“面包重量”到“用户停留时长”&#xff1a;产品经理/运营必懂的CDF与PDF实战解读 想象你走进一家面包店&#xff0c;发现每个面包的重量都有些微差异——有的重152克&#xff0c;有的148克&#xff0c;几乎没有恰好150克的。这种连续变量的特性&#xff0c;恰恰是理解用户行…...

从理论到实践:一维与二维水污染扩散模型的在线模拟与代码实现

1. 水污染扩散模型的基础原理 第一次接触水污染扩散模型时&#xff0c;我也被那些专业术语搞得一头雾水。后来在实际项目中反复应用才发现&#xff0c;理解这些原理其实就像理解咖啡在杯子里扩散一样简单。想象一下&#xff0c;当你把一勺糖倒入咖啡中&#xff0c;糖分是如何逐…...

【AGI决策能力评估权威框架】:2024全球7大实验室实测数据+3层可验证指标体系首次公开

第一章&#xff1a;AGI的规划与决策能力评估 2026奇点智能技术大会(https://ml-summit.org) AGI的规划与决策能力并非单一维度指标&#xff0c;而是融合目标分解、状态建模、多步推理、反事实评估与实时适应的复合认知过程。当前主流评估框架已从静态任务准确率转向动态环境下…...

2026奇点大会唯一未删减技术圆桌实录(含OpenAI、Ethereum基金会、中科院自动化所三方闭门共识):AGI主权归属的区块链终局方案

第一章&#xff1a;2026奇点智能技术大会&#xff1a;AGI与区块链 2026奇点智能技术大会(https://ml-summit.org) AGI系统与去中心化身份的协同演进 在2026奇点智能技术大会上&#xff0c;核心议题之一是通用人工智能&#xff08;AGI&#xff09;如何依托区块链构建可信自主代…...

Rust的闭包捕获语义分析与内存管理在长期存活闭包中的最佳实践

Rust的闭包捕获语义分析与内存管理在长期存活闭包中的最佳实践 Rust以其独特的所有权系统和内存安全特性著称&#xff0c;而闭包作为函数式编程的核心概念&#xff0c;在Rust中同样扮演着重要角色。闭包的捕获语义和内存管理在长期存活的场景下&#xff08;例如异步任务或事件…...

CMU Subword Modeling | 15 Orthography versus IPA: Why We Need Both

本文解读 CMU “Subword Modeling” (Spring 2026) 第15讲&#xff1a;Orthography versus IPA: Why We Need Both。 这节课回答一个 NLP 从业者常见的疑问&#xff1a;「普通字母表不就能表示声音了吗&#xff1f;为什么还需要 IPA&#xff1f;」答案是文字到语音的映射在三个…...

DNS解析故障排查实战:从“网络不通“到定位根因的完整方法论

DNS解析故障排查实战&#xff1a;从"网络不通"到定位根因的完整方法论 为什么 DNS 故障总是最难发现的那一类 网络故障里&#xff0c;DNS 问题有一个特殊的迷惑性&#xff1a;它让你以为是别的问题。 用户反馈"网络断了"——其实是 DNS 解析失败&#x…...

用 QClaw 打造 AI 小说家,30 万字签约全流程复盘

文章目录前言第一步&#xff1a;下载安装 QClaw第二步&#xff1a;新建自定义 Agent第三步&#xff1a;精心设计小说家人设第四步&#xff1a;对 AI 小说家进行专项培训第五步&#xff1a;明确平台调性&#xff0c;设计世界观第六步&#xff1a;正式派发创作任务总结前言 最近…...

别再花钱买NAS了!用HFS+Nat123在Windows上5分钟搭建个人网盘(附中文汉化)

零成本打造个人云存储&#xff1a;WindowsHFSNat123实战指南 手里有台闲置的Windows电脑&#xff1f;别让它吃灰了。今天我要分享的这套方案&#xff0c;能让你用不到5分钟时间&#xff0c;把旧电脑变成随时可访问的私人云盘。相比动辄上千元的NAS设备&#xff0c;这套方案不仅…...

从零到一:三维重建技术全流程解析

从零到一&#xff1a;三维重建技术全流程解析 三维重建技术正在重塑我们与数字世界的交互方式。想象一下&#xff0c;仅凭几张普通照片就能在计算机中还原出物体的三维形态——这项看似科幻的能力&#xff0c;如今已成为医疗影像、工业检测、文化遗产保护等领域的常规操作。不…...

用PyTorch3D玩转3D艺术:手把手教你生成渐变小牛和旋转植物GIF

用PyTorch3D玩转3D艺术&#xff1a;手把手教你生成渐变小牛和旋转植物GIF 在数字艺术与创意编程的交汇处&#xff0c;PyTorch3D正成为技术爱好者手中的魔法棒。当传统3D建模软件需要复杂操作时&#xff0c;这个基于PyTorch的库让代码生成炫酷视觉效果变得像搭积木一样简单。本文…...

用PyTorch的F.cosine_similarity实现文本/向量两两相似度计算:以推荐系统为例

PyTorch向量相似度计算的工程实践&#xff1a;从原理到推荐系统实战 在推荐系统和自然语言处理领域&#xff0c;向量相似度计算是最基础也最频繁的操作之一。想象一下这样的场景&#xff1a;你的推荐系统需要实时为百万级用户计算他们可能感兴趣的物品&#xff0c;而每个用户和…...

<climits>

简介这个头文件比较特殊&#xff0c;不包含复杂的函数&#xff0c;而是定义了一系列宏常量&#xff0c;用于描述当前编译平台下各种整型数据类型的取值范围&#xff08;最小值和最大值&#xff09;UCHAR_MAX //(255U): 无符号字符型的最大值。U 表示无符号常量SCHAR_MIN //-12…...

文档批量加水印这个工具帮我解决了文档版权追踪的问题

在日常工作中&#xff0c;文档的版权保护一直是个头疼的问题。特别是对于需要向外部分发的文件&#xff0c;怎么证明"这份文档是从我这儿出去的"&#xff0c;怎么在泄露发生时能够追踪到源头&#xff1f;这篇文章介绍一个能批量给文档添加不可见水印的工具&#xff0…...

告别几十个ECU!手把手拆解车身域控制器(附SPC58NH/S32G方案选型指南)

车身域控制器实战指南&#xff1a;从传统ECU到集中式架构的硬件整合 车身电子系统正经历一场从分散到集中的革命。想象一下&#xff0c;一辆现代汽车内部可能分布着上百个独立工作的电子控制单元(ECU)&#xff0c;它们各自为政&#xff0c;通过复杂的线束网络相互连接。这不仅增…...

AO3镜像站:为创意自由搭建的桥梁

AO3镜像站&#xff1a;为创意自由搭建的桥梁 【免费下载链接】AO3-Mirror-Site 项目地址: https://gitcode.com/gh_mirrors/ao/AO3-Mirror-Site 在数字时代的创作海洋中&#xff0c;Archive of Our Own&#xff08;AO3&#xff09;如同一个巨大的创意港湾&#xff0c;汇…...

来自学习的第二天

今天是我学习编程的第二天&#xff0c;希望能够学好&#xff0c;能够学得多&#xff0c;以后当个大佬&#xff0c;我相信我一定可以的...

平衡二叉树的奥秘:AVLTree高效实现解析

平衡二叉树&#xff08;AVLTree&#xff09;平衡二叉树&#xff08;AVLTree&#xff09;是一种自平衡二叉搜索树&#xff0c;由 Adelson-Velsky 和 Landis 于 1962 年提出。它通过维护每个节点的平衡因子&#xff08;定义为左子树高度减去右子树高度&#xff09;来确保树的高度…...