From d582a65978f2687c421df8de570b7de1be2c3d93 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Mon, 30 Mar 2026 13:46:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8E=86=E5=8F=B2=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=AF=BC=E5=85=A5=E8=A7=A3=E7=8E=AF=E4=B8=8E=E6=B5=81?= =?UTF-8?q?=E6=B0=B4=E6=9F=A5=E8=AF=A2SQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 18436 -> 18436 bytes AGENTS.md | 4 +- ...cdiProjectHistoryImportSubmittedEvent.java | 38 ++++ ...CcdiProjectHistoryImportEventListener.java | 27 +++ .../service/impl/CcdiProjectServiceImpl.java | 9 +- .../ccdi/project/CcdiBankStatementMapper.xml | 23 ++- .../CcdiBankStatementMapperXmlTest.java | 22 +++ .../impl/CcdiProjectServiceImplTest.java | 20 +- ...-03-29-agents-db-import-encoding-record.md | 17 ++ ...3-29-project-import-history-plan-record.md | 17 ++ ...3-29-project-import-history-test-record.md | 176 ++++++++++++++++++ 11 files changed, 330 insertions(+), 23 deletions(-) create mode 100644 ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/event/CcdiProjectHistoryImportSubmittedEvent.java create mode 100644 ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportEventListener.java create mode 100644 docs/reports/implementation/2026-03-29-agents-db-import-encoding-record.md create mode 100644 docs/tests/records/2026-03-29-project-import-history-test-record.md diff --git a/.DS_Store b/.DS_Store index 283a74af5a6e4dd3d937182611d1e469b725a94b..04c8fe28e66e6a5a0f3d4be68933dbeec7be0b5a 100644 GIT binary patch delta 97 zcmZpfz}PZ@al->m#vPL%Y8Hx1R971t8R;k(n;6yVC{$Y-8R#gOSQ<^XP&VE?S1XTo TGnc_NPNLLivuv6->=q{GItoSxlP?N6Zl0r+$GVx@;3_8;nJm`L><)j~0foyH Af&c&j diff --git a/AGENTS.md b/AGENTS.md index c88fe397..b09dfae2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ - 前端开发直接在当前分支进行,不需要额外创建 git worktree - 测试结束后,自动关闭测试过程中启动的前后端进程 - 遇到 MCP 数据库操作时,使用项目配置文件中的数据库连接信息 -- 执行包含中文内容的 MySQL SQL 脚本时,禁止直接手写 `mysql -e` 或普通重定向执行;必须优先使用 `bin/mysql_utf8_exec.sh `,确保会话字符集为 `utf8mb4`,避免写入乱码 +- 执行包含中文内容的 MySQL SQL 脚本或数据库导入时,禁止直接手写 `mysql -e` 或普通重定向执行;必须优先使用 `bin/mysql_utf8_exec.sh `,确保会话字符集为 `utf8mb4`,避免导入或写入乱码 - 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code`、`indicator_code`、`param_code` 时,禁止混用大小写风格 --- @@ -164,7 +164,7 @@ return AjaxResult.success(result); - 非业务字段如 `create_by`、`create_time` 由后端自动维护 - 前端表单不要暴露通用审计字段 - 新增菜单、字典、初始化数据时,同步补充 SQL 脚本 -- 执行数据库脚本前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新时默认使用 `bin/mysql_utf8_exec.sh` +- 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh` ### 前端规范 diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/event/CcdiProjectHistoryImportSubmittedEvent.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/event/CcdiProjectHistoryImportSubmittedEvent.java new file mode 100644 index 00000000..3d939563 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/event/CcdiProjectHistoryImportSubmittedEvent.java @@ -0,0 +1,38 @@ +package com.ruoyi.ccdi.project.domain.event; + +import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; + +/** + * 历史项目导入提交事件 + */ +public class CcdiProjectHistoryImportSubmittedEvent { + + private final Long targetProjectId; + private final Integer targetLsfxProjectId; + private final CcdiProjectImportHistoryDTO dto; + private final String operator; + + public CcdiProjectHistoryImportSubmittedEvent(Long targetProjectId, Integer targetLsfxProjectId, + CcdiProjectImportHistoryDTO dto, String operator) { + this.targetProjectId = targetProjectId; + this.targetLsfxProjectId = targetLsfxProjectId; + this.dto = dto; + this.operator = operator; + } + + public Long getTargetProjectId() { + return targetProjectId; + } + + public Integer getTargetLsfxProjectId() { + return targetLsfxProjectId; + } + + public CcdiProjectImportHistoryDTO getDto() { + return dto; + } + + public String getOperator() { + return operator; + } +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportEventListener.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportEventListener.java new file mode 100644 index 00000000..0c3c3573 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportEventListener.java @@ -0,0 +1,27 @@ +package com.ruoyi.ccdi.project.service.impl; + +import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent; +import com.ruoyi.ccdi.project.service.ICcdiProjectHistoryImportService; +import jakarta.annotation.Resource; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +/** + * 历史项目导入事件监听器 + */ +@Component +public class CcdiProjectHistoryImportEventListener { + + @Resource + private ICcdiProjectHistoryImportService historyImportService; + + @EventListener + public void onSubmitted(CcdiProjectHistoryImportSubmittedEvent event) { + historyImportService.submitImport( + event.getTargetProjectId(), + event.getTargetLsfxProjectId(), + event.getDto(), + event.getOperator() + ); + } +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java index a43fbcda..57444f96 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java @@ -7,11 +7,11 @@ import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO; +import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; -import com.ruoyi.ccdi.project.service.ICcdiProjectHistoryImportService; import com.ruoyi.ccdi.project.service.ICcdiProjectService; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.lsfx.client.LsfxAnalysisClient; @@ -20,6 +20,7 @@ import com.ruoyi.lsfx.domain.response.GetTokenResponse; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; @@ -46,7 +47,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { private LsfxAnalysisClient lsfxAnalysisClient; @Resource - private ICcdiProjectHistoryImportService historyImportService; + private ApplicationEventPublisher applicationEventPublisher; @Override @Transactional(rollbackFor = Exception.class) @@ -139,7 +140,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { - historyImportService.submitImport(project.getProjectId(), project.getLsfxProjectId(), dto, operator); + applicationEventPublisher.publishEvent( + new CcdiProjectHistoryImportSubmittedEvent(project.getProjectId(), project.getLsfxProjectId(), dto, operator) + ); } }); return project; diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml index a19d2c8c..2a273c36 100644 --- a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml @@ -49,16 +49,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select bank_statement_id, project_id, LE_ID, ACCOUNT_ID, group_id, - LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE, - TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE, - CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO, - customer_bank, customer_reference, customer_cert_no, customer_social_credit_code, USER_MEMO, BANK_COMMENTS, - BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE, - internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by, - meta_json, no_balance, begin_balance, end_balance, - override_bs_id, payment_method, cret_no - from ccdi_bank_statement + bank_statement_id, project_id, LE_ID, ACCOUNT_ID, group_id, + LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE, + TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE, + CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO, + customer_bank, customer_reference, customer_cert_no, customer_social_credit_code, USER_MEMO, BANK_COMMENTS, + BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE, + internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by, + meta_json, no_balance, begin_balance, end_balance, + override_bs_id, payment_method, cret_no @@ -304,8 +303,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" FROM ccdi_bank_statement bs - bs.project_id = #{projectId} - AND bs.batch_id = #{batchId} + (bs.project_id = #{projectId}) + AND (bs.batch_id = #{batchId}) AND () = ]]> CASE diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java index 5ad1bcc3..000babd5 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java @@ -16,6 +16,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -172,6 +173,27 @@ class CcdiBankStatementMapperXmlTest { } } + @Test + void selectStatementsForHistoryImport_shouldNotGenerateDuplicatedSelectKeyword() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper.selectStatementsForHistoryImport"); + + Map params = new HashMap<>(); + params.put("projectId", 48L); + params.put("batchId", 17094); + params.put("startDate", "2026-03-17"); + params.put("endDate", "2026-03-18"); + + BoundSql boundSql = mappedStatement.getBoundSql(params); + String sql = boundSql.getSql().replaceAll("\\s+", " ").trim(); + + assertFalse(sql.startsWith("SELECT select"), sql); + assertTrue(sql.startsWith("SELECT bank_statement_id"), sql); + assertFalse(sql.contains("FROM ccdi_bank_statement FROM ccdi_bank_statement"), sql); + assertFalse(sql.contains("?AND"), sql); + assertTrue(sql.contains("WHERE (bs.project_id = ?) AND (bs.batch_id = ?) AND ( CASE"), sql); + } + @Test void insertBatch_shouldAvoidUpdatingAutoIncrementPrimaryKeyInDuplicateBranch() throws Exception { try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) { diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java index 87f3b434..ec9fa589 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java @@ -7,20 +7,22 @@ import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO; +import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; -import com.ruoyi.ccdi.project.service.ICcdiProjectHistoryImportService; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.lsfx.client.LsfxAnalysisClient; import com.ruoyi.lsfx.domain.response.GetTokenResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.List; @@ -31,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -50,7 +51,7 @@ class CcdiProjectServiceImplTest { private LsfxAnalysisClient lsfxAnalysisClient; @Mock - private ICcdiProjectHistoryImportService historyImportService; + private ApplicationEventPublisher applicationEventPublisher; @Test void shouldCountTaggingProjectsSeparately() { @@ -143,7 +144,7 @@ class CcdiProjectServiceImplTest { } @Test - void shouldCreateProjectThenSubmitHistoryImportAfterCommit() { + void shouldCreateProjectThenPublishHistoryImportEventAfterCommit() { CcdiProjectImportHistoryDTO dto = new CcdiProjectImportHistoryDTO(); dto.setProjectName("新项目"); dto.setDescription("从历史导入"); @@ -165,11 +166,18 @@ class CcdiProjectServiceImplTest { assertNotNull(project); assertEquals(90L, project.getProjectId()); assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size()); - verify(historyImportService, never()).submitImport(any(), any(), any(), anyString()); + verify(applicationEventPublisher, never()).publishEvent(any()); TransactionSynchronizationManager.getSynchronizations().forEach(sync -> sync.afterCommit()); - verify(historyImportService).submitImport(90L, 3001, dto, "tester"); + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Object.class); + verify(applicationEventPublisher).publishEvent(eventCaptor.capture()); + CcdiProjectHistoryImportSubmittedEvent event = + (CcdiProjectHistoryImportSubmittedEvent) eventCaptor.getValue(); + assertEquals(90L, event.getTargetProjectId()); + assertEquals(3001, event.getTargetLsfxProjectId()); + assertEquals("tester", event.getOperator()); + assertEquals(dto, event.getDto()); } finally { TransactionSynchronizationManager.clearSynchronization(); } diff --git a/docs/reports/implementation/2026-03-29-agents-db-import-encoding-record.md b/docs/reports/implementation/2026-03-29-agents-db-import-encoding-record.md new file mode 100644 index 00000000..30c55a86 --- /dev/null +++ b/docs/reports/implementation/2026-03-29-agents-db-import-encoding-record.md @@ -0,0 +1,17 @@ +# AGENTS 数据库导入编码约束补充记录 + +## 本次改动 + +- 更新根目录 `AGENTS.md` 协作约定,明确 MySQL SQL 执行与数据库导入都必须保证 `utf8mb4` 会话字符集 +- 更新根目录 `AGENTS.md` 数据库规范,明确中文数据导入场景默认使用 `bin/mysql_utf8_exec.sh` +- 将“避免导入乱码”写入说明,降低后续执行数据库导入时遗漏编码设置的风险 + +## 结论 + +- 仓库级协作约定已覆盖“数据库导入编码”问题,不再仅限于中文 SQL 脚本执行场景 +- 后续凡是导入含中文数据的 SQL 或数据库内容,默认按 `utf8mb4` 会话执行 + +## 验证说明 + +- 已人工检查文档保存路径位于 `docs/reports/implementation/` +- 本次仅修改文档,无需运行代码测试 diff --git a/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md b/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md index f174ed3d..b757ae1f 100644 --- a/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md +++ b/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md @@ -84,3 +84,20 @@ - 扩展 `project-import-history-dialog-behavior.test.js`,补齐“请选择历史项目”“请输入新项目名称”两条关键校验文案断言,锁定导入弹窗最小交互约束 - 扩展 `project-detail-tagging-polling.test.js`,补齐 `this.$route.params.projectId` 与 `projectStatusPolling` 代码痕迹断言,确认新项目详情页继续以路由参数承接并保留状态轮询能力 - 完成前端回归验证:`cd ruoyi-ui && node tests/unit/project-import-history-submit-flow.test.js && node tests/unit/project-import-history-dialog-layout.test.js && node tests/unit/project-import-history-dialog-behavior.test.js && node tests/unit/upload-data-file-list-table.test.js && node tests/unit/upload-data-delete-retag-copy.test.js && node tests/unit/upload-data-history-import-readonly.test.js && node tests/unit/project-detail-tagging-polling.test.js` + +### 2026-03-29 Task 6 后端历史导入解环修复 + +- 根据联调时出现的 `CcdiBankTagServiceImpl -> CcdiProjectServiceImpl -> CcdiProjectHistoryImportServiceImpl` 循环依赖,确认根因是 `CcdiProjectServiceImpl#importFromHistory` 直接依赖历史导入服务,而历史导入服务又会回调打标服务 +- 新增 `CcdiProjectHistoryImportSubmittedEvent` 与 `CcdiProjectHistoryImportEventListener`,将“创建项目后提交历史导入任务”改为事务提交后发布事件,再由监听器委托 `ICcdiProjectHistoryImportService` +- 调整 `CcdiProjectServiceImpl` 与 `CcdiProjectServiceImplTest`,锁定“导入历史项目在 `afterCommit` 阶段发布事件”的行为,移除对 `ICcdiProjectHistoryImportService` 的直接注入 +- 验证命令:`mvn -pl ccdi-project -am -Dtest=CcdiProjectServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test` +- 启动验证:`mvn -pl ruoyi-admin -am -DskipTests package` 后执行 `java -jar ruoyi-admin/target/ruoyi-admin.jar`,确认默认启动不再报循环依赖且应用正常启动 + +### 2026-03-29 Task 7 后端历史导入联调修复 + +- 针对“通过导入创建的项目一直进行中,上传文件列表也不正确”做联调排查,先执行 `bin/mysql_utf8_exec.sh sql/migration/2026-03-29-add-file-upload-history-import-source-fields.sql`,将 `ccdi_file_upload_record` 缺失的 `source_type`、`source_project_id`、`source_project_name` 字段补齐到联调数据库 +- 继续排查异步导入日志,定位 `CcdiBankStatementMapper.xml` 中 `selectStatementsForHistoryImport` 存在两处 SQL 组装问题:一处重复拼出 `SELECT select ... FROM ccdi_bank_statement FROM ...`,另一处在日期过滤场景下与动态条件相邻,导致联调日志出现 `?AND`/`17094AND` 形式的拼接结果 +- 调整 `selectCcdiBankStatementVo` 为纯列片段,并将历史导入查询的基础条件包裹为 `(bs.project_id = #{projectId}) AND (bs.batch_id = #{batchId})`,避免动态日期条件继续与前置参数直接粘连 +- 扩展 `CcdiBankStatementMapperXmlTest`,新增历史导入查询 SQL 渲染断言,锁定“不重复生成 `select` 关键字”与“不生成 `?AND`”两个回归点 +- 验证命令:`mvn -pl ccdi-project -am -Dtest=CcdiBankStatementMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test` +- 联调验证:`mvn -pl ruoyi-admin -am -DskipTests package` 后启动 `java -jar ruoyi-admin/target/ruoyi-admin.jar`,重新调用 `/ccdi/project/import` 创建项目 `61`;接口轮询确认项目状态从 `0` 进入 `3` 再落到 `1`,上传记录列表返回 `14` 条历史导入文件;数据库确认 `ccdi_project.status = 1`、`target_count = 12`、`ccdi_file_upload_record` 共 `14` 条且 `source_type = HISTORY_IMPORT`、`ccdi_bank_statement` 共 `20` 条 diff --git a/docs/tests/records/2026-03-29-project-import-history-test-record.md b/docs/tests/records/2026-03-29-project-import-history-test-record.md new file mode 100644 index 00000000..717fd171 --- /dev/null +++ b/docs/tests/records/2026-03-29-project-import-history-test-record.md @@ -0,0 +1,176 @@ +# 2026-03-29 历史项目导入功能测试记录 + +## 测试目标 + +- 按实施计划验证“项目管理 -> 导入历史项目”功能的真实链路 +- 通过浏览器、数据库、后端日志交叉确认以下能力: + - 历史项目列表是否只展示已完成、已归档项目 + - 导入弹窗校验、项目创建、成功跳转是否正常 + - 异步历史导入是否生成文件记录与流水数据 + - 详情页上传记录是否展示来源信息、是否具备只读限制 + +## 测试环境 + +- 测试日期:2026-03-29 +- 前端地址:`http://127.0.0.1:1026` +- 后端地址:`http://127.0.0.1:62318` +- 数据库:`116.62.17.81:3307/ccdi` +- 登录账号:`admin/admin123` + +## 测试前检查 + +### 1. 默认启动失败 + +- 使用当前代码重新打包 `ruoyi-admin` 后,按默认参数启动后端,应用未能启动成功。 +- 日志报错为 Spring Bean 循环依赖: + - `CcdiBankTagServiceImpl` + - `CcdiProjectServiceImpl` + - `CcdiProjectHistoryImportServiceImpl` +- 结论:当前代码在默认配置下无法完成联调测试。 + +### 2. 为继续测试临时放开循环依赖 + +- 仅为继续验证后续功能,临时使用启动参数: + +```bash +java -jar ruoyi-admin/target/ruoyi-admin.jar --spring.main.allow-circular-references=true +``` + +- 说明:以下浏览器与接口测试结果均建立在该临时启动参数下,不代表默认启动已通过。 + +### 3. 数据库结构预检查 + +- 执行 `SHOW COLUMNS FROM ccdi_file_upload_record;` +- 结果:数据库中缺少以下字段: + - `source_type` + - `source_project_id` + - `source_project_name` + +## 浏览器测试记录 + +### 1. 历史项目弹窗与列表 + +- 登录后进入“初核项目管理”页面。 +- 点击快捷方式“导入历史项目”,弹窗可正常打开。 +- 弹窗中看到“新项目名称 / 备注 / 流水时间 / 历史项目列表”布局,符合计划描述。 +- 历史项目列表实际加载成功,只展示“已完成 / 已归档”项目。 +- 浏览器看到的历史项目样本包括: + - `phase2联调验证-20260322-1` + - `0320` + - `测试0319` + - `测试0323` + +### 2. 表单校验 + +- 未选择历史项目直接点击“导入”,页面提示:`请选择历史项目` +- 选择历史项目后,不填写新项目名称直接点击“导入”,页面提示:`请输入新项目名称` +- 结论:前端最小校验生效。 + +### 3. 真实提交 + +- 选择历史项目: + - `49 phase2联调验证-20260322-1` + - `48 0320` +- 填写: + - 新项目名称:`自动测试-历史导入-20260329-1017` + - 备注:`测试历史项目导入链路` + - 流水时间:`2026-03-17` 至 `2026-03-18` +- 点击“导入”后: + - 页面提示:`历史项目导入任务已开始` + - 自动跳转至详情页:`/ccdiProject/detail/57` + - 新项目标题显示为 `自动测试-历史导入-20260329-1017` + - 项目状态显示为 `进行中` + - 上传记录列表显示 `暂无数据` + +## 数据库验证 + +### 1. 历史项目样本确认 + +- 查询 `ccdi_file_upload_record` 后确认以下项目存在可用于导入的 `parsed_success` 记录: + - `49 phase2联调验证-20260322-1`:`11` 条 + - `48 0320`:`9` 条 + - `46 测试0319`:`4` 条 + +### 2. 新项目创建结果 + +- 提交后数据库生成新项目: + +```text +project_id = 57 +project_name = 自动测试-历史导入-20260329-1017 +status = 0 +lsfx_project_id = 1001 +target_count = 0 +``` + +### 3. 导入结果落库检查 + +- 查询 `ccdi_file_upload_record`: + - `project_id = 57`,结果 `0` 条 +- 查询 `ccdi_bank_statement`: + - `project_id = 57`,结果 `0` 条 + +## 日志验证 + +### 1. 项目创建成功 + +- 日志记录了项目创建成功: + - `projectId=57` + - `projectName=自动测试-历史导入-20260329-1017` + - 状态初始化为 `进行中` + +### 2. 异步导入线程失败 + +- 后端异步线程 `file-upload-1` 在读取来源上传记录时抛出异常: + +```text +Unknown column 'source_type' in 'field list' +``` + +- 对应 SQL 为: + +```sql +select id, project_id, lsfx_project_id, log_id, file_name, file_size, + file_status, source_type, source_project_id, source_project_name, + enterprise_names, account_nos, error_message, upload_time, upload_user +from ccdi_file_upload_record +where project_id in (?, ?) + and file_status = 'parsed_success' + and log_id is not null +``` + +- 结论:异步导入任务在读取来源记录阶段即失败,后续的流水复制、文件记录写入、打标触发都没有执行成功。 + +## 测试结论 + +### 已验证通过 + +- 导入历史项目弹窗可打开 +- 历史项目列表能够加载真实数据,且列表范围符合“已完成/已归档” +- 前端校验提示正常 +- 点击导入后可创建新项目 +- 提交成功后提示文案正确,且会跳转到新项目详情页 + +### 验证失败 + +- 默认配置下后端无法启动,存在循环依赖 +- 联调用数据库缺少历史导入所需来源字段 +- 异步历史导入任务执行失败 +- 新项目未生成任何上传记录 +- 新项目未生成任何流水数据 + +### 当前无法继续验证的项 + +- 上传记录“历史导入 · 来源项目名”展示 +- 历史导入文件记录删除入口是否隐藏 +- 导入完成后自动触发打标、结果刷新、统计刷新 + +## 阻塞问题汇总 + +1. `CcdiBankTagServiceImpl -> CcdiProjectServiceImpl -> CcdiProjectHistoryImportServiceImpl` 存在循环依赖,导致默认启动失败。 +2. 联调用数据库 `ccdi_file_upload_record` 未执行来源字段迁移,导致异步导入 SQL 直接失败。 + +## 清理记录 + +- 已停止本次测试临时启动的后端进程 +- 已停止本次测试临时启动的前端开发进程