fix(ccdi-project): truncate upload error messages

This commit is contained in:
wkc
2026-03-09 16:51:28 +08:00
parent 763e39d153
commit 041974b318
3 changed files with 86 additions and 7 deletions

View File

@@ -50,6 +50,8 @@ import java.util.concurrent.RejectedExecutionException;
@Service @Service
public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
private static final int MAX_ERROR_MESSAGE_LENGTH = 2000;
@Data @Data
private static class FetchBankStatementResult { private static class FetchBankStatementResult {
private boolean success; private boolean success;
@@ -330,10 +332,26 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
CcdiFileUploadRecord record = new CcdiFileUploadRecord(); CcdiFileUploadRecord record = new CcdiFileUploadRecord();
record.setId(recordId); record.setId(recordId);
record.setFileStatus(status); record.setFileStatus(status);
record.setErrorMessage(errorMessage); record.setErrorMessage(normalizeErrorMessage(errorMessage));
recordMapper.updateById(record); recordMapper.updateById(record);
} }
private void updateFailedRecord(CcdiFileUploadRecord record, String errorMessage) {
record.setFileStatus("parsed_failed");
record.setErrorMessage(normalizeErrorMessage(errorMessage));
recordMapper.updateById(record);
}
private String normalizeErrorMessage(String errorMessage) {
if (!StringUtils.hasText(errorMessage)) {
return null;
}
if (errorMessage.length() <= MAX_ERROR_MESSAGE_LENGTH) {
return errorMessage;
}
return errorMessage.substring(0, MAX_ERROR_MESSAGE_LENGTH);
}
/** /**
* 异步处理单个文件的完整流程 * 异步处理单个文件的完整流程
* 包含:上传 → 轮询解析状态 → 获取结果 → 保存流水数据 * 包含:上传 → 轮询解析状态 → 获取结果 → 保存流水数据
@@ -444,9 +462,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
FetchBankStatementResult fetchResult = FetchBankStatementResult fetchResult =
fetchAndSaveBankStatements(projectId, lsfxProjectId, logId); fetchAndSaveBankStatements(projectId, lsfxProjectId, logId);
if (!fetchResult.isSuccess()) { if (!fetchResult.isSuccess()) {
record.setFileStatus("parsed_failed"); updateFailedRecord(record, fetchResult.getErrorMessage());
record.setErrorMessage(fetchResult.getErrorMessage());
recordMapper.updateById(record);
return; return;
} }
@@ -459,9 +475,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
} else { } else {
// 解析失败 // 解析失败
log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc); log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc);
record.setFileStatus("parsed_failed"); updateFailedRecord(record, "解析失败: " + uploadStatusDesc);
record.setErrorMessage("解析失败: " + uploadStatusDesc);
recordMapper.updateById(record);
} }
log.info("【文件上传】处理完成: fileName={}", record.getFileName()); log.info("【文件上传】处理完成: fileName={}", record.getFileName());

View File

@@ -39,6 +39,7 @@ class CcdiFileUploadServiceImplTest {
private static final Integer LSFX_PROJECT_ID = 200; private static final Integer LSFX_PROJECT_ID = 200;
private static final Long RECORD_ID = 300L; private static final Long RECORD_ID = 300L;
private static final Integer LOG_ID = 400; private static final Integer LOG_ID = 400;
private static final int MAX_ERROR_MESSAGE_LENGTH = 2000;
@InjectMocks @InjectMocks
private CcdiFileUploadServiceImpl service; private CcdiFileUploadServiceImpl service;
@@ -148,6 +149,44 @@ class CcdiFileUploadServiceImplTest {
verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID); verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID);
} }
@Test
void processFileAsync_shouldTruncateLongErrorMessageWhenBankStatementFetchFails() throws IOException {
List<CcdiFileUploadRecord> updates = new ArrayList<>();
captureUpdatedRecords(updates);
when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any())).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)))
.thenThrow(new RuntimeException("bank statement fetch failed:" + "x".repeat(3000)));
CcdiFileUploadRecord record = buildRecord();
Path tempFile = createTempFile();
service.processFileAsync(PROJECT_ID, LSFX_PROJECT_ID, tempFile.toString(), RECORD_ID, "batch-1", record);
CcdiFileUploadRecord failedRecord = findLastUpdatedRecordByStatus(updates, "parsed_failed");
assertTrue(failedRecord.getErrorMessage().length() <= MAX_ERROR_MESSAGE_LENGTH);
}
@Test
void processFileAsync_shouldTruncateLongErrorMessageWhenUnexpectedFailureOccurs() throws IOException {
List<CcdiFileUploadRecord> updates = new ArrayList<>();
captureUpdatedRecords(updates);
when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any()))
.thenThrow(new RuntimeException("upload failed:" + "x".repeat(3000)));
CcdiFileUploadRecord record = buildRecord();
Path tempFile = createTempFile();
service.processFileAsync(PROJECT_ID, LSFX_PROJECT_ID, tempFile.toString(), RECORD_ID, "batch-1", record);
CcdiFileUploadRecord failedRecord = findLastUpdatedRecordByStatus(updates, "parsed_failed");
assertTrue(failedRecord.getErrorMessage().length() <= MAX_ERROR_MESSAGE_LENGTH);
}
private void captureRecordStatus(List<String> events, AtomicInteger sequence) { private void captureRecordStatus(List<String> events, AtomicInteger sequence) {
doAnswer(invocation -> { doAnswer(invocation -> {
CcdiFileUploadRecord record = invocation.getArgument(0); CcdiFileUploadRecord record = invocation.getArgument(0);
@@ -156,6 +195,18 @@ class CcdiFileUploadServiceImplTest {
}).when(recordMapper).updateById(any(CcdiFileUploadRecord.class)); }).when(recordMapper).updateById(any(CcdiFileUploadRecord.class));
} }
private void captureUpdatedRecords(List<CcdiFileUploadRecord> updates) {
doAnswer(invocation -> {
CcdiFileUploadRecord record = invocation.getArgument(0);
CcdiFileUploadRecord snapshot = new CcdiFileUploadRecord();
snapshot.setId(record.getId());
snapshot.setFileStatus(record.getFileStatus());
snapshot.setErrorMessage(record.getErrorMessage());
updates.add(snapshot);
return 1;
}).when(recordMapper).updateById(any(CcdiFileUploadRecord.class));
}
private CcdiFileUploadRecord buildRecord() { private CcdiFileUploadRecord buildRecord() {
CcdiFileUploadRecord record = new CcdiFileUploadRecord(); CcdiFileUploadRecord record = new CcdiFileUploadRecord();
record.setId(RECORD_ID); record.setId(RECORD_ID);
@@ -236,4 +287,14 @@ class CcdiFileUploadServiceImplTest {
} }
return -1; return -1;
} }
private CcdiFileUploadRecord findLastUpdatedRecordByStatus(List<CcdiFileUploadRecord> updates,
String status) {
for (int i = updates.size() - 1; i >= 0; i--) {
if (status.equals(updates.get(i).getFileStatus())) {
return updates.get(i);
}
}
throw new AssertionError("未找到状态为 " + status + " 的更新记录");
}
} }

View File

@@ -0,0 +1,4 @@
USE ccdi;
ALTER TABLE ccdi_file_upload_record
MODIFY COLUMN error_message TEXT COMMENT '错误信息(解析失败时记录)';