Преглед изворни кода

fix(进件): 六个挂账 finding 的注释订正(R48/R53/R54/R56/R57/R58)—— 无行为改动

全在注释 / javadoc / 文案层,10 个文件、29 行改动(增删等量,逐行核过:全部是注释行,
零条非注释行):
- R48 错页号两处:page 196236566 → 196236564(同一错映射被用了两次,两处都改)
- R53 遮罩注释失真三处:`mask` 是两层(先按键名、再按长数字),成立的说法是「不设上界、不截断」
- R54 十一处「一执行那条 SQL 就当场变假」的句子加 2026-09-23 日期锚
- R56 全称量词为假:把「下面每个 p.get」收窄到验签失败那条分支,并改用常量名指代
- R57 计数对不上任何读法:换成按方法归属的句子(改前全文件 update 返回 0 的桩只有一处,属 save 路径)
- R58 菜单 SQL 自述的四个行号全错:改成「权限串 + 端点路径」名字锚,不新增任何裸数字

另(非 finding,措辞订正):TenantInnerInterceptor 那句「交付脚本只做只读预检」订正为
「不替你删任何行、只做预检与建索引」—— 论证(删哪一行是用户的数据决策)未动。

回归:Tests run: 645, Failures: 0, Errors: 0, Skipped: 0(BUILD SUCCESS;需库隧道在位)。
alphaH пре 3 недеља
родитељ
комит
9e7f22f2cd

+ 5 - 5
java/src/main/java/com/payment/platform/core/tenant/TenantInnerInterceptor.java

@@ -127,7 +127,7 @@ public class TenantInnerInterceptor extends TenantLineInnerInterceptor {
             //          pay_lsy_merchant.mch_no       idx_lsy_merchant_mch_no
             //            ⚠ 2026-09-23:**已有一条未认证路径在用 mch_no 定位**(审核通知入口,
             //              走 (乙):selectList 后多行一律不写)。本行原先写「不得用来定位」,
-            //              那句对**唯一性**仍成立(索引今天不存在),但对「能不能用」不成立 ——
+            //              那句对**唯一性**仍成立(索引 2026-09-23 时不存在),但对「能不能用」不成立 ——
             //              见本条末尾那条实例。别从本行读成「这条路径违规」。
             //          pay_lsy_notify_log.trade_no   idx_lsy_notify_trade_no
             //                                        (trade_no, trade_type)
@@ -183,7 +183,7 @@ public class TenantInnerInterceptor extends TenantLineInnerInterceptor {
             //       **义务**:任何**未认证**的读都**不得**用 selectOne —— 无认证查询不带租户条件,
             //       LIMIT 1 会返回**任意租户**的审计行(raw_body 含上游报文原文);
             //       要按业务键做未认证读,必须**先补唯一约束**(trade_no / pay_trace_no 均无)。
-            //   ★★ pay_lsy_merchant 的 mch_no **没有唯一索引**(见 LsyMerchantService.requireByMchNo
+            //   ★★ pay_lsy_merchant 的 mch_no **2026-09-23 时没有唯一索引**(见 LsyMerchantService.requireByMchNo
             //     的 javadoc)。本条原写「当前**没有任何**未认证路径查询它…本次加入是**惰性**的」
             //     —— **2026-09-23 起那句已假**:审核通知入口
             //     (POST /payment/collection/notify/onboarding,LeshouyingNotifyController
@@ -192,12 +192,12 @@ public class TenantInnerInterceptor extends TenantLineInnerInterceptor {
             //       · 安全来自**查询形状** —— LsyMerchantService.updateReviewStatusByMchNo
             //         用 selectList 后自己判:0 行 ⇒ NOT_FOUND、>1 行 ⇒ AMBIGUOUS,
             //         两种都**一行都不写**并让调用方应答 FAIL,只有恰好 1 行才定向写入;
-            //       · **不是**来自唯一性:索引 uk_lsy_merchant_mch_no **今天不存在**,而且
+            //       · **不是**来自唯一性:索引 uk_lsy_merchant_mch_no **2026-09-23 时**不存在、也
             //         **建不起来** —— 真库实测 mch_no='Y52211090004' 有 2 行(同租户,
             //         enterprise_id 一行有值一行 NULL,分别落在 uk_lsy_merchant_tenant_ent
             //         与 uk_lsy_merchant_tenant_level 上,**两行各自都合法**)。
-            //         交付脚本 java/src/main/resources/db/lsy-merchant-mch-no-uk.sql 只做只读预检,
-            //         删哪一行是**用户的数据决策**。
+            //         交付脚本 java/src/main/resources/db/lsy-merchant-mch-no-uk.sql 不替你删任何行,
+            //         只做预检与建索引;删哪一行是**用户的数据决策**。
             //     **义务**:① 该索引仍是**第二道闸**(纵深防御 + 给将来按 mch_no 的未认证调用方
             //     立约束),条件具备后补上;② 在它建好之前,任何按 mch_no 的**未认证**读都
             //     **不得**用 selectOne / LIMIT 1,必须自己判多行;③ 索引建好后,

+ 1 - 1
java/src/main/java/com/payment/platform/module/payment/leshouying/enums/LsyMerchantReviewStatus.java

@@ -19,7 +19,7 @@ package com.payment.platform.module.payment.leshouying.enums;
  * </ol>
  *
  * <p>★ <b>这不是 spec §6.4 禁止的那种「上游值域归一化」。</b>那条禁的是把上游**三套**
- * {@code reviewed_status} 值域(异步通知 page 196236556、入网状态 page 196236566、主动查询
+ * {@code reviewed_status} 值域(异步通知 page 196236556、入网状态 page 196236564、主动查询
  * page 196236567)猜着合并成一个枚举 —— 三套各自实现、各按各的解析,**不做归一化猜测**。
  * 本枚举解的不是上游报文:它是**我们自己一列**的词表,取值只从 196236556 那一套推导。
  * 解上游值域的那个枚举({@code LsyOnboardingReviewStatus},审核通知那一侧的产物)与本枚举

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

@@ -12,7 +12,7 @@ import cn.hutool.core.util.StrUtil;
  *       {@code 1.审核通过,2.审核驳回}(类型标称 {@code String(4)}),而**同页的请求示例**写的是
  *       中文字符串 {@code "reviewed_status":"审核通过"}(正好 4 字);该示例里还多出参数表
  *       没有的 {@code settlement_status}。</li>
- *   <li><b>page 196236566(查询商户入网状态)</b>:返回参数表写
+ *   <li><b>page 196236564(查询商户入网状态)</b>:返回参数表写
  *       {@code 0.审核中,1.审核通过,2.审核驳回,3.预审核通过,4.待提交,5.已提交,6.重新进件},
  *       而**同页的接口描述块**给的是另一个 0–6 映射
  *       ({@code 0已发起进件 / 1返回渠道子商户进件流程成功的终态 / 2重新修改信息进件 /

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

@@ -101,7 +101,7 @@ public class LsyMerchantService {
      * <p>保留它是因为 {@code mch_no → tenant} 的反查在进件(子项目 B)之后可能需要。
      *
      * <p><b>调用方注意:</b>本方法<b>不带租户条件</b>,且 {@code pay_lsy_merchant} 在
-     * {@code mch_no} 上<b>没有唯一索引</b>(唯一约束是 {@code (tenant_id, enterprise_id)})。
+     * {@code mch_no} 上<b>2026-09-23 时没有唯一索引</b>(唯一约束是 {@code (tenant_id, enterprise_id)})。
      * 查询为 {@code LIMIT 1} 且无 {@code ORDER BY},一旦存在重复 {@code mch_no},
      * 返回的是<b>任意一行</b>。调用方须自行确认 {@code mch_no} 的唯一性。
      *
@@ -166,7 +166,7 @@ public class LsyMerchantService {
      * 与 {@code TenantInnerInterceptor} 里那条「未认证的读不得用 {@code selectOne}」是同一件事。
      *
      * <h2>为什么 WHERE 用 {@code id} 而不是 {@code mch_no}</h2>
-     * 定位与写入之间若有人插进一行同 {@code mch_no} 的行(唯一索引还没建,插得进来),
+     * 定位与写入之间若有人插进一行同 {@code mch_no} 的行(唯一索引 2026-09-23 时还没建,插得进来),
      * {@code WHERE mch_no = ?} 会<b>一次改两行</b>。主键定位没有这个窗口。
      *
      * @param mchNo          本地列 {@code mch_no} 的值(调用方从上游入站键 {@code merchant_no} 取)

+ 4 - 4
java/src/main/java/com/payment/platform/module/payment/leshouying/service/LsyNotifyService.java

@@ -57,9 +57,9 @@ public class LsyNotifyService {
      * `raw_body` 的长度上限。
      *
      * ★ **上限是为了让未认证入口无法用它放大存储,不是为了省空间。**
-     *   `raw_body` 落库用的是 `LeshouyingSensitive.mask(rawBody)` —— `mask` 只遮长数字,
-     *   **不减长度**。该列 DDL 类型是 TEXT(无上限),于是一条未验签请求就能写进任意长的
-     *   一行,行长只受请求体大小限制。这是典型的「存储放大」入口。
+     *   `raw_body` 落库用的是 `LeshouyingSensitive.mask(rawBody)` —— 它**先按键名遮、再按长数字遮**,
+     *   且**不设上界、不截断**(未命中的内容原样通过)。该列 DDL 类型是 TEXT(无上限),
+     *   于是一条未验签请求就能写进任意长的一行,行长只受请求体大小限制 —— 这是「存储放大」入口。
      *   4096 足够放下任何真实回调报文(报文是短字段的 JSON),不会误伤审计价值。
      */
     private static final int RAW_BODY_MAX = 4096;
@@ -413,7 +413,7 @@ public class LsyNotifyService {
             //   干净的代价就是这条通道没人替它把关。回调报文将来回显什么字段不由我们决定,
             //   所以脱敏放在写入口,而不是靠「上游应该不会回显」的假设。
             // ★ 审计价值靠保留——保留的是报文结构、return_code/result_msg、trade_no 这些排障要读的字段;
-            //   被遮掉的只有长数字。不要因为「遮了就不完整」而删掉 raw_body 这一列。
+            //   被遮掉的只是「键名在名单里的值」与「长数字」这两类片段。不要因为「遮了就不完整」而删掉 raw_body 这一列。
             // ★ 「先 mask 再截断」的顺序不能反:反过来会把一个跨在截断点上的付款码切成两段 6 位,
             //   值层(要求连续 12 位)两段都接不住 —— 与上面 brief 里那个顺序陷阱是同一个坑。
             logEntity.setRawBody(truncate(LeshouyingSensitive.mask(rawBody), RAW_BODY_MAX));

+ 3 - 3
java/src/main/java/com/payment/platform/module/payment/leshouying/service/LsyOnboardingNotifyService.java

@@ -106,9 +106,9 @@ public class LsyOnboardingNotifyService {
         //   · 经控制器进来的报文**走不到这里**:LeshouyingNotifyController 在调本方法**之前**
         //     就已经解引用了 body(先 body.get("merchant_no") 打日志、再调服务),
         //     body == null 时 500 发生在控制器那一帧,本行轮不到。
-        //   · 它真正挡的是「params 直接传 null」这一种:没有它,p 就是 null,而下面每个
-        //     p.get 都在**验签失败**那条分支上 —— 那条分支**必须**能打出「上游到底发了什么」,
-        //     在那里 NPE 会让一次可疑回调变成一个 500,日志里反而什么都没有。
+        //   · 它真正挡的是「params 直接传 null」这一种:没有它,p 就是 null,而**验签失败**那条分支
+        //     里读 {@code INBOUND_MCH_NO} / {@code INBOUND_REVIEWED_STATUS} 的 p.get 会 NPE —— 那条分支
+        //     **必须**能打出「上游到底发了什么」,NPE 会让一次可疑回调变成一个 500,日志里反而什么都没有。
         //   · 它做的只是把 null 归一成空 map ⇒ verify 必然判 false ⇒ 走「验签失败」那条应答。
         //     (用例:LsyOnboardingNotifyServiceTest#onboardingNotify_nullBody_isRejectedAsBadSign_notNpe)
         Map<String, Object> p = (params == null) ? Map.of() : params;

+ 1 - 1
java/src/main/resources/db/lsy-merchant-mch-no-uk.sql

@@ -9,7 +9,7 @@
 -- 为什么仍然值得建这个索引(背景,非本计划新立):
 --   三-B 的审核通知入口(POST /payment/collection/notify/onboarding)**免登录**,
 --   没有认证上下文 ⇒ 租户拦截器**不追加**租户谓词 ⇒ 它只能按 `mch_no` 定位本地行;
---   而 `pay_lsy_merchant.mch_no` **今天没有唯一索引**。
+--   而 `pay_lsy_merchant.mch_no` **2026-09-23 时没有唯一索引**。
 --   ★★ 但**这个索引不是那条路径的安全前提**。2026-09-23 订正:本段原以「必须先把
 --      uk_lsy_merchant_mch_no 补上 —— 唯一性是无认证查询不串租户的前提」作为义务来源,
 --      那读起来就是**把索引当成前置**,该读法已作废(计划 Task 6 Step 0 有同一处订正)。

+ 5 - 5
java/src/main/resources/db/lsy-onboarding-menu.sql

@@ -74,11 +74,11 @@
 -- ── ★ permission 必须与 Task 7 的 @PreAuthorize 逐字相同 ────────────────
 -- 差一个字符就是 403,而且超管测不出来(同上)。
 -- 本脚本三个 permission 逐字取自(**2026-09-23 从源码现取,未抄任何文档**):
---   LeshouyingOnboardingController.java:107  @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:list')")
---   LeshouyingOnboardingController.java:133  @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:create')")
---   LeshouyingOnboardingController.java:152  @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:commit')")
--- 类级 `@RequestMapping("/payment/collection/onboarding")`(同文件 :78)。★ 若 Task 7 之后改动,
--- 本文件必须重取 —— 取值口径是「现取」,不是「抄一次就完」。
+--   @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:list')")    —— GET /page
+--   @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:create')")  —— POST /create、/upload-image 及 GET /areas、/mcc
+--   @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:commit')")  —— POST /commit
+-- 三条串都声明在 LeshouyingOnboardingController,端点路径相对其类级映射
+-- @RequestMapping("/payment/collection/onboarding")。★ 若 Task 7 之后改动,本文件必须重取 —— 取值口径是「现取」,不是「抄一次就完」。
 --
 -- ── ★ component_path 的形态 ───────────────────────────────────────────
 -- 必须是**相对 src/views/ 的路径**:不带 `views/` 前缀、不带 `.vue` 后缀。

+ 6 - 6
java/src/test/java/com/payment/platform/module/payment/leshouying/service/LsyMerchantServiceTest.java

@@ -246,7 +246,7 @@ class LsyMerchantServiceTest {
      * <p>这**不是理论情形**:真库里 {@code mch_no='Y52211090004'} 就有 <b>2</b> 行,
      * 同租户、{@code enterprise_id} 一行有值一行 NULL,**两行各自都合法**
      * (分别落在 {@code uk_lsy_merchant_tenant_ent} 与 {@code uk_lsy_merchant_tenant_level} 上)。
-     * 唯一索引 {@code uk_lsy_merchant_mch_no} 今天不存在、且因这两行**建不起来**,
+     * 唯一索引 {@code uk_lsy_merchant_mch_no} 2026-09-23 时不存在、且因这两行**建不起来**,
      * 删哪一行是用户的数据决策 ⇒ 这个状态可能长期存在。
      *
      * <p>所以:0 行 ⇒ 404;&gt;1 行 ⇒ **抛**(且 ERROR 日志里带命中的 id,那是人工处置的唯一线索)。
@@ -548,7 +548,7 @@ class LsyMerchantServiceTest {
     //   · save 的编辑分支写不了 review_status,返回值又与正常成功无法区分。
     //
     // 【为什么必须是 selectList 而不是 selectOne / LIMIT 1】
-    //   pay_lsy_merchant 在 mch_no 上**今天没有唯一索引** —— 2026-09-23 实测真库里有重复
+    //   pay_lsy_merchant 在 mch_no 上**2026-09-23 时没有唯一索引** —— 该日实测真库里有重复
     //   (mch_no='Y52211090004' 两行,且两行都合法:一行 enterprise_id 有值、一行 NULL)。
     //   未认证查询不带租户谓词,`LIMIT 1` 会**静默取任意一行**,而那一行可能是别的租户的。
     //   ⇒ 安全来自**查询形状**,不来自唯一性(唯一性是第二道闸,见 db/lsy-merchant-mch-no-uk.sql)。
@@ -602,7 +602,7 @@ class LsyMerchantServiceTest {
      * WHERE 指向**定位到那一行的 id**,而不是 {@code mch_no}。
      *
      * <p>用 {@code id} 的理由不是审美:定位与写入之间若有人插进一行同 {@code mch_no} 的行,
-     * {@code WHERE mch_no = ?} 会**一次改两行**(而唯一索引今天还不存在,插得进来)。
+     * {@code WHERE mch_no = ?} 会**一次改两行**(而唯一索引 2026-09-23 时还不存在,插得进来)。
      * 主键定位没有这个窗口。
      */
     @Test
@@ -644,7 +644,7 @@ class LsyMerchantServiceTest {
     /**
      * 多行 ⇒ {@code AMBIGUOUS},且**一行都不写**。
      *
-     * <p>这是本写入口存在的**主要理由**:{@code mch_no} 今天真的重复(真库实测 2 行),
+     * <p>这是本写入口存在的**主要理由**:{@code mch_no} 2026-09-23 时真的重复(真库实测 2 行),
      * 而这条路径免登录、查询不带租户谓词 —— 随便挑一行的后果是**写到别的租户头上**。
      */
     @Test
@@ -704,8 +704,8 @@ class LsyMerchantServiceTest {
      * ★ {@code update} 返回 <b>0</b> ⇒ {@code NOT_FOUND},且<b>不抛</b>。
      *
      * <p>可达窗口只有「定位到写入之间那一行没了」(并发删除 / 主键换了)。正因为窗口窄,
-     * 它此前<b>零覆盖</b>:全 diff 里 {@code merchantMapper.update} 的桩只有三处
-     * {@code thenReturn(1)}、{@code thenReturn(0)} 一处都没有 ⇒ 删掉这段处理全量照样绿。
+     * 它此前<b>零覆盖</b>:加这段处理之前,{@code merchantMapper.update} 返回 0 的桩全文件只有一处,
+     * 且属于 {@code save} 那条路径的用例({@code save_updateBranch_zeroAffectedRows_throws404})⇒ 删掉这段处理全量照样绿。
      * 而它是这条路径上唯一把「DB 说一行都没改到」翻译成「怎么应答上游」的地方。
      *
      * <p>为什么不抛:调用方要的是一个结果去决定怎么应答上游,而这种情况的正确答案与

+ 1 - 1
java/src/test/java/com/payment/platform/module/payment/leshouying/service/LsyOnboardingNotifyServiceTest.java

@@ -273,7 +273,7 @@ class LsyOnboardingNotifyServiceTest {
      * {@code mch_no='Y52211090004'} 有 2 行({@code id} 1 与 2102313081860202497),
      * 且两行**都合法**(一行 {@code enterprise_id} 有值、一行 NULL,分别落在
      * {@code uk_lsy_merchant_tenant_ent} 与 {@code uk_lsy_merchant_tenant_level} 上)。
-     * 唯一索引 {@code uk_lsy_merchant_mch_no} **今天不存在**(建不起来,见
+     * 唯一索引 {@code uk_lsy_merchant_mch_no} **2026-09-23 时不存在**(建不起来,见
      * {@code db/lsy-merchant-mch-no-uk.sql})。
      *
      * <p>所以免登录入口在**没有索引**时靠的是**查询形状**:{@code selectList} 之后自己判,