预留Reservation这个东西说穿了就是在系统里先占个坑告诉系统某物料、某数量、某库位后面我要拿来跑 261 或者 201。MB21 是手工加坑的标准入口可量一旦大起来——按工单批量生成备件预留、按项目号批量生成领料预留——手工敲就不现实了基本都是走 BAPI。我最近手里这个需求就是这么来的用 BAPI 批量创建 MB21 预留同时客户在 RESB 上追加了几个自定义字段项目内部编号、需求来源、月度归属要求创建预留的时候一并写进去。前半截很顺BAPI_RESERVATION_CREATE1 一调预留号回来了MB23 里也看得到后半截卡了小半天——返回表干干净净一个错误都没有可打开 MB23那几个增强字段全是空的。这篇文章就把这两件事摊开讲标准 BAPI 创建预留怎么调才不会翻车以及增强字段到底怎么才能跟着一起落库。踩过的坑、验证过的顺序、几种兜底方案我都写进去适合正在做类似接口、或者准备动手的人直接抄。1. 先把 MB21 预留的数据底座摸清楚1.1 预留头和行项目分别落在 RKPF 和 RESBMB21 一保存数据分两处落地抬头进RKPF预留号 RSNUM、工厂、参照单据等等行项目进RESB行号 RSPOS、物料、数量、单位、需求日期、库位、移动类型、批次、各类科目分配字段以及挂在 CI_RESB 里的客户自定义字段。这个判断必须在动手写代码之前做完因为它决定了你的扩展结构该用 BAPI_TE_RESB 还是 BAPI_TE_RKPF。我见过有人把行项目上的字段挂到表头扩展里传然后回来说写不进去——写不进去才是正常的扩展结构按表区分表头结构里压根没这个字段的位置。RESB 还有一点要留神它不只是预留的行项目表生产订单的组件需求依赖需求也躺在同一张表里。所以后面做数据核查、批量回填、写清理程序的时候WHERE 条件一定要带上 RSNUM 范围或者 RSART 记录类型别看到 RESB 就一锅端。我在测试系统里就吃过一次亏直接用物料号查 RESB捞出来一堆生产订单的组件行还以为是接口重复写数了。1.2 为什么我优先选 BAPI 而不是 MB21 录屏预留这一块标准给出来的 BAPI 大致有这几个BAPI_RESERVATION_CREATE老版本入口参数结构比较薄扩展支持也弱。BAPI_RESERVATION_CREATE1带扩展参数 EXTENSIONIN 的那一版新建预留我基本都用它。BAPI_RESERVATION_CHANGE改预留行项目、表头、扩展都能带着走。BAPI_RESERVATION_GETDETAIL按 RSNUM 回读明细做验证和补数全靠它。三种实现方式的对比我一般是这么权衡的方式稳定性速度增强字段可行性维护成本BAPI_RESERVATION_CREATE1高版本升级基本不受影响快单条毫秒级取决于扩展框架是否实现低MB21 录屏BDC中屏幕字段一动就挂慢还要处理屏幕流屏幕上的字段可以直接录高直接调内部函数模块低签名随时可能变快看具体函数很高录屏这条路不是不能走。它最大的诱惑是所见即所得——屏幕上能填的、包括客户自己加在屏幕上的增强字段SHDB 录一遍就能复现。但代价也很实在屏幕顺序、必输标记、甚至一个消息弹窗都可能让批量程序在半夜挂掉。而且真到了要处理屏幕增强字段的时候SHDB 往往录不到那些字段还得手写 BDCDATA 去按屏幕名和字段名拼非常脆。所以我的原则是能用 BAPI 就用 BAPIBAPI 确实覆盖不到的那一小块比如某些屏幕级校验或者增强逻辑再单独想办法补。1.3 标准 BAPI 覆盖不到的那一小块客户自定义字段BAPI 的参数结构是标准字段的映射客户追加的 ZZ 字段天生不在里面。SAP 为此留了一套扩展框架调用时把自定义结构塞进EXTENSIONIN结构是 BAPIPAREX标准代码在内部按结构名分发再把值搬到对应的数据库字段上。这套机制不难难的是不知道自己不知道。返回表全绿、消息类型是 S 或者压根没消息很容易让人以为搞定了。实际上标准代码可能连 EXTENSIONIN 都没读你传进去的那几行数据就那么静静地躺在内存里随 LUW 结束一起消失。2. 最小可用调用从入参拼装到拿回 RSNUM2.1 表头真正必填的不多但别只填工厂BAPI_RESERVATION_CREATE1 的导入参数 reservation_header 是 BAPI_RESERVATION_HEADER1。这个结构里字段不多真正意义上必须给的就是工厂PLANT。抬头文本、参照单据之类的按需。只给工厂够不够够。但有两件事你得想清楚一是如果所有行项目的工厂都一致表头工厂决定了这批预留的工厂归属行项目上再重复给一次不冲突二是有些客户会在表头挂增强字段比如项目号、批次归属月那就得走 BAPI_TE_RKPF 那条路后面会讲。2.2 行项目三件套物料、数量单位、需求日期reservation_items 里的结构是 BAPI_RESERVATION_ITEM常用的字段大概是这些ITEM行号、MATERIAL、PLANT、STGE_LOC、ENTRY_QNT、ENTRY_UOM、REQ_DATE、BATCH、ITEM_TEXT以及各科目分配字段。我踩过的一个坑是ENTRY_UOM这里必须传物料有换算关系的单位。如果传了个物料基本计量单位之外的、又没在物料主数据里维护换算关系的单位BAPI 不会给你软着陆会直接返回错误消息。批量场景下这种错误特别烦因为它是按行的几百行里混进去三五行全批就废了。另一个是行号。系统允许你不给 ITEM 让它自动编号但我的建议永远是显式给10、20、30 这么递增。原因在第四节——增强字段要靠行号去关联行项目你让系统自动编号后面就得先回读再匹配白白多一次往返。2.3 移动类型决定了哪些科目分配字段会变成必输这条经验能省你很多排查时间。移动类型不是个孤立的字段它后面牵着一串校验移动类型典型场景通常必须配套的字段261生产订单领料AUFNR订单号201成本中心领料KOSTL成本中心281项目领料PSP_PNRWBS 元素241发给订单的转储AUFNR261 特殊库存供应商寄售/订单库存领用SPEC_STOCK、SUPPLIER 或订单号如果是特殊库存SPEC_STOCK 要一起给寄售是 K订单库存是 E供应商特殊库存是 O然后配合 SUPPLIER 或者对应单据号。有配置的地方还可能要求 KNTTP科目分配类别也显式传K 是成本中心、F 是订单、P 是项目。报请输入科目分配类别这种错的时候先去查这条链别去怀疑 BAPI。2.4 RESERVATION_ITEMSX不填有时也行但别赌BAPI_RESERVATION_CREATE1 有个 reservation_itemsx 参数结构是 BAPI_RESERVATION_ITEMX。它里面是一堆标记位用来告诉系统这些字段我确实传了值请按这个值处理。创建场景下大部分字段即使不标记也能带过去所以网上很多示例压根不传 ITEMSX。但我实测下来的态度是凡是 ITEMS 里给了值的字段就在 ITEMSX 里把对应标记打上 X。多写几行代码换来的是不会在某个版本、某个补丁级别上突然静默丢字段。真遇到那种入参明明有值、返回成功、数据库是空的情况第一个要试的就是把 ITEMSX 补全。2.5 一段可以直接复制改造的调用骨架DATA: ls_header TYPE bapi_reservation_header1, lt_items TYPE STANDARD TABLE OF bapi_reservation_item, ls_items TYPE bapi_reservation_item, lt_itemsx TYPE STANDARD TABLE OF bapi_reservation_itemx, ls_itemsx TYPE bapi_reservation_itemx, lt_extin TYPE STANDARD TABLE OF bapiparex, lt_return TYPE STANDARD TABLE OF bapiret2, lv_rsnum TYPE resb-rsnum. 1) 表头 ls_header-plant 1000. 2) 行项目 CLEAR ls_items. ls_items-item 00010. ls_items-material MAT-0001. ls_items-plant 1000. ls_items-stge_loc 0001. ls_items-entry_qnt 10. ls_items-entry_uom EA. ls_items-move_type 261. ls_items-req_date sy-datum 7. ls_items-aufnr 10001234. ls_items-item_text 批量接口创建. APPEND ls_items TO lt_items. CLEAR ls_itemsx. ls_itemsx-item 00010. ls_itemsx-entry_qnt X. APPEND ls_itemsx TO lt_itemsx. 3) 调用 CALL FUNCTION BAPI_RESERVATION_CREATE1 EXPORTING reservation_header ls_header IMPORTING reservation lv_rsnum TABLES reservation_items lt_items reservation_itemsx lt_itemsx extensionin lt_extin return lt_return.两点提醒BAPI_RESERVATION_ITEM 和 BAPI_RESERVATION_ITEMX 的字段名动手前一定要在 SE11 里打开看一眼不同版本或补丁级别里字段命名会有细微差异比如移动类型有的叫 MOVE_TYPE、有的叫别的照抄网上的代码然后卡在语法检查上是真浪费时间。导出参数名同理多数版本叫 RESERVATIONSE37 里点开参数页确认一下最稳。2.6 提交与回滚BAPI_TRANSACTION_COMMIT 不是可选动作这条对新手杀伤力最大BAPI 只负责把数据准备好不会自己落库。你必须在判断完返回表之后显式提交READ TABLE lt_return TRANSPORTING NO FIELDS WITH KEY type E. IF sy-subrc 0 OR lv_rsnum IS INITIAL. CALL FUNCTION BAPI_TRANSACTION_ROLLBACK. ELSE. CALL FUNCTION BAPI_TRANSACTION_COMMIT EXPORTING wait X. ENDIF.在 SE37 里做单条测试的时候也一样——测试序列里不加 COMMIT你会看到创建成功回到 SE16N 一查什么都没有然后开始怀疑人生。还有个更隐蔽的BAPI_TRANSACTION_ROLLBACK 会回滚整个 LUW。如果你在一个 LUW 里连着调了 500 次 BAPI中间某一条失败触发了回滚前面 499 条成功的也一起没了。所以批量提交必须分包后面第六节细说。3. 增强字段怎么跟着一起写进去3.1 先分清你的字段是展示型还是存储型这一步不做后面全是白干。客户说加个字段背后可能是三种完全不同的需求存储型字段值必须落到数据库表里别的程序、报表、接口要读。这种走附加结构 扩展框架。展示型只在 MB23 或者某个自定义报表上显示值可以从别的表带出来。这种不需要改 RESB写个 BADI 或者做报表关联就行。校验型填的时候要按规则卡控不落库。这种是屏幕增强或者校验出口的活。只有第一种情况才需要走下面这套 BAPI 扩展。我见过把展示型需求做成存储型的结果 RESB 上多了一堆冗余字段每次物料主数据或者成本中心改了还得回头同步纯给自己找活。3.2 CI_RESB 附加结构追加的动作比追加的内容更要紧客户字段要以附加结构append structure的形式挂在标准表或标准结构上而不是直接改表。RESB 对应的客户包含是CI_RESBRKPF 对应CI_RKPF。这里有个上线顺序的问题我把它单独拎出来讲因为它真的会出事附加结构必须先传到目标系统再传调用 BAPI 的程序。反过来的话程序在目标系统里语法检查就会挂——它引用了一个还不存在字段。做传输的时候附加结构和程序分到不同的请求里或者至少保证同一个请求里附加结构的对象排在前面这个细节在项目里被坑过一次就够了。还有个更细的追加字段用附加结构、不要用直接往 CI_RESB 里加字段以外的方式瞎折腾。添加结构在升级、打补丁的时候相对安全直接改标准表结构一个 SPAU/SPDD 就能让你忙一整天。3.3 BAPI_TE_RESB 和 CI_RESB 到底是什么关系这是整件事里最容易凭记忆下错结论的地方我的建议是不要凭记忆打开 SE11 看一眼。操作方式是这样SE11 里打开结构 BAPI_TE_RESB看它下面是不是挂了INCLUDE CI_RESB或者同名的客户包含。分两种情况挂了那你只要往 CI_RESB 追加字段BAPI_TE_RESB 里自动就有了不用重复追加。新版本基本是这个设计SAP 的很多 BAPI_TE_xxx 都是包含同名的 CI_xxx。没挂老版本比较常见那就得在 BAPI_TE_RESB 上再追加一份同名、同类型、同长度的字段。两份定义必须严格一致差一个长度都可能被 CASTING 之后读歪。确认完这一步你才算真正知道该往哪个结构里塞值。3.4 EXTENSIONIN 的拼装CASTING 到 VALUEPART1EXTENSIONIN 是表类型 BAPIPAREX 的内表字段就两个关键的STRUCTURE结构名填BAPI_TE_RESB和VALUEPART1 ~ VALUEPART4各 CHAR 240。问题来了VALUEPART1 是字符型BAPI_TE_RESB 是个结构怎么把结构塞进字符字段业界的标准老套路是 FIELD-SYMBOL CASTINGDATA: ls_extin TYPE bapiparex. FIELD-SYMBOLS: fs_te_resb TYPE bapi_te_resb. CLEAR ls_extin. ls_extin-structure BAPI_TE_RESB. 把 VALUEPART1 那块内存看成一个 BAPI_TE_RESB ASSIGN ls_extin-valuepart1 TO fs_te_resb CASTING. fs_te_resb-reservation lv_rsnum. fs_te_resb-item 00010. fs_te_resb-zz_prjno P-2024-001. fs_te_resb-zz_src K. UNASSIGN fs_te_resb. APPEND ls_extin TO lt_extin.原理不复杂CASTING 让同一个内存区域有了两种看法你按结构写进去的字节标准代码那边按字符串读回来再按结构名反 CASTING 回去就对上了。明白这一点很多 BAPI 扩展的问题自己就能想明白。3.5 结构超过 240 字节怎么办ASSIGN ... CASTING有个硬约束目标类型不能比内存区域大。VALUEPART1 只有 240 字节如果 BAPI_TE_RESB 加上你的 ZZ 字段之后总长超过 240这个 ASSIGN 就会在运行时抛错。这时候要用 VALUEPART2、3、4四个字段连起来是 960 字节标准代码读的时候会按顺序拼回去。切片我不会手写字节循环用系统提供的工具类更省事DATA: lv_container TYPE string. DATA: ls_te_resb TYPE bapi_te_resb. CALL METHOD cl_abap_container_utilitiesfill_container_c EXPORTING im_value ls_te_resb IMPORTING ex_container lv_container. ls_extin-valuepart1 lv_container0(240). IF strlen( lv_container ) 240. ls_extin-valuepart2 lv_container240(240). ENDIF.反方向读取把 VALUEPART 拼回结构用同一个类的 read_container_c 就行。这套写法比手写 OFFSET 循环干净得多也不会因为结构长度变了就崩。顺手说一句多数项目的增强字段也就三五个240 字节足够所以 CASTING 那套更常见但一旦字段多起来或者带长文本上面这段就是救命的。3.6 表头增强要不要一起做如果 ZZ 字段挂在 CI_RKPF 上比如项目号、批次归属这种整单唯一的属性扩展结构名换成BAPI_TE_RKPF键字段一般是 RESERVATION。同一张 EXTENSIONIN 里可以混着传多个不同结构名的记录标准代码按 STRUCTURE 字段分发所以表头和行项目可以一次性带过去不用分两次调用。4. 创建时拿不到 RSNUM增强字段怎么关联到行4.1 这是个真问题不是细节行项目的扩展数据要靠预留号 行号才能定位到具体哪一行。但创建的时候预留号是系统内部编号你调用之前根本不知道。这就是为什么很多人第一次做这个需求会卡住。两条路路子一创建时直接带 EXTENSIONINTE 结构里 RESERVATION 留空或者给全零只靠 ITEM行号关联。前提是标准代码支持这种按行号匹配的写法。路子二先调 CREATE1 把预留建出来拿到 RSNUM再调 CHANGE 带上 EXTENSIONIN 把增强字段灌进去。多一次往返但逻辑清晰、可控。我的实际做法是先用 SE37 试试路子一通了就赚一次往返不通立刻转路子二不要在那死磕。因为路子一通不通完全取决于你系统里这套 BAPI 的实现跟你的代码质量没关系。4.2 路子二的完整动作第一步CREATE1 不带 EXTENSIONIN 或者带空表拿到 lv_rsnum顺手把行号记下来因为我们显式给了行号所以手上是现成的。第二步拼 BAPI_TE_RESBRESERVATION 填 lv_rsnumITEM 填行号其它 ZZ 字段填值装进 EXTENSIONIN。第三步调 CHANGECALL FUNCTION BAPI_RESERVATION_CHANGE EXPORTING reservation lv_rsnum TABLES reservation_items lt_items reservation_itemsx lt_itemsx extensionin lt_extin return lt_return.有个细节要注意CHANGE 的语义是改所以 reservation_itemsx 在这里就不是可选项了你要改哪些字段、或者只想让扩展字段生效得把对应标记打清楚否则标准代码可能认为这一行你什么都没改而直接跳过。4.3 DB 直写的兜底方案以及它的红线如果连 CHANGE 都不认你的扩展字段说白了就是这个 BAPI 没实现扩展框架还有最后一条路按 RSNUM RSPOS 直接 UPDATE RESB 里的 ZZ 字段。这条路能用但我给它画三条红线缺一条都别用必须和 BAPI 的 COMMIT 在同一个 LUW 内。也就是说BAPI 提交之前把 UPDATE 做掉让 BAPI_TRANSACTION_COMMIT 一起兜走。分两个 LUW一旦中间失败就是预留建了、字段没写的脏数据比什么都不做还麻烦。ZZ 字段绝对不能参与任何标准逻辑。如果这个字段会被 MRP、可用性检查、过账逻辑读到那你绕开标准更新链路就等着不一致吧。纯客户属性、纯记录用才可以考虑。写之前先回读确认。UPDATE 之前用 GETDETAIL 或者直接 SELECT 确认这条 RSNUMRSPOS 存在且状态正确别 UPDATE 了一堆不存在的数据还以为成功了——SQL 层面 sy-subrc 是 4 的时候很多人不看。说实话走到这条路我心里是不太舒服的能用前两条路解决就别用这条。4.4 什么时候干脆回录屏有一种情况我会主动放弃 BAPI客户字段是屏幕增强做上去的也就是在 MB21 的屏幕上有输入框而且配套了屏幕级校验逻辑值不一定直接写 RESB。这时候 BAPI 和 DB 直写都会绕开那套校验业务上是不允许的。那就只能回到 MB21 录屏。做法是用 SHDB 录一遍标准操作拿到 BDCDATA然后针对客户字段手动补 BDCDATA 记录——屏幕名、字段名从技术信息里看字段名就是 PAI 里那个数据元素名。这条路慢、脆、维护成本高但它至少走的是完整标准流程业务上说得通。5. 排查链路返回表说成功但字段是空的5.1 第一步永远是把 RETURN 读干净BAPI 的返回表是逐行的不是一行的。类型有 S成功、I信息、W警告、E错误、A终止。常见的错误写法是只判断 sy-subrc或者只看 lt_return 的第一行。正确做法是READ TABLE lt_return TRANSPORTING NO FIELDS WITH KEY type E. IF sy-subrc 0. READ TABLE lt_return TRANSPORTING NO FIELDS WITH KEY type A. ENDIF.顺便说个经验E 和 A 处理方式不一样。E 是错误通常整批该回滚A 是终止处理完必须停下来别继续往下跑。有些人一看到 type 不是 S 就当错误处理把一批本来能过的数据也毙了。5.2 断点打在哪看标准代码认不认你的结构名返回表看着干净但字段是空的时候我会这么一步步往下走SE37 单条测试入参手填先确认标准字段那条链路是通的这一步不通过就别往下走。加上 EXTENSIONIN 再跑一次用 SE16N 查 RESB 对应行的 ZZ 字段。还是空的话在 BAPI_RESERVATION_CREATE1 里打 /h单步进去找处理 EXTENSIONIN 的那段代码一般是个 LOOP 按 STRUCTURE 名分发。如果压根没有这段逻辑结论就出来了这个 BAPI 没实现扩展。一个很灵的验证技巧把 EXTENSIONIN 里的 STRUCTURE 名故意写成一个不存在的名字比如 BAPI_TE_RESB_XXX。如果返回表报不支持的扩展结构之类的错说明框架在跑只是你的字段没对上如果既不报错也没别的反应那就是根本没读 EXTENSIONIN直接转第四节的方案。这个故意写错结构名的办法我用过好几次比翻代码快多了能省掉半小时的单步。5.3 典型报错与根因对照返回消息大致意思根因处理请输入工厂表头 PLANT 空或者行项目 PLANT 空两处至少一处给全请输入科目分配类别移动类型要求 ACCT 类别KNTTP 未传按移动类型补 KNTTP 和对应字段单位 XX 没有转换关系ENTRY_UOM 与物料基本单位缺换算改传基本计量单位或先补主数据物料在工厂中不存在MARC 里没有该物料工厂组合查主数据不是接口问题预留号分配失败编号范围RSNUM用尽或未缓冲查 SNRO 里的预留号范围权限检查失败执行用户缺预留创建授权找 BASIS 补权限排查的时候有个小习惯很有用把整张 RETURN 里所有 E/A 行的消息号消息类 编号记下来去 SE91 里查原文。BAPI 返回的文本有时候被截断或者翻译得不明不白原文一看就明白了。5.4 回读验证别只看返回表写完必须回读。BAPI_RESERVATION_GETDETAIL 按 RSNUM 把抬头和行项目捞回来核对几个关键点行数对不对、每行的数量和单位对不对、ZZ 字段有没有值。SE16N 直接查 RESB 也行但要注意两点一是 SE16N 看到的是当前数据库状态如果 COMMIT 没做或者被 ROLLBACK 了你什么都看不到二是 SE16N 里要带 RSNUM 条件别用物料号裸查前面说过生产订单组件也在里面。6. 批量上线的工程化细节6.1 LUW 切分别把一千条塞进一个事务前面提过 ROLLBACK 会掀掉整个 LUW 的表。批量场景下的稳妥做法是分包每 100 到 200 条预留一组组内如果有任何一条报 E整组回滚然后把这一组的入参降级成单条重试逐条定位到底哪一条有问题。这么做的另一个好处是性能。每次 BAPI 调用都会有一堆内部 SELECT 和内表操作事务开太久锁持有时间也长。实测下来 200 条一包的节奏比较舒服再往下拆收益就不明显了反而多交易的开销上来了。6.2 编号范围和权限上线前先确认预留号来自内部编号范围对象 RSNUM。批量创建的时候编号消耗很快测试环境里经常跑到范围上界然后报错。上线前做容量评估预估一年创建多少条看看当前编号范围够不够顺便确认这个编号范围对象在系统里是不是做了缓冲——缓冲会影响到并发场景下取号的连续性但这个一般由 BASIS 把关你只要知道报编号错误时该找谁就行。权限同理。接口跑在哪个用户下这个用户得能创建预留。这种事最好在上线前用生产环境做一次小批量冒烟别等大批量跑起来才发现权限不对。6.3 增强字段这事其实有个统一的思路做这个需求的时候我一直有个感觉增强字段的问题本质都是这条数据到底存在哪里的问题。想通这一点很多看起来不相干的场景就串起来了。比如标准报表上的增强字段取值。报表取不到自定义字段十有八九是字段存在附加结构里但报表的数据源结构没带上那一层。像财务类报表、计划类对象PLAF 这种上挂客户字段排查思路是一样的先确认字段落在哪张表/哪个结构上再看取值链路有没有把它带出来最后才去动实现。顺序反了就会一直在改报表逻辑上打转。所以我的通用排查三步是字段存在哪SE11 看附加结构挂在谁身上。这个字段在哪些场景需要被读到屏幕、报表、接口、打印。每条链路各自怎么取SQL 直读、扩展框架、BADI、报表数据源改造。把它当一个问题、而不是一堆问题效率会高很多。6.4 顺带说个横向对比改价格和写增强字段是两码事前阵子还处理过一个采购订单改价的接口用的是 BAPI_PO_CHANGE。有个细节值得对照着看采购订单的价格不是存在 EKPO 的一个字段里那么简单它走的是条件condition体系改价实际上需要先在条件表里删旧记录、再插新记录提交的时候传的是条件结构而不是单价。这和预留改增强字段是同一个道理动手之前先搞清楚这个字段是主表的普通字段还是藏在从表、条件表或者客户包含里改法完全不同。想当然地找到字段名就赋值在标准 BAPI 上基本都要栽跟头。最后说个实际体会这类接口需求纯粹写代码的时间可能只占三分之一剩下三分之二全花在确认字段落哪张表确认 BAPI 认不认扩展确认异常分支怎么处理上。我现在做类似需求习惯先花半小时把 SE11 和 SE37 打开把结构关系和参数签名全看一遍再动手敲第一行代码——这个习惯帮我省下的返工时间比任何编码技巧都多。
阅读完成 · 觉得有帮助?