From 66b0935b11d601df909fdbe663a88806aff69371 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Wed, 28 Oct 2026 13:42:03 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=B9=E7=94=A8=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/sql/README.md | 2 + .../V35__widen_finance_fee_command_action.sql | 3 + deploy/sql/海外区域统一增量-MySQL8.sql | 2 +- .../src/main/resources/db/lastleg.sql | 2 +- .../OverseasFeeCommandSchemaContractTest.java | 83 +++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 deploy/sql/migrations/overseas/baiyue_lastleg/V35__widen_finance_fee_command_action.sql create mode 100644 lastleg-service/src/test/java/com/baiyue/lastleg/financefee/OverseasFeeCommandSchemaContractTest.java diff --git a/deploy/sql/README.md b/deploy/sql/README.md index 762fab2a8..f41a55170 100644 --- a/deploy/sql/README.md +++ b/deploy/sql/README.md @@ -1,5 +1,7 @@ # 单测试环境数据库空库初始化 +`migrations/overseas/baiyue_lastleg/V35__widen_finance_fee_command_action.sql` 修复海外人工费用资金重试报 S0500:仅将 `baiyue_lastleg.ll_finance_fee_command.action` 扩为 `VARCHAR(32)`,容纳 20 字符的 `MANUAL_FUNDING_RETRY`,保留中文备注、非空及原幂等唯一键。Lastleg 源基线和海外空库基线同步。已有环境先核对目标库及列定义,经授权执行 V35、回查列长度和记录总数,再通过原费用重试入口验证;可重复执行,不修改费用、审核、余额或资金记录,不重放空库基线。DDL 隐式提交,遇错停止;本次仅准备修复文件,尚未执行测试环境 SQL 或部署,文件存在不表示已经执行。 + `overseas-fee-menu-finance-parent-repair.sql` 为已有 `baiyue_iam` 的定向权限修复:将菜单 `platform:overseas-fee:list` 改名为“海外费用明细”,父菜单设为 `platform:finance`(财务管理),保留原路由和 PLATFORM_ADMIN 授权;可重复执行,目标菜单缺失或权限身份冲突时停止。 `overseas-procurement-test-data-reset.sql` 为用户已确认可丢弃采购造数后的测试专用重置脚本:仅清空六张当前采购专属表,删除二十一张旧采购表;不会修改共用费用、余额、文件对象、库存或其他业务表,也不处理共用表的历史采购关联记录或退款。先停止采购写入并更新分离后的后端,完整执行文件、遇错停止,禁止 `--force`;DELETE 使用事务,后续 DROP 为隐式提交,不能整体回滚。无需存储过程、DELIMITER 或确认变量;成功返回 `procurement_test_reset_complete`,两个数量均为 0,成功后可重复执行。2026-10-27 已在本地隔离 MySQL 8.0.40 使用真实 27 表定义、三张 APPROVED 单及收货/出库/旧费用记录验证两次执行成功,共用表对照数据不变;未连接用户目标数据库,不表示部署完成。 diff --git a/deploy/sql/migrations/overseas/baiyue_lastleg/V35__widen_finance_fee_command_action.sql b/deploy/sql/migrations/overseas/baiyue_lastleg/V35__widen_finance_fee_command_action.sql new file mode 100644 index 000000000..1efeda27e --- /dev/null +++ b/deploy/sql/migrations/overseas/baiyue_lastleg/V35__widen_finance_fee_command_action.sql @@ -0,0 +1,3 @@ +-- 海外费用资金重试动作长度修复:兼容 MANUAL_FUNDING_RETRY 等费用幂等动作。 +ALTER TABLE baiyue_lastleg.ll_finance_fee_command + MODIFY COLUMN action VARCHAR(32) NOT NULL COMMENT '费用写操作动作:CREATE、SUBMIT、REVIEW、ADJUST、VOID或资金重试'; diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index 1b376435e..74ef68f36 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -917,7 +917,7 @@ CREATE TABLE ll_return_carton_stock_receipt ( CREATE TABLE ll_finance_fee_command ( id bigint NOT NULL COMMENT '幂等命令ID', operator_id varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作人ID', - action varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'CREATE、SUBMIT、REVIEW、ADJUST或VOID', + action varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '费用写操作动作:CREATE、SUBMIT、REVIEW、ADJUST、VOID或资金重试', idempotency_key varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作幂等键', request_hash char(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '请求内容SHA-256', source_fee_id bigint NOT NULL COMMENT '稳定费用来源ID', diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index 9efa9318b..d68023f44 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -916,7 +916,7 @@ PREPARE ll_finance_fee_ddl FROM @ddl; EXECUTE ll_finance_fee_ddl; DEALLOCATE PRE CREATE TABLE IF NOT EXISTS ll_finance_fee_command ( id BIGINT NOT NULL COMMENT '幂等命令ID', operator_id VARCHAR(128) NOT NULL COMMENT '操作人ID', - action VARCHAR(16) NOT NULL COMMENT 'CREATE、SUBMIT、REVIEW、ADJUST或VOID', idempotency_key VARCHAR(128) NOT NULL COMMENT '操作幂等键', + action VARCHAR(32) NOT NULL COMMENT '费用写操作动作:CREATE、SUBMIT、REVIEW、ADJUST、VOID或资金重试', idempotency_key VARCHAR(128) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256', source_fee_id BIGINT NOT NULL COMMENT '稳定费用来源ID', source_version INT NOT NULL COMMENT '命令结果版本', created_at DATETIME(3) NOT NULL COMMENT '命令创建时间', PRIMARY KEY(id), UNIQUE KEY uk_ll_finance_fee_command(operator_id,action,idempotency_key), diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/financefee/OverseasFeeCommandSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/financefee/OverseasFeeCommandSchemaContractTest.java new file mode 100644 index 000000000..47b80f098 --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/financefee/OverseasFeeCommandSchemaContractTest.java @@ -0,0 +1,83 @@ +package com.baiyue.lastleg.financefee; + +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import com.baiyue.lastleg.financefee.entity.OverseasFeeCommand; +import com.baiyue.lastleg.financefee.mapper.OverseasFeeCommandMapper; +import org.junit.jupiter.api.Test; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.*; + +class OverseasFeeCommandSchemaContractTest { + private static final String MIGRATION = "../deploy/sql/migrations/overseas/baiyue_lastleg/V35__widen_finance_fee_command_action.sql"; + + /** 两份初始化入口均须容纳完整重试动作,避免新环境再次截断幂等命令。 */ + @Test void initializationEntriesSupportFundingRetryAction() throws Exception { + for (String path : List.of("src/main/resources/db/lastleg.sql", "../deploy/sql/海外区域统一增量-MySQL8.sql")) { + String ddl = commandTable(Files.readString(Path.of(path))); + var action = Pattern.compile("\\baction\\s+varchar\\((\\d+)\\)", Pattern.CASE_INSENSITIVE).matcher(ddl); + assertThat(action.find()).as(path).isTrue(); + assertThat(Integer.parseInt(action.group(1))).as(path).isGreaterThanOrEqualTo(32); + assertThat(ddl).contains("uk_ll_finance_fee_command"); + } + } + + /** 真实 Mapper 复现旧列截断,迁移后重试动作可落库回读且幂等唯一约束仍生效。 */ + @Test void migrationAllowsFundingRetryAndPreservesIdempotency() throws Exception { + var dataSource = new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", ""); + var jdbc = new JdbcTemplate(dataSource); + jdbc.execute("CREATE SCHEMA baiyue_lastleg"); + jdbc.execute("SET SCHEMA baiyue_lastleg"); + // 每次 Mapper 连接都使用相同服务库,不依赖单个连接的当前 schema。 + dataSource.setUrl(dataSource.getUrl() + ";SCHEMA=baiyue_lastleg"); + String ddl = commandTable(Files.readString(Path.of("src/main/resources/db/lastleg.sql"))); + jdbc.execute(ddl.replace("VARCHAR(32)", "VARCHAR(16)")); + var configuration = new MybatisConfiguration(); + configuration.setMapUnderscoreToCamelCase(true); + configuration.addMapper(OverseasFeeCommandMapper.class); + var factory = new MybatisSqlSessionFactoryBean(); + factory.setDataSource(dataSource); + factory.setConfiguration(configuration); + factory.afterPropertiesSet(); + var mapper = new SqlSessionTemplate(factory.getObject()).getMapper(OverseasFeeCommandMapper.class); + var command = new OverseasFeeCommand(); + command.setId(1L); + command.setOperatorId("operator"); + command.setAction("MANUAL_FUNDING_RETRY"); + command.setIdempotencyKey("retry-1"); + command.setRequestHash("a".repeat(64)); + command.setSourceFeeId(701L); + command.setSourceVersion(1); + command.setCreatedAt(LocalDateTime.now()); + assertThatThrownBy(() -> mapper.insert(command)).hasStackTraceContaining("Value too long"); + + String migration = Files.readString(Path.of(MIGRATION)); + assertThat(migration).contains("ALTER TABLE baiyue_lastleg.ll_finance_fee_command", "MODIFY COLUMN action VARCHAR(32) NOT NULL COMMENT"); + jdbc.execute(migration); + jdbc.execute(migration); + assertThat(mapper.insert(command)).isEqualTo(1); + var stored = mapper.find("operator", "MANUAL_FUNDING_RETRY", "retry-1"); + assertThat(stored.getAction()).isEqualTo("MANUAL_FUNDING_RETRY"); + assertThat(stored.getSourceFeeId()).isEqualTo(701L); + command.setId(2L); + assertThatThrownBy(() -> mapper.insert(command)).hasStackTraceContaining("UK_LL_FINANCE_FEE_COMMAND"); + assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_finance_fee_command", Integer.class)).isEqualTo(1); + } + + /** 仅提取费用命令实际建表定义,避免手写测试结构与正式基线脱节。 */ + private static String commandTable(String sql) { + var table = Pattern.compile("CREATE TABLE(?: IF NOT EXISTS)? ll_finance_fee_command \\([\\s\\S]*?\\) ENGINE=InnoDB[^\\r\\n]*;").matcher(sql); + assertThat(table.find()).isTrue(); + return table.group(); + } +}