修复金综流水多文件拉取处理
This commit is contained in:
@@ -686,13 +686,28 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
throw new RuntimeException("拉取本行信息失败: 未返回logId");
|
throw new RuntimeException("拉取本行信息失败: 未返回logId");
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer logId = response.getData().get(0);
|
List<Integer> logIds = response.getData().stream()
|
||||||
if (logId == null) {
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
if (logIds.isEmpty()) {
|
||||||
throw new RuntimeException("拉取本行信息失败: 未返回logId");
|
throw new RuntimeException("拉取本行信息失败: 未返回logId");
|
||||||
}
|
}
|
||||||
|
|
||||||
processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, caller);
|
boolean anySuccess = false;
|
||||||
return true;
|
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) {
|
} catch (Exception e) {
|
||||||
log.error("【拉取本行信息】处理失败: idCard={}, recordId={}", idCard, record.getId(), e);
|
log.error("【拉取本行信息】处理失败: idCard={}, recordId={}", idCard, record.getId(), e);
|
||||||
updateFailedRecord(record, e.getMessage());
|
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);
|
bankTagService.submitAutoRebuild(projectId, triggerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordAfterLogIdReady(Long projectId,
|
private boolean processRecordAfterLogIdReady(Long projectId,
|
||||||
Integer lsfxProjectId,
|
Integer lsfxProjectId,
|
||||||
CcdiFileUploadRecord record,
|
CcdiFileUploadRecord record,
|
||||||
Integer logId,
|
Integer logId,
|
||||||
CallerContext caller) {
|
CallerContext caller) {
|
||||||
processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, false, caller);
|
return processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, false, caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordAfterLogIdReady(Long projectId,
|
private boolean processRecordAfterLogIdReady(Long projectId,
|
||||||
Integer lsfxProjectId,
|
Integer lsfxProjectId,
|
||||||
CcdiFileUploadRecord record,
|
CcdiFileUploadRecord record,
|
||||||
Integer logId,
|
Integer logId,
|
||||||
boolean preserveRecordFileName,
|
boolean preserveRecordFileName,
|
||||||
CallerContext caller) {
|
CallerContext caller) {
|
||||||
log.info("【文件上传】步骤3: 更新状态为解析中, logId={}", logId);
|
log.info("【文件上传】步骤3: 更新状态为解析中, logId={}", logId);
|
||||||
record.setLogId(logId);
|
record.setLogId(logId);
|
||||||
record.setFileStatus("parsing");
|
record.setFileStatus("parsing");
|
||||||
@@ -840,7 +873,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
if (!parseSuccess) {
|
if (!parseSuccess) {
|
||||||
log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc);
|
log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc);
|
||||||
updateFailedRecord(record, "解析失败: " + uploadStatusDesc);
|
updateFailedRecord(record, "解析失败: " + uploadStatusDesc);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("【文件上传】步骤6: 解析成功,保存主体信息");
|
log.info("【文件上传】步骤6: 解析成功,保存主体信息");
|
||||||
@@ -857,7 +890,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
logId, fallbackCretNo);
|
logId, fallbackCretNo);
|
||||||
if (!fetchResult.isSuccess()) {
|
if (!fetchResult.isSuccess()) {
|
||||||
updateFailedRecord(record, fetchResult.getErrorMessage());
|
updateFailedRecord(record, fetchResult.getErrorMessage());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
record.setFileStatus("parsed_success");
|
record.setFileStatus("parsed_success");
|
||||||
@@ -865,6 +898,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
record.setAccountNos(accountNosStr);
|
record.setAccountNos(accountNosStr);
|
||||||
record.setErrorMessage(null);
|
record.setErrorMessage(null);
|
||||||
recordMapper.updateById(record);
|
recordMapper.updateById(record);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
@Test
|
||||||
void processFileAsync_shouldUploadToLsfxWithOriginalRecordFileName() throws IOException {
|
void processFileAsync_shouldUploadToLsfxWithOriginalRecordFileName() throws IOException {
|
||||||
when(lsfxClient.uploadFile(eq(CALLER), eq(LSFX_PROJECT_ID), any(), eq("原始流水.xlsx")))
|
when(lsfxClient.uploadFile(eq(CALLER), eq(LSFX_PROJECT_ID), any(), eq("原始流水.xlsx")))
|
||||||
@@ -995,9 +1054,9 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FetchInnerFlowResponse buildFetchInnerFlowResponse(Integer logId) {
|
private FetchInnerFlowResponse buildFetchInnerFlowResponse(Integer... logIds) {
|
||||||
FetchInnerFlowResponse response = new FetchInnerFlowResponse();
|
FetchInnerFlowResponse response = new FetchInnerFlowResponse();
|
||||||
response.setData(List.of(logId));
|
response.setData(List.of(logIds));
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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` 会新增独立上传记录,并保留证件号、上传人和上传中状态。
|
||||||
23
docs/reports/implementation/2026-07-21-jzl-multiple-logid.md
Normal file
23
docs/reports/implementation/2026-07-21-jzl-multiple-logid.md
Normal 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`,兼容最新外部接口日志链路。
|
||||||
Reference in New Issue
Block a user