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

docs(台账): 入账游标 SQL 的执行核对读数 + 菜单改名核对(§2.3)

用户执行了 db/lsy-refund-reconcile-cursor.sql,我直连库只读取数核对
(脚本 D:\lsy-regress\cursorcheck.py + filevsdb.py,只读事务,凭据只打印长度)。

对了的:列是 timestamp with time zone、注释与 V1.8 逐字相同、索引
(status, next_query_time) 形状对;顺带把「V1.8 文件 ↔ 活库」这一环闭掉 ——
4 张表列名集合逐表一致(pay_lsy_refund 23/23),85 条列注释库/文件逐字相同差 0。
这一环此前没有任何测试覆盖(LsyEntityMappingTest 只做「实体 ↔ V1.8 文件」,
而活库是后来被 ALTER 改的)。

必须如实记下的一条:pay_lsy_refund 是空表(total_rows=0),所以「NULL 计数=0」
是**空洞为真** —— 回填只被证明跑通了,没被证明逻辑对。该不变量现在全靠 Java
侧撑着,而列在库里 default=None ⇒ 任何不走 setNextQueryTime 的插入都会产生
一个静默游标为 NULL 的行。当场复核该洞是闭的:全仓 refundMapper.insert 只一处
(LsyRefundService:210),紧邻其上 :208 就是 setNextQueryTime;模块内无 XML mapper、
无 @Insert;测试对捕获实体断言 getNextQueryTime() 非空。

另一处无害但记下:库里该列 attnum=23(ALTER 追加到末尾),V1.8 文件里排第 14 位
⇒ 全新安装与既有库列序不同。已核实无消费者依赖列序(LsyEntityMappingTest 比的是
名字 Set<String>、MP 按实体生成显式列清单、模块内零 SELECT *),故不改。

顺带核对:lsy-menu-rename.sql 也已执行,sys_menu 4 行里三个页面 component_path
都是 module_payment/collection/*,旧 leshouying 路径 0 行 ⇒ 前端三页静默落
404.vue 的风险解除。sys_role_menus 8 行 = 管理员角色(id=1) 与租户角色(id=2) 各 4 条。
alphaH пре 3 недеља
родитељ
комит
2179be5882

+ 31 - 5
docs/superpowers/specs/2026-09-21-leshouying-payment-channel-open-questions.md

@@ -397,12 +397,38 @@ EXPIRED」是**安全方向**;退款行没有对应物 —— 写 FAIL 会**
 ⇒ 断言**双向**都实测了:该红的红、该绿的绿;红的那两条读数说明这些用例**真的在测这个机制**,
 而不是碰巧通过。
 
-**⚠ 未完成的一步(阻塞发版):`db/lsy-refund-reconcile-cursor.sql` 尚未执行。**
+**✅ 已执行并核对(2026-09-22,用户跑完 SQL 后我直连库只读取数)。**
 
-我没有执行 DDL 的权限(本项目规矩:SQL 我写、由你跑)。该文件含加列 + 加索引 + 回填,
-**必须在部署新代码之前跑完**——新代码的扫描条件含 `next_query_time`,列不存在会抛 PostgreSQL `42703`。
-回填取 `created_time`(即立刻到期),方向安全;回填不带 `status` 条件,避免在 SQL 里抄第三份
-`IN ('CREATED','REFUNDING')`。文件末尾有三句核对 SQL(列类型 / NULL 计数期望 0 / 索引定义)。
+该文件含加列 + 加索引 + 回填,**必须在部署新代码之前跑完**——新代码的扫描条件含 `next_query_time`,
+列不存在会抛 PostgreSQL `42703`(顺序现已满足)。回填取 `created_time`(即立刻到期),方向安全;
+回填不带 `status` 条件,避免在 SQL 里抄第三份 `IN ('CREATED','REFUNDING')`。
+
+核对读数(脚本 `D:\lsy-regress\cursorcheck.py` 与 `filevsdb.py`,只读事务,凭据只打印长度):
+
+| 项 | 读数 |
+|---|---|
+| 列 | `next_query_time`,`timestamp with time zone`,`is_nullable=YES`,**`default=None`** |
+| 列注释 | 库里 `'下次对账时间(退避调度)'`,与 `V1.8` 的 `COMMENT ON` **逐字相同** |
+| 索引 | `idx_lsy_refund_reconcile ON (status, next_query_time)`,形状对 |
+| **文件 ↔ 活库** | 4 张表**列名集合逐表一致**(`pay_lsy_refund` 23/23);85 条列注释库/文件逐字相同,差 0 |
+| NULL 计数 | **0** —— ⚠ **但 `pay_lsy_refund` 是空表(`total_rows=0`),这条是空洞为真** |
+
+⚠ **回填只被证明「跑通了」,没被证明「逻辑对」**:表为空 ⇒ `UPDATE … WHERE next_query_time IS NULL`
+作用 0 行,「NULL = 0」不含任何信息。所以「不留 NULL」这条不变量**现在全靠 Java 侧撑着**,
+而列在库里 `default=None`——任何不走 `setNextQueryTime` 的插入都会产生一个**静默**游标为 NULL 的行。
+当场复核该洞是闭的:全仓 `refundMapper.insert` **只有一处**(`LsyRefundService:210`),
+紧邻其上的 `:208` 就是 `setNextQueryTime(...)`;模块内**无** XML mapper、**无** `@Insert`;
+`LsyRefundServiceTest` 对捕获到的实体断言 `getNextQueryTime()` 非空。
+
+**一处无害但需记下的分叉**:库里该列在 `attnum=23`(ALTER 追加到末尾),而 `V1.8` 文件里它排在第 14 位
+(`upstream_result_msg` 之后)。全新安装与既有库的列序不同。已核实**无消费者依赖列序**:
+`LsyEntityMappingTest` 比的是名字 `Set<String>`,MyBatis-Plus 按实体生成显式列清单,
+且模块内**零** `SELECT *`。故不改。
+
+**顺带核对(同一批只读查询)**:`lsy-menu-rename.sql` 也已执行——`sys_menu` 4 行里三个页面
+`component_path` 都是 `module_payment/collection/{order,refund,merchant}/index`,旧 `leshouying` 路径 **0 行**;
+`sys_role_menus` 授权 **8 行** = 管理员角色(id=1) 与租户角色(id=2) 各 4 条。
+⇒ 前端三页静默落 `404.vue` 的风险解除。
 
 ### 2.4 ~~下游回调重试通道**当前完全不通**~~ —— **已不成立(2026-09-22 复核实测)**