From 2bcba71259be74b0ba52010ea0833e1c4e2af852 Mon Sep 17 00:00:00 2001 From: wjj <2069666735@qq.com> Date: Tue, 21 Jul 2026 16:42:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=91=E7=BB=BC=E6=B5=81?= =?UTF-8?q?=E6=B0=B4=E5=A4=9A=E6=96=87=E4=BB=B6=E6=8B=89=E5=8F=96=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CcdiFileUploadServiceImpl.java | 70 ++++++++++++++----- .../impl/CcdiFileUploadServiceImplTest.java | 63 ++++++++++++++++- ...l-multiple-logid-backend-implementation.md | 20 ++++++ .../2026-07-21-jzl-multiple-logid.md | 23 ++++++ 4 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 docs/plans/backend/2026-07-21-jzl-multiple-logid-backend-implementation.md create mode 100644 docs/reports/implementation/2026-07-21-jzl-multiple-logid.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 6404ae71..bcaa4b9f 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 @@ -686,13 +686,28 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { throw new RuntimeException("拉取本行信息失败: 未返回logId"); } - Integer logId = response.getData().get(0); - if (logId == null) { + List logIds = response.getData().stream() + .filter(Objects::nonNull) + .toList(); + if (logIds.isEmpty()) { throw new RuntimeException("拉取本行信息失败: 未返回logId"); } - processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, caller); - return true; + boolean anySuccess = false; + for (int i = 0; i < logIds.size(); i++) { + Integer logId = logIds.get(i); + CcdiFileUploadRecord currentRecord = i == 0 + ? record + : createAdditionalPullBankInfoRecord(record, idCard); + try { + anySuccess |= processRecordAfterLogIdReady(projectId, lsfxProjectId, currentRecord, logId, caller); + } catch (Exception logException) { + log.error("【拉取本行信息】处理logId失败: idCard={}, logId={}, recordId={}", + idCard, logId, currentRecord.getId(), logException); + updateFailedRecord(currentRecord, logException.getMessage()); + } + } + return anySuccess; } catch (Exception e) { log.error("【拉取本行信息】处理失败: idCard={}, recordId={}", idCard, record.getId(), e); updateFailedRecord(record, e.getMessage()); @@ -700,6 +715,24 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { } } + private CcdiFileUploadRecord createAdditionalPullBankInfoRecord(CcdiFileUploadRecord sourceRecord, + String idCard) { + CcdiFileUploadRecord record = new CcdiFileUploadRecord(); + record.setProjectId(sourceRecord.getProjectId()); + record.setLsfxProjectId(sourceRecord.getLsfxProjectId()); + record.setFileName(idCard); + record.setFileSize(0L); + record.setFileStatus("uploading"); + record.setAccountNos(idCard); + record.setUploadTime(new Date()); + record.setUploadUser(sourceRecord.getUploadUser()); + recordMapper.insertBatch(List.of(record)); + if (record.getId() == null) { + throw new RuntimeException("创建金综流水上传记录失败: 未生成记录ID"); + } + return record; + } + /** * 异步处理单个文件的完整流程 * 包含:上传 → 轮询解析状态 → 获取结果 → 保存流水数据 @@ -782,20 +815,20 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { bankTagService.submitAutoRebuild(projectId, triggerType); } - private void processRecordAfterLogIdReady(Long projectId, - Integer lsfxProjectId, - CcdiFileUploadRecord record, - Integer logId, - CallerContext caller) { - processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, false, caller); + private boolean processRecordAfterLogIdReady(Long projectId, + Integer lsfxProjectId, + CcdiFileUploadRecord record, + Integer logId, + CallerContext caller) { + return processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, false, caller); } - private void processRecordAfterLogIdReady(Long projectId, - Integer lsfxProjectId, - CcdiFileUploadRecord record, - Integer logId, - boolean preserveRecordFileName, - CallerContext caller) { + private boolean processRecordAfterLogIdReady(Long projectId, + Integer lsfxProjectId, + CcdiFileUploadRecord record, + Integer logId, + boolean preserveRecordFileName, + CallerContext caller) { log.info("【文件上传】步骤3: 更新状态为解析中, logId={}", logId); record.setLogId(logId); record.setFileStatus("parsing"); @@ -840,7 +873,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { if (!parseSuccess) { log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc); updateFailedRecord(record, "解析失败: " + uploadStatusDesc); - return; + return false; } log.info("【文件上传】步骤6: 解析成功,保存主体信息"); @@ -857,7 +890,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { logId, fallbackCretNo); if (!fetchResult.isSuccess()) { updateFailedRecord(record, fetchResult.getErrorMessage()); - return; + return false; } record.setFileStatus("parsed_success"); @@ -865,6 +898,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { record.setAccountNos(accountNosStr); record.setErrorMessage(null); recordMapper.updateById(record); + return true; } /** 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 f8303c7e..5eafc3f0 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 @@ -510,6 +510,65 @@ class CcdiFileUploadServiceImplTest { )); } + @Test + void processPullBankInfoAsync_shouldProcessAllJzlLogIds() { + Integer secondLogId = LOG_ID + 1; + Integer thirdLogId = LOG_ID + 2; + List insertedAdditionalRecords = new ArrayList<>(); + + doAnswer(invocation -> { + List records = invocation.getArgument(0); + for (int i = 0; i < records.size(); i++) { + records.get(i).setId(RECORD_ID + i + 1); + CcdiFileUploadRecord snapshot = new CcdiFileUploadRecord(); + snapshot.setId(records.get(i).getId()); + snapshot.setFileName(records.get(i).getFileName()); + snapshot.setAccountNos(records.get(i).getAccountNos()); + snapshot.setUploadUser(records.get(i).getUploadUser()); + snapshot.setFileStatus(records.get(i).getFileStatus()); + insertedAdditionalRecords.add(snapshot); + } + return records.size(); + }).when(recordMapper).insertBatch(any()); + + when(lsfxClient.fetchInnerFlow(eq(CALLER), any())) + .thenReturn(buildFetchInnerFlowResponse(LOG_ID, secondLogId, thirdLogId)); + when(lsfxClient.checkParseStatus(eq(CALLER), eq(LSFX_PROJECT_ID), org.mockito.ArgumentMatchers.anyString())) + .thenReturn(buildCheckParseStatusResponse(false)); + when(lsfxClient.getFileUploadStatus(eq(CALLER), any())).thenReturn(buildParsedSuccessStatusResponse()); + when(lsfxClient.getBankStatement(eq(CALLER), any(GetBankStatementRequest.class))) + .thenReturn(buildEmptyBankStatementResponse()); + + CcdiFileUploadRecord record = buildRecord(); + record.setUploadUser("admin"); + + boolean success = service.processPullBankInfoAsync( + PROJECT_ID, + LSFX_PROJECT_ID, + record, + "110101199001018888", + LsfxConstants.DATA_CHANNEL_JZL, + null, + null, + CALLER + ); + + assertTrue(success); + assertEquals(2, insertedAdditionalRecords.size()); + assertEquals("110101199001018888", insertedAdditionalRecords.get(0).getFileName()); + assertEquals("110101199001018888", insertedAdditionalRecords.get(0).getAccountNos()); + assertEquals("admin", insertedAdditionalRecords.get(0).getUploadUser()); + verify(lsfxClient).checkParseStatus(CALLER, LSFX_PROJECT_ID, String.valueOf(LOG_ID)); + verify(lsfxClient).checkParseStatus(CALLER, LSFX_PROJECT_ID, String.valueOf(secondLogId)); + verify(lsfxClient).checkParseStatus(CALLER, LSFX_PROJECT_ID, String.valueOf(thirdLogId)); + verify(lsfxClient).getBankStatement(eq(CALLER), org.mockito.ArgumentMatchers.argThat(request -> + LOG_ID.equals(request.getLogId()))); + verify(lsfxClient).getBankStatement(eq(CALLER), org.mockito.ArgumentMatchers.argThat(request -> + secondLogId.equals(request.getLogId()))); + verify(lsfxClient).getBankStatement(eq(CALLER), org.mockito.ArgumentMatchers.argThat(request -> + thirdLogId.equals(request.getLogId()))); + } + @Test void processFileAsync_shouldUploadToLsfxWithOriginalRecordFileName() throws IOException { when(lsfxClient.uploadFile(eq(CALLER), eq(LSFX_PROJECT_ID), any(), eq("原始流水.xlsx"))) @@ -995,9 +1054,9 @@ class CcdiFileUploadServiceImplTest { return response; } - private FetchInnerFlowResponse buildFetchInnerFlowResponse(Integer logId) { + private FetchInnerFlowResponse buildFetchInnerFlowResponse(Integer... logIds) { FetchInnerFlowResponse response = new FetchInnerFlowResponse(); - response.setData(List.of(logId)); + response.setData(List.of(logIds)); return response; } diff --git a/docs/plans/backend/2026-07-21-jzl-multiple-logid-backend-implementation.md b/docs/plans/backend/2026-07-21-jzl-multiple-logid-backend-implementation.md new file mode 100644 index 00000000..b800095e --- /dev/null +++ b/docs/plans/backend/2026-07-21-jzl-multiple-logid-backend-implementation.md @@ -0,0 +1,20 @@ +# 金综流水多 logId 处理后端实施计划 + +## 背景 + +金综 `dataChannelCode=JZL` 拉取链路可能在一次请求中返回多个 XML 文件对应的多个 `logId`。接入端如果只处理首个 `logId`,会导致同一证件号下后续金综 XML 文件未进入解析状态轮询、上传状态查询和流水明细落库。 + +## 实施内容 + +- 将金综拉取响应处理从单个 `logId` 改为遍历 `response.data` 中全部非空 `logId`。 +- 第一个 `logId` 复用拉取提交时创建的上传记录。 +- 第二个及后续 `logId` 自动创建独立上传记录,避免多个 XML 的文件名、账号、解析状态相互覆盖。 +- 每个 `logId` 独立执行解析状态轮询、文件状态查询、流水明细获取和落库。 +- 继续沿用最新代码中的 `CallerContext`,确保流水平台调用仍写入外部接口日志。 +- 单个 `logId` 失败只标记对应记录失败,不中断其余 `logId`;本次证件号任务至少一个 `logId` 成功即视为成功。 + +## 验证计划 + +- 补充单元测试模拟金综返回 3 个 `logId`。 +- 验证 3 个 `logId` 均调用解析状态轮询和流水明细查询。 +- 验证后续 `logId` 会新增独立上传记录,并保留证件号、上传人和上传中状态。 diff --git a/docs/reports/implementation/2026-07-21-jzl-multiple-logid.md b/docs/reports/implementation/2026-07-21-jzl-multiple-logid.md new file mode 100644 index 00000000..96876d7e --- /dev/null +++ b/docs/reports/implementation/2026-07-21-jzl-multiple-logid.md @@ -0,0 +1,23 @@ +# 金综流水多 logId 处理实施记录 + +## 修改内容 + +- 修复拉取金综流水时只处理首个 `logId` 的问题。 +- `FetchInnerFlowResponse.data` 中多个 `logId` 现在会逐个进入解析状态轮询、上传状态查询和流水明细落库。 +- 第一个 `logId` 复用原上传记录,后续 `logId` 自动新增独立上传记录。 +- 单个 `logId` 处理失败时只更新对应记录为失败,不影响其余 `logId` 继续处理。 +- 基于最新 `origin/dev-ui` 实施,保留 `CallerContext` 外部接口日志链路。 + +## 影响范围 + +- `/ccdi/file-upload/pull-bank-info` 金综 `JZL` 拉取后的异步处理链路。 +- 项目详情上传数据列表中金综多 XML 文件对应的上传记录展示。 +- 后续流水明细保存与自动打标触发判断。 + +## 验证情况 + +- 后端单测通过: + - `mvn -pl ccdi-project -am test "-Dtest=CcdiFileUploadServiceImplTest" "-Dsurefire.failIfNoSpecifiedTests=false"` + - 结果:36 个测试通过。 +- 新增用例覆盖金综返回 3 个 `logId` 的场景,确认 3 个 `logId` 均进入解析状态轮询和流水明细查询,且后续 `logId` 会新增独立上传记录。 +- 验证多 `logId` 处理继续传递同一个 `CallerContext`,兼容最新外部接口日志链路。