From 4a75d68bf627954927fbcbdb57d01c950c18d96e Mon Sep 17 00:00:00 2001 From: wjj <2069666735@qq.com> Date: Mon, 20 Jul 2026 11:03:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=81=E6=B0=B4=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E8=BA=AB=E4=BB=BD=E8=AF=81=E5=8F=B7=E5=9B=9E=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CcdiFileUploadServiceImpl.java | 25 +++++++- .../impl/CcdiFileUploadServiceImplTest.java | 63 ++++++++++++++++++- ...cret-no-backfill-backend-implementation.md | 23 +++++++ ...2026-07-20-bank-upload-cret-no-backfill.md | 36 +++++++++++ 4 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 docs/plans/backend/2026-07-20-bank-upload-cret-no-backfill-backend-implementation.md create mode 100644 docs/reports/implementation/2026-07-20-bank-upload-cret-no-backfill.md diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java index 6746822b..44da99d8 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java @@ -46,6 +46,7 @@ import java.nio.file.StandardCopyOption; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.regex.Matcher; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; @@ -819,7 +820,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { enterpriseNamesStr, accountNosStr); log.info("【文件上传】步骤7: 获取流水数据"); - FetchBankStatementResult fetchResult = fetchAndSaveBankStatements(projectId, lsfxProjectId, logId); + String fallbackCretNo = extractIdCardFromFileName(record.getFileName()); + FetchBankStatementResult fetchResult = fetchAndSaveBankStatements(projectId, lsfxProjectId, logId, + fallbackCretNo); if (!fetchResult.isSuccess()) { updateFailedRecord(record, fetchResult.getErrorMessage()); return; @@ -892,7 +895,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { * @param logId 文件ID */ private FetchBankStatementResult fetchAndSaveBankStatements(Long projectId, Integer groupId, - Integer logId) { + Integer logId, + String fallbackCretNo) { log.info("【文件上传】开始获取流水数据: projectId={}, groupId={}, logId={}", projectId, groupId, logId); @@ -957,6 +961,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { if (statement != null) { statement.setBatchId(logId); statement.setProjectId(projectId); + fillMissingCretNo(statement, fallbackCretNo); normalizeDedupFields(statement); batchList.add(statement); @@ -1000,6 +1005,22 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { } } + private String extractIdCardFromFileName(String fileName) { + if (!StringUtils.hasText(fileName)) { + return null; + } + String mainFileName = getMainFileName(fileName); + Matcher matcher = UPLOAD_FILE_NAME_ID_CARD_PATTERN.matcher(mainFileName); + return matcher.find() ? matcher.group() : null; + } + + private void fillMissingCretNo(CcdiBankStatement statement, String fallbackCretNo) { + if (statement == null || StringUtils.hasText(statement.getCretNo()) || !StringUtils.hasText(fallbackCretNo)) { + return; + } + statement.setCretNo(fallbackCretNo); + } + private void cleanupBankStatements(Long projectId, Integer logId) { bankStatementMapper.deleteByProjectIdAndBatchId(projectId, logId); } diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java index 4bf2da13..88bb5507 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java @@ -6,6 +6,7 @@ import ch.qos.logback.core.read.ListAppender; import com.alibaba.excel.EasyExcel; import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.enums.TriggerType; +import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement; import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO; import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord; import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper; @@ -470,6 +471,44 @@ class CcdiFileUploadServiceImplTest { ), eq("原始流水.xlsx")); } + @Test + void processFileAsync_shouldBackfillMissingCretNoFromUploadFileName() throws IOException { + AtomicReference> insertedStatements = new AtomicReference<>(); + doAnswer(invocation -> { + List statements = invocation.getArgument(0); + insertedStatements.set(new ArrayList<>(statements)); + return statements.size(); + }).when(bankStatementMapper).insertBatch(any()); + + CcdiProject project = new CcdiProject(); + project.setProjectId(PROJECT_ID); + when(projectMapper.selectById(PROJECT_ID)).thenReturn(project); + when(bankStatementMapper.countMatchedStaffCountByProjectId(PROJECT_ID)).thenReturn(1); + when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any(), eq("张三_330101199001010011_流水.xlsx"))) + .thenReturn(buildUploadResponse()); + when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID))) + .thenReturn(buildCheckParseStatusResponse(false)); + when(lsfxClient.getFileUploadStatus(any())).thenReturn(buildParsedSuccessStatusResponse()); + when(lsfxClient.getBankStatement(any(GetBankStatementRequest.class))) + .thenAnswer(invocation -> { + GetBankStatementRequest request = invocation.getArgument(0); + if (Integer.valueOf(1).equals(request.getPageSize())) { + return buildBankStatementCountResponse(1); + } + return buildBankStatementResponseWithBlankCretNo(); + }); + + CcdiFileUploadRecord record = buildRecord(); + record.setFileName("张三_330101199001010011_流水.xlsx"); + Path tempFile = createTempFile(); + + service.processFileAsync(PROJECT_ID, LSFX_PROJECT_ID, tempFile.toString(), RECORD_ID, "batch-1", record); + + assertNotNull(insertedStatements.get()); + assertEquals(1, insertedStatements.get().size()); + assertEquals("330101199001010011", insertedStatements.get().get(0).getCretNo()); + } + @Test void processFileAsync_shouldKeepOriginalFileNameWhenStatusReturnsDifferentName() throws IOException { when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any(), org.mockito.ArgumentMatchers.anyString())) @@ -717,7 +756,8 @@ class CcdiFileUploadServiceImplTest { "fetchAndSaveBankStatements", PROJECT_ID, LSFX_PROJECT_ID, - LOG_ID + LOG_ID, + null ); assertTrue(Boolean.TRUE.equals(ReflectionTestUtils.getField(result, "success"))); @@ -929,6 +969,27 @@ class CcdiFileUploadServiceImplTest { return response; } + private GetBankStatementResponse buildBankStatementCountResponse(int totalCount) { + GetBankStatementResponse.BankStatementData data = new GetBankStatementResponse.BankStatementData(); + data.setTotalCount(totalCount); + + GetBankStatementResponse response = new GetBankStatementResponse(); + response.setData(data); + return response; + } + + private GetBankStatementResponse buildBankStatementResponseWithBlankCretNo() { + GetBankStatementResponse.BankStatementItem item = new GetBankStatementResponse.BankStatementItem(); + item.setBankStatementId(1L); + item.setLeName("测试主体"); + item.setAccountNo("62220001"); + item.setCustomerName("交易对手"); + item.setCustomerAccountNo("62220002"); + item.setDrAmount(BigDecimal.TEN); + item.setCretNo(null); + return buildBankStatementResponseWithItems(1, List.of(item)); + } + private void invokeSubmitTasksAsync(List tempFilePaths, List records, String batchId) throws Exception { diff --git a/docs/plans/backend/2026-07-20-bank-upload-cret-no-backfill-backend-implementation.md b/docs/plans/backend/2026-07-20-bank-upload-cret-no-backfill-backend-implementation.md new file mode 100644 index 00000000..5f30c82c --- /dev/null +++ b/docs/plans/backend/2026-07-20-bank-upload-cret-no-backfill-backend-implementation.md @@ -0,0 +1,23 @@ +# 上传流水身份证号本地回填后端实施计划 + +## 目标 + +修复上传行外流水文件后,流水平台返回明细中 `cretNo` 为空导致 `ccdi_bank_statement.cret_no` 为空、结果总览和打标无法按身份证号关联的问题。 + +## 范围 + +- 仅调整后端批量上传流水文件链路。 +- 不调整拉取本行信息链路。 +- 不调整前端页面、数据库表结构、结果总览查询和打标 SQL。 + +## 实施方案 + +1. 复用现有文件名空白清理与身份证号识别规则,从归一化后的上传记录文件名中提取 18 位身份证号。 +2. 保持转传流水平台的文件名不变,继续使用当前上传记录文件名。 +3. 保存平台返回的流水明细时,若 `CcdiBankStatement.cretNo` 为空,则使用当前上传记录文件名中提取到的身份证号回填。 +4. 补充单元测试覆盖平台 `cretNo` 为空时本地回填。 + +## 验证 + +- 运行 `mvn -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`。 +- 必要时运行 `mvn -pl ccdi-project -am -DskipTests compile`。 diff --git a/docs/reports/implementation/2026-07-20-bank-upload-cret-no-backfill.md b/docs/reports/implementation/2026-07-20-bank-upload-cret-no-backfill.md new file mode 100644 index 00000000..5f80c215 --- /dev/null +++ b/docs/reports/implementation/2026-07-20-bank-upload-cret-no-backfill.md @@ -0,0 +1,36 @@ +# 上传流水身份证号本地回填实施记录 + +## 背景 + +上传行外流水文件时,文件名已经会校验必须包含 18 位身份证号,但流水平台返回明细中的 `cretNo` 可能为空。当前结果总览、流水打标和专项分析主要依赖 `ccdi_bank_statement.cret_no` 关联员工或关系人,空值会导致上传后有流水但结果出不来。 + +## 修改内容 + +- 仅调整后端上传流水文件链路,不修改前端、不修改拉取本行信息链路、不修改数据库结构。 +- `CcdiFileUploadServiceImpl` + - 在上传记录文件名中复用现有身份证号识别规则提取 18 位身份证号。 + - 保存流水平台返回的流水明细前,若 `CcdiBankStatement.cretNo` 为空,则使用上传记录文件名中的身份证号回填。 + - 继续使用原有上传记录文件名转传流水平台,不新增 `身份证号-原文件名` 前缀改写。 +- `CcdiFileUploadServiceImplTest` + - 保留“转传流水平台使用记录文件名”的测试。 + - 新增平台返回 `cretNo` 为空时,本地入库实体回填上传文件名身份证号的测试。 + +## 影响范围 + +- 对平台正常返回 `cretNo` 的流水无影响,仍优先使用平台返回值。 +- 对平台未返回 `cretNo`、但上传文件名包含身份证号的流水,入库时会补齐 `ccdi_bank_statement.cret_no`。 +- 对文件名不含身份证号的上传仍在上传前拦截,不进入落库阶段。 + +## 验证 + +```bash +mvn -pl ccdi-project -am "-Dtest=CcdiFileUploadServiceImplTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +``` + +- 结果:BUILD SUCCESS +- 覆盖:上传链路文件名校验、上传平台文件名传递、解析完成后流水入库、`cretNo` 为空时本地回填。 + +## 备注 + +- PowerShell 下 Maven `-D` 参数需要加引号;未加引号时命令会被解析为错误生命周期阶段。 +- 本次未启动前端或后端服务,因此无测试进程需要关闭。