修复金综流水多文件拉取处理

This commit is contained in:
wjj
2026-07-21 16:42:45 +08:00
parent ffaa2fb2a0
commit 2bcba71259
4 changed files with 156 additions and 20 deletions

View File

@@ -686,13 +686,28 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
throw new RuntimeException("拉取本行信息失败: 未返回logId");
}
Integer logId = response.getData().get(0);
if (logId == null) {
List<Integer> 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;
}
/**

View File

@@ -510,6 +510,65 @@ class CcdiFileUploadServiceImplTest {
));
}
@Test
void processPullBankInfoAsync_shouldProcessAllJzlLogIds() {
Integer secondLogId = LOG_ID + 1;
Integer thirdLogId = LOG_ID + 2;
List<CcdiFileUploadRecord> insertedAdditionalRecords = new ArrayList<>();
doAnswer(invocation -> {
List<CcdiFileUploadRecord> 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.<GetBankStatementRequest>argThat(request ->
LOG_ID.equals(request.getLogId())));
verify(lsfxClient).getBankStatement(eq(CALLER), org.mockito.ArgumentMatchers.<GetBankStatementRequest>argThat(request ->
secondLogId.equals(request.getLogId())));
verify(lsfxClient).getBankStatement(eq(CALLER), org.mockito.ArgumentMatchers.<GetBankStatementRequest>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;
}

View File

@@ -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` 会新增独立上传记录,并保留证件号、上传人和上传中状态。

View File

@@ -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`,兼容最新外部接口日志链路。