Quellcode durchsuchen

fix(进件): Task 4 评审订正 - 订正三处 javadoc 失实 + 钉住 merchantInfo 的数字字段类型

评审 6 条,逐条落点:

F1 LsyReviewStatusDTO 类 javadoc 原写「三个字段全部原样透传」,对 statusText 是假的:
   上游报文里没有 status_text 这个键,它是本模块按本页值域翻的;且与同文件 statusText
   的字段 javadoc 自相矛盾。改成把「上游原值」(reviewedStatus / reviewedReason)与
   「本模块翻的」(statusText)分开讲,去掉「全部」这类总括词。

F2 merchantInfo_returnsRawUpstreamFields_untouched:merchant_type 原先用 String.valueOf
   断言,把 Integer 3 与 String "3" 抹平 ⇒ 抓不到「把所有值统一 stringify 再返回」这类
   实现。改为钉运行时类型 Integer。类型先实测再写:真实解析器把 JSON 数字 3 解成
   java.lang.Integer(同用例的 wx_channel_rate 桩值本就是 JSON 字符串,当不了对照)。
   两向变异:stringify 型变异 ⇒ 该断言 1 红(expected: java.lang.Integer<3> but was:
   java.lang.String<3>,class 内 merchantInfo 出现 String.valueOf);还原后 21 绿,
   源码与变异前逐字节相同、class fp 回到 c7a37368。

F4 空白文案依据:两处 javadoc 各补一句「LsyMerchantService.save 抛的就是逐字
   『商户号不能为空』(:178)」,同时保留「与 requireByMchNo 的『mch_no 不能为空』
   (:106)不一致」这个事实 —— 两句都在。

F5 R-4 块里 requireByMchNo 的 404 引用补全为 `"商户号不存在: " + mchNo`(原文带商户号后缀)。

F6 R-4 块新增一条禁令:三-B 的端点不得把 result_msg(上游原文)原样放进面向商户的
   响应体,按 Global Constraint 2 过 LeshouyingSensitive.mask。

F7 LsyReviewStatus.of 补一句交叉引用:同包 LsyPayType.of 语义相反(空白→AUTO、
   未知码抛 IllegalArgumentException),不要按方法名互抄。

另(对评审一处「别动」的偏离,已在报告 §八 记明):R-4 块里 requireByTenant 那句 404
的引文,评审记为「逐字准确」,但实测内层引号是『商户映射』而源码是「商户映射」
(LsyMerchantService:81)—— 按本仓「按字面 grep javadoc」的标准订正为原文;
两处引文改用 {@code} 包裹以便按字面检索。
alphaH vor 3 Wochen
Ursprung
Commit
90cdb7777c

+ 11 - 1
java/src/main/java/com/payment/platform/module/payment/leshouying/dto/LsyReviewStatusDTO.java

@@ -5,7 +5,17 @@ import lombok.Data;
 /**
  * 商户审核状态 —— 上游「商户审核主动查询」接口(page 196236567)的返回。
  *
- * <p>三个字段**全部原样透传**,本 DTO 不做任何跨接口归一化;值域冲突见
+ * <p><b>三个字段分两类,不要用一个总括词盖过去</b>:
+ * <ul>
+ *   <li><b>上游原值</b>(报文里有什么就是什么,本类不筛不改):
+ *       {@link #reviewedStatus}、{@link #reviewedReason} —— 各自的语义边界见字段 javadoc。</li>
+ *   <li><b>本模块翻出来的</b>:{@link #statusText} —— <b>上游报文里没有 {@code status_text}
+ *       这个键</b>,它是本模块按**本页这一套**值域翻的(见
+ *       {@link com.payment.platform.module.payment.leshouying.enums.LsyReviewStatus})。
+ *       所以「报文里找不到 → 上游没返回」这种读法在这里是错的:它压根不是上游的字段。</li>
+ * </ul>
+ *
+ * <p>本 DTO 不做任何跨接口归一化;值域冲突(同名 {@code reviewed_status} 有三套)见
  * {@link com.payment.platform.module.payment.leshouying.enums.LsyReviewStatus} 的 javadoc。
  */
 @Data

+ 6 - 0
java/src/main/java/com/payment/platform/module/payment/leshouying/enums/LsyReviewStatus.java

@@ -60,6 +60,12 @@ public enum LsyReviewStatus {
      *
      * <p>【为什么匹配不到不抛】同一个值域在文档里就已经三套了,上游加码是**预期内**的事。
      * 抛异常的后果是「整条链路 500」,而调用方需要的只是把原始码显示出来。
+     *
+     * <p>★ <b>不要按方法名与本包另一个 {@code of} 互抄</b>:{@link LsyPayType#of(String)}
+     * 的语义与这里**相反** —— 它对空白返回 {@code AUTO}、对未知码抛
+     * {@code IllegalArgumentException}。那边能这么定,是因为它的码域封闭、且 {@code AUTO}
+     * 是个有业务含义的默认值;这边的码域**不封闭**(同名 {@code reviewed_status}
+     * 光文档里就有三套值域),所以空白与未知码一律 {@code null}。
      */
     public static LsyReviewStatus of(String code) {
         if (StrUtil.isBlank(code)) {

+ 11 - 2
java/src/main/java/com/payment/platform/module/payment/leshouying/service/LsyOnboardingService.java

@@ -30,8 +30,8 @@ import java.util.Map;
  *   <li><b>不查本地映射是有意的,不是漏了。</b>进件阶段这个商户<b>还没进
  *       {@code pay_lsy_merchant}</b>(那一行是进件成功之后才写的)—— 所以在这里调
  *       {@code LsyMerchantService.requireByTenant} / {@code requireByMchNo} 必然落空,
- *       报的是它们各自那句 404(「该租户未配置商户号,请先在『商户映射』中配置」/
- *       「商户号不存在」)。那正是本轮要防的「校验前提不成立」:**在进件阶段,
+ *       报的是它们各自那句 404({@code 该租户未配置商户号,请先在「商户映射」中配置} /
+ *       {@code "商户号不存在: " + mchNo})。那正是本轮要防的「校验前提不成立」:**在进件阶段,
  *       『配置里没有这个商户』不是异常,而是这个阶段的常态** —— 一句 404 会把操作者
  *       送去配一个此刻不可能存在的映射。</li>
  *   <li><b>三-B 加 HTTP 端点时,必须在端点侧先证明该 {@code mchNo} 属于当前租户</b>
@@ -41,6 +41,10 @@ import java.util.Map;
  *   <li><b>三-A 没有端点 ⇒ 当前没有可达路径</b>(本批次一行 {@code @RestController} 都不加)。
  *       但<b>这行 javadoc 是三-B 的前置条件,不许删</b> —— 三-B 的端点是这个缺口的第一个可达入口,
  *       而到那时「本层看着像做过校验」(方法名收 mchNo、还抛 400)正是最容易被误判的地方。</li>
+ *   <li><b>三-B 的端点不得把 {@code result_msg} 原样放进面向商户的响应体。</b>
+ *       {@link #merchantInfo} 有意把 {@code result_code} / {@code result_msg} 原样带回(不筛不读),
+ *       但 {@code result_msg} 是**上游原文**;按 Global Constraint 2 的口径,入站 / 异常正文内容
+ *       来自外部 ⇒ 过 {@code LeshouyingSensitive.mask} 之后再往外给。三-B 的端点是第一处可达出口。</li>
  * </ol>
  */
 @Slf4j
@@ -182,6 +186,11 @@ public class LsyOnboardingService {
      * 空串发上游的后果是上游回一句笼统的「参数不合法」,本侧看不出是哪个参数。
      * 形状照 {@code LsyMerchantService.requireByMchNo}(空白检查 + 400);
      * 文案取「商户号不能为空」。
+     *
+     * <p>★ 这个字面值**不是新造的**:{@code LsyMerchantService.save} 抛的就是**逐字**
+     * {@code "商户号不能为空"}(实测 {@code LsyMerchantService.java:178})。
+     * 同类的两个方法字面不同:{@code save} 与这里同字面,{@code requireByMchNo}
+     * 那句是 {@code "mch_no 不能为空"}({@code :106})—— 两件事都记着,别只留一句。
      */
     private static Map<String, Object> mchNoParams(String mchNo) {
         if (StrUtil.isBlank(mchNo)) {

+ 15 - 2
java/src/test/java/com/payment/platform/module/payment/leshouying/service/LsyOnboardingServiceTest.java

@@ -318,8 +318,17 @@ class LsyOnboardingServiceTest {
         Map<String, Object> info = service.merchantInfo(MCH_NO);
 
         assertEquals("0.006", info.get("wx_channel_rate"), "字符串型费率必须原样带回");
-        assertEquals("3", String.valueOf(info.get("merchant_type")),
-                "数字型字段必须原样带回,被剔掉的话这里会是字符串 \"null\"");
+        // ★ 这里钉的是**运行时类型**,不是字符串化之后的值:`String.valueOf` 会把 Integer 3 与
+        //   String "3" 抹平,于是「把所有值统一 stringify 再返回」这种实现照样绿 —— 而它正是
+        //   本条用例声称要抓的那类改动。类型**以实测为准**:真实解析器把 JSON 数字 3 解成
+        //   java.lang.Integer(2026-09-22 实测:Integer 3 / wx_channel_rate=java.lang.String /
+        //   整个 map=java.util.LinkedHashMap);同一条用例里的 wx_channel_rate 当不了对照 ——
+        //   它的桩值本来就是 JSON 字符串,stringify 型实现在它上面是恒等变换。
+        Object merchantType = info.get("merchant_type");
+        assertEquals(Integer.valueOf(3), merchantType,
+                "数字型字段必须原样带回:JSON 数字 3 ⇒ Integer 3,既不能取成 String \"3\","
+                        + "也不能被剔成 null。实际: " + merchantType
+                        + (merchantType == null ? " (null)" : " (" + merchantType.getClass().getName() + ")"));
         assertEquals(Set.of("result_code", "result_msg", "merchant_type", "wx_channel_rate"),
                 info.keySet(), "不得立白名单:上游给了什么键就带什么键,实际: " + info.keySet());
     }
@@ -330,6 +339,10 @@ class LsyOnboardingServiceTest {
      * <p>文案逐字取自 brief。brief 同一句里又说「照 {@code LsyMerchantService.requireByMchNo}
      * 的口径」,而那个先例的文案其实是 {@code "mch_no 不能为空"} —— 两者不一致;
      * 「照口径」指的是**空白检查 + 400 这个形状**,文案按 brief 的字面值走。
+     *
+     * <p>★ 这个字面值**不是新造的**:同类的 {@code LsyMerchantService.save}
+     * 抛的就是**逐字** {@code "商户号不能为空"}(实测 {@code LsyMerchantService.java:178})。
+     * 两句都在:{@code save} 与这里同字面,{@code requireByMchNo} 与这里不同字面。
      */
     @Test
     void merchantInfo_blankMchNo_throws400WithoutCallingUpstream() {