From 041974b3183cf7afa09aee3b057f67125889ddf4 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Mon, 9 Mar 2026 16:51:28 +0800 Subject: [PATCH] fix(ccdi-project): truncate upload error messages --- .../impl/CcdiFileUploadServiceImpl.java | 28 ++++++--- .../impl/CcdiFileUploadServiceImplTest.java | 61 +++++++++++++++++++ ...-ccdi-file-upload-record-error-message.sql | 4 ++ 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 sql/2026-03-09-fix-ccdi-file-upload-record-error-message.sql 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 5408652..41352fb 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 @@ -50,6 +50,8 @@ import java.util.concurrent.RejectedExecutionException; @Service public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { + private static final int MAX_ERROR_MESSAGE_LENGTH = 2000; + @Data private static class FetchBankStatementResult { private boolean success; @@ -330,10 +332,26 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { CcdiFileUploadRecord record = new CcdiFileUploadRecord(); record.setId(recordId); record.setFileStatus(status); - record.setErrorMessage(errorMessage); + record.setErrorMessage(normalizeErrorMessage(errorMessage)); 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 = fetchAndSaveBankStatements(projectId, lsfxProjectId, logId); if (!fetchResult.isSuccess()) { - record.setFileStatus("parsed_failed"); - record.setErrorMessage(fetchResult.getErrorMessage()); - recordMapper.updateById(record); + updateFailedRecord(record, fetchResult.getErrorMessage()); return; } @@ -459,9 +475,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { } else { // 解析失败 log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc); - record.setFileStatus("parsed_failed"); - record.setErrorMessage("解析失败: " + uploadStatusDesc); - recordMapper.updateById(record); + updateFailedRecord(record, "解析失败: " + uploadStatusDesc); } log.info("【文件上传】处理完成: fileName={}", record.getFileName()); 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 d8c41ac..335fdbb 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 @@ -39,6 +39,7 @@ class CcdiFileUploadServiceImplTest { private static final Integer LSFX_PROJECT_ID = 200; private static final Long RECORD_ID = 300L; private static final Integer LOG_ID = 400; + private static final int MAX_ERROR_MESSAGE_LENGTH = 2000; @InjectMocks private CcdiFileUploadServiceImpl service; @@ -148,6 +149,44 @@ class CcdiFileUploadServiceImplTest { verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID); } + @Test + void processFileAsync_shouldTruncateLongErrorMessageWhenBankStatementFetchFails() throws IOException { + List 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 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 events, AtomicInteger sequence) { doAnswer(invocation -> { CcdiFileUploadRecord record = invocation.getArgument(0); @@ -156,6 +195,18 @@ class CcdiFileUploadServiceImplTest { }).when(recordMapper).updateById(any(CcdiFileUploadRecord.class)); } + private void captureUpdatedRecords(List 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() { CcdiFileUploadRecord record = new CcdiFileUploadRecord(); record.setId(RECORD_ID); @@ -236,4 +287,14 @@ class CcdiFileUploadServiceImplTest { } return -1; } + + private CcdiFileUploadRecord findLastUpdatedRecordByStatus(List 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 + " 的更新记录"); + } } diff --git a/sql/2026-03-09-fix-ccdi-file-upload-record-error-message.sql b/sql/2026-03-09-fix-ccdi-file-upload-record-error-message.sql new file mode 100644 index 0000000..dc277fa --- /dev/null +++ b/sql/2026-03-09-fix-ccdi-file-upload-record-error-message.sql @@ -0,0 +1,4 @@ +USE ccdi; + +ALTER TABLE ccdi_file_upload_record + MODIFY COLUMN error_message TEXT COMMENT '错误信息(解析失败时记录)';