494 lines
18 KiB
Markdown
494 lines
18 KiB
Markdown
|
|
# Project Detail Pull Bank Info Backend Implementation Plan
|
|||
|
|
|
|||
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|||
|
|
|
|||
|
|
**Goal:** Build the backend parse-and-pull workflow for the project detail “拉取本行信息” modal, including ID-card Excel parsing, task submission, thread-pool scheduling, file-upload record updates, and bank-statement ingestion reuse.
|
|||
|
|
|
|||
|
|
**Architecture:** Extend the existing `CcdiFileUploadController` and `CcdiFileUploadServiceImpl` instead of creating a second task subsystem. Parse身份证文件 on demand, persist one `ccdi_file_upload_record` per身份证 with `accountNos` initialized to the身份证号, then reuse the existing `fileUploadExecutor` plus a shared “logId ready” pipeline to update file name, polling status, and bank statements consistently for both uploaded files and pulled inner-flow tasks.
|
|||
|
|
|
|||
|
|
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, EasyExcel 3.3.4, JUnit 5, Mockito
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 1: Add parse/submit contracts and write the first failing parse test
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiPullBankInfoSubmitDTO.java`
|
|||
|
|
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiIdCardParseVO.java`
|
|||
|
|
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiIdCardExcelRow.java`
|
|||
|
|
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java`
|
|||
|
|
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
|||
|
|
|
|||
|
|
**Step 1: Write the failing test**
|
|||
|
|
|
|||
|
|
在 `CcdiFileUploadServiceImplTest` 中新增身份证文件解析测试,验证“首个 sheet 第一列、忽略表头、空行、重复值”:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void parseIdCardFile_shouldReadFirstSheetFirstColumnAndDeduplicate() throws Exception {
|
|||
|
|
MultipartFile file = createIdCardExcel(
|
|||
|
|
"身份证号",
|
|||
|
|
"110101199001018888",
|
|||
|
|
"",
|
|||
|
|
"110101199001018888",
|
|||
|
|
"110101199001019999"
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
List<String> result = service.parseIdCardFile(file);
|
|||
|
|
|
|||
|
|
assertEquals(List.of("110101199001018888", "110101199001019999"), result);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
再补一个非法身份证失败测试:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void parseIdCardFile_shouldRejectInvalidIdCard() throws Exception {
|
|||
|
|
MultipartFile file = createIdCardExcel("身份证号", "123456");
|
|||
|
|
|
|||
|
|
RuntimeException exception = assertThrows(RuntimeException.class, () -> service.parseIdCardFile(file));
|
|||
|
|
|
|||
|
|
assertTrue(exception.getMessage().contains("身份证"));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Step 2: Run test to verify it fails**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: FAIL because `parseIdCardFile` and the new DTO / VO / Excel row model do not exist yet.
|
|||
|
|
|
|||
|
|
**Step 3: Write minimal implementation**
|
|||
|
|
|
|||
|
|
在 `ICcdiFileUploadService` 中新增方法:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
List<String> parseIdCardFile(MultipartFile file);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
创建 `CcdiIdCardExcelRow`:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Data
|
|||
|
|
public class CcdiIdCardExcelRow {
|
|||
|
|
|
|||
|
|
@ExcelProperty(index = 0)
|
|||
|
|
private String idCard;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 `CcdiFileUploadServiceImpl` 中用 EasyExcel 实现最小可用解析逻辑:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
List<CcdiIdCardExcelRow> rows = EasyExcel.read(file.getInputStream(), CcdiIdCardExcelRow.class)
|
|||
|
|
.sheet(0)
|
|||
|
|
.headRowNumber(1)
|
|||
|
|
.doReadSync();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
然后:
|
|||
|
|
|
|||
|
|
- `trim()` 去空白
|
|||
|
|
- 过滤空值
|
|||
|
|
- 用 `LinkedHashSet` 去重保序
|
|||
|
|
- 使用 18 位身份证正则校验
|
|||
|
|
- 当无有效身份证时抛出 `RuntimeException("首个sheet第一列未解析到有效身份证号")`
|
|||
|
|
|
|||
|
|
**Step 4: Run test to verify it passes**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: PASS for the new parse tests; existing tests remain green.
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiPullBankInfoSubmitDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiIdCardParseVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiIdCardExcelRow.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
|||
|
|
git commit -m "新增拉取本行信息参数模型与身份证解析能力"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 2: Add pull-bank-info submission logic and make record initialization fail first
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java`
|
|||
|
|
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
|||
|
|
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml`
|
|||
|
|
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
|||
|
|
|
|||
|
|
**Step 1: Write the failing test**
|
|||
|
|
|
|||
|
|
新增“提交拉取任务时先插入上传记录”的测试,验证 `accountNos` 和 `uploadUser` 初始化正确:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void submitPullBankInfo_shouldInsertUploadingRecordsWithIdCardAsAccountNo() {
|
|||
|
|
CcdiProject project = new CcdiProject();
|
|||
|
|
project.setProjectId(PROJECT_ID);
|
|||
|
|
project.setLsfxProjectId(LSFX_PROJECT_ID);
|
|||
|
|
when(projectMapper.selectById(PROJECT_ID)).thenReturn(project);
|
|||
|
|
|
|||
|
|
AtomicReference<List<CcdiFileUploadRecord>> inserted = new AtomicReference<>();
|
|||
|
|
doAnswer(invocation -> {
|
|||
|
|
List<CcdiFileUploadRecord> records = invocation.getArgument(0);
|
|||
|
|
for (int i = 0; i < records.size(); i++) {
|
|||
|
|
records.get(i).setId((long) (i + 1));
|
|||
|
|
}
|
|||
|
|
inserted.set(new ArrayList<>(records));
|
|||
|
|
return records.size();
|
|||
|
|
}).when(recordMapper).insertBatch(any());
|
|||
|
|
|
|||
|
|
TransactionSynchronizationManager.initSynchronization();
|
|||
|
|
try {
|
|||
|
|
String batchId = service.submitPullBankInfo(
|
|||
|
|
PROJECT_ID,
|
|||
|
|
List.of("110101199001018888", "110101199001019999"),
|
|||
|
|
"2026-03-01",
|
|||
|
|
"2026-03-10",
|
|||
|
|
9527L,
|
|||
|
|
"admin"
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
assertNotNull(batchId);
|
|||
|
|
assertEquals("110101199001018888", inserted.get().get(0).getAccountNos());
|
|||
|
|
assertEquals("admin", inserted.get().get(0).getUploadUser());
|
|||
|
|
assertEquals("uploading", inserted.get().get(0).getFileStatus());
|
|||
|
|
assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size());
|
|||
|
|
} finally {
|
|||
|
|
TransactionSynchronizationManager.clearSynchronization();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Step 2: Run test to verify it fails**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: FAIL because `submitPullBankInfo` does not exist and `insertBatch` SQL does not persist `accountNos`.
|
|||
|
|
|
|||
|
|
**Step 3: Write minimal implementation**
|
|||
|
|
|
|||
|
|
在 `ICcdiFileUploadService` 中新增方法:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
String submitPullBankInfo(Long projectId,
|
|||
|
|
List<String> idCards,
|
|||
|
|
String startDate,
|
|||
|
|
String endDate,
|
|||
|
|
Long userId,
|
|||
|
|
String username);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 `CcdiFileUploadServiceImpl` 中实现:
|
|||
|
|
|
|||
|
|
- 校验项目存在且带有 `lsfxProjectId`
|
|||
|
|
- 校验日期非空、开始日期不大于结束日期
|
|||
|
|
- 校验身份证集合非空
|
|||
|
|
- 为每个身份证创建一条 `CcdiFileUploadRecord`
|
|||
|
|
- `record.setAccountNos(idCard);`
|
|||
|
|
- `record.setFileName(idCard);`
|
|||
|
|
- `record.setUploadUser(username);`
|
|||
|
|
- `record.setFileStatus("uploading");`
|
|||
|
|
- `record.setUploadTime(new Date());`
|
|||
|
|
|
|||
|
|
在 `CcdiFileUploadRecordMapper.xml` 的 `insertBatch` 中补上 `account_nos`:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
insert into ccdi_file_upload_record (
|
|||
|
|
project_id, lsfx_project_id, file_name, file_size, file_status,
|
|||
|
|
enterprise_names, account_nos, upload_time, upload_user
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
注册事务提交后的异步调度:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
|||
|
|
@Override
|
|||
|
|
public void afterCommit() {
|
|||
|
|
CompletableFuture.runAsync(() -> submitPullBankInfoTasks(...));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Step 4: Run test to verify it passes**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: PASS for the new submission test and no regression in existing upload tests.
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
|||
|
|
git commit -m "实现拉取本行信息任务提交与记录初始化"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 3: Refactor the shared logId pipeline and add the first failing pull-flow test
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
|||
|
|
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
|||
|
|
|
|||
|
|
**Step 1: Write the failing test**
|
|||
|
|
|
|||
|
|
新增“拉取行内流水拿到 `logId` 后复用公共流水线”的测试,先验证文件名回写和最终成功:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void processPullBankInfoAsync_shouldUpdateFileNameFromStatusResponse() {
|
|||
|
|
when(lsfxClient.fetchInnerFlow(any())).thenReturn(buildFetchInnerFlowResponse(LOG_ID));
|
|||
|
|
when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
|
|||
|
|
.thenReturn(buildCheckParseStatusResponse(false));
|
|||
|
|
when(lsfxClient.getFileUploadStatus(any())).thenReturn(buildParsedSuccessStatusResponse("XX身份证.xlsx"));
|
|||
|
|
when(lsfxClient.getBankStatement(any(GetBankStatementRequest.class)))
|
|||
|
|
.thenReturn(buildEmptyBankStatementResponse());
|
|||
|
|
|
|||
|
|
CcdiFileUploadRecord record = buildRecord();
|
|||
|
|
service.processPullBankInfoAsync(
|
|||
|
|
PROJECT_ID,
|
|||
|
|
LSFX_PROJECT_ID,
|
|||
|
|
record,
|
|||
|
|
"110101199001018888",
|
|||
|
|
"2026-03-01",
|
|||
|
|
"2026-03-10",
|
|||
|
|
9527L
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
verify(recordMapper, atLeastOnce()).updateById(argThat(item ->
|
|||
|
|
"XX身份证.xlsx".equals(item.getFileName())));
|
|||
|
|
verify(recordMapper, atLeastOnce()).updateById(argThat(item ->
|
|||
|
|
"parsed_success".equals(item.getFileStatus())));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
再补一个失败测试,验证 `fetchInnerFlow` 异常只影响当前记录:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void processPullBankInfoAsync_shouldMarkParsedFailedWhenFetchInnerFlowThrows() {
|
|||
|
|
when(lsfxClient.fetchInnerFlow(any())).thenThrow(new RuntimeException("fetch inner flow failed"));
|
|||
|
|
|
|||
|
|
CcdiFileUploadRecord record = buildRecord();
|
|||
|
|
service.processPullBankInfoAsync(
|
|||
|
|
PROJECT_ID,
|
|||
|
|
LSFX_PROJECT_ID,
|
|||
|
|
record,
|
|||
|
|
"110101199001018888",
|
|||
|
|
"2026-03-01",
|
|||
|
|
"2026-03-10",
|
|||
|
|
9527L
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
verify(recordMapper, atLeastOnce()).updateById(argThat(item ->
|
|||
|
|
"parsed_failed".equals(item.getFileStatus())));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Step 2: Run test to verify it fails**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: FAIL because `processPullBankInfoAsync` and the shared “logId ready” pipeline do not exist yet.
|
|||
|
|
|
|||
|
|
**Step 3: Write minimal implementation**
|
|||
|
|
|
|||
|
|
在 `CcdiFileUploadServiceImpl` 中拆分两段逻辑:
|
|||
|
|
|
|||
|
|
1. 文件来源阶段
|
|||
|
|
- `processFileAsync(...)` 中只负责上传文件并拿到 `logId`
|
|||
|
|
- `processPullBankInfoAsync(...)` 中只负责调用 `fetchInnerFlow` 并拿到 `logId`
|
|||
|
|
|
|||
|
|
2. 公共处理阶段
|
|||
|
|
- 新增 `processRecordAfterLogIdReady(...)`
|
|||
|
|
|
|||
|
|
公共处理方法至少负责:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private void processRecordAfterLogIdReady(Long projectId,
|
|||
|
|
Integer lsfxProjectId,
|
|||
|
|
CcdiFileUploadRecord record,
|
|||
|
|
Integer logId) {
|
|||
|
|
record.setLogId(logId);
|
|||
|
|
record.setFileStatus("parsing");
|
|||
|
|
recordMapper.updateById(record);
|
|||
|
|
boolean parsingComplete = waitForParsingComplete(lsfxProjectId, logId.toString());
|
|||
|
|
GetFileUploadStatusResponse statusResponse = lsfxClient.getFileUploadStatus(statusRequest);
|
|||
|
|
String fileName = StringUtils.hasText(logItem.getUploadFileName())
|
|||
|
|
? logItem.getUploadFileName()
|
|||
|
|
: logItem.getDownloadFileName();
|
|||
|
|
record.setFileName(fileName);
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`processPullBankInfoAsync(...)` 最小实现:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
FetchInnerFlowRequest request = new FetchInnerFlowRequest();
|
|||
|
|
request.setGroupId(lsfxProjectId);
|
|||
|
|
request.setCustomerNo(idCard);
|
|||
|
|
request.setDataChannelCode("ZJRCU");
|
|||
|
|
request.setRequestDateId(Integer.parseInt(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)));
|
|||
|
|
request.setDataStartDateId(Integer.parseInt(startDate.replace("-", "")));
|
|||
|
|
request.setDataEndDateId(Integer.parseInt(endDate.replace("-", "")));
|
|||
|
|
request.setUploadUserId(userId.intValue());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
从 `FetchInnerFlowResponse.getData()` 里取第一个 `logId` 后进入公共处理方法。
|
|||
|
|
|
|||
|
|
**Step 4: Run test to verify it passes**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: PASS for the new pull-flow tests and the existing file-upload tests.
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
|||
|
|
git commit -m "抽取logId后公共处理流水线并接入本行拉取"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 4: Add controller endpoints and fail on controller tests first
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
|||
|
|
- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
|||
|
|
|
|||
|
|
**Step 1: Write the failing test**
|
|||
|
|
|
|||
|
|
为解析接口和提交接口各写一个控制器测试:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void parseIdCardFile_shouldReturnAjaxResultSuccess() {
|
|||
|
|
MockMultipartFile file = new MockMultipartFile(
|
|||
|
|
"file",
|
|||
|
|
"ids.xlsx",
|
|||
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|||
|
|
"test".getBytes(StandardCharsets.UTF_8)
|
|||
|
|
);
|
|||
|
|
when(fileUploadService.parseIdCardFile(file)).thenReturn(List.of("110101199001018888"));
|
|||
|
|
|
|||
|
|
AjaxResult result = controller.parseIdCardFile(file);
|
|||
|
|
|
|||
|
|
assertEquals(200, result.get("code"));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void pullBankInfo_shouldUseCurrentLoginUserInfo() {
|
|||
|
|
CcdiPullBankInfoSubmitDTO dto = new CcdiPullBankInfoSubmitDTO();
|
|||
|
|
dto.setProjectId(PROJECT_ID);
|
|||
|
|
dto.setIdCards(List.of("110101199001018888"));
|
|||
|
|
dto.setStartDate("2026-03-01");
|
|||
|
|
dto.setEndDate("2026-03-10");
|
|||
|
|
|
|||
|
|
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
|||
|
|
mocked.when(SecurityUtils::getUserId).thenReturn(9527L);
|
|||
|
|
mocked.when(SecurityUtils::getUserName).thenReturn("admin");
|
|||
|
|
when(fileUploadService.submitPullBankInfo(PROJECT_ID, dto.getIdCards(), "2026-03-01", "2026-03-10", 9527L, "admin"))
|
|||
|
|
.thenReturn("batch-1");
|
|||
|
|
|
|||
|
|
AjaxResult result = controller.pullBankInfo(dto);
|
|||
|
|
|
|||
|
|
assertEquals(200, result.get("code"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Step 2: Run test to verify it fails**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadControllerTest`
|
|||
|
|
|
|||
|
|
Expected: FAIL because the new controller methods do not exist yet.
|
|||
|
|
|
|||
|
|
**Step 3: Write minimal implementation**
|
|||
|
|
|
|||
|
|
在 `CcdiFileUploadController` 中新增接口:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@PostMapping("/parse-id-card-file")
|
|||
|
|
public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) {
|
|||
|
|
List<String> idCards = fileUploadService.parseIdCardFile(file);
|
|||
|
|
return AjaxResult.success("解析成功", new CcdiIdCardParseVO(idCards, idCards.size()));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@PostMapping("/pull-bank-info")
|
|||
|
|
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
|
|||
|
|
Long userId = SecurityUtils.getUserId();
|
|||
|
|
String username = SecurityUtils.getUserName();
|
|||
|
|
String batchId = fileUploadService.submitPullBankInfo(
|
|||
|
|
dto.getProjectId(),
|
|||
|
|
dto.getIdCards(),
|
|||
|
|
dto.getStartDate(),
|
|||
|
|
dto.getEndDate(),
|
|||
|
|
userId,
|
|||
|
|
username
|
|||
|
|
);
|
|||
|
|
return AjaxResult.success("拉取任务已提交", batchId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
补上参数校验和 Swagger 注释。
|
|||
|
|
|
|||
|
|
**Step 4: Run test to verify it passes**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadControllerTest`
|
|||
|
|
|
|||
|
|
Expected: PASS
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java
|
|||
|
|
git commit -m "新增拉取本行信息后端接口"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 5: Verify the backend end-to-end inside the module
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
|||
|
|
- Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
|||
|
|
- Modify if needed after failures: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml`
|
|||
|
|
- Modify if needed after failures: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
|||
|
|
- Modify if needed after failures: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
|||
|
|
|
|||
|
|
**Step 1: Run focused backend tests**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest,CcdiFileUploadControllerTest`
|
|||
|
|
|
|||
|
|
Expected: PASS
|
|||
|
|
|
|||
|
|
**Step 2: Run module compile**
|
|||
|
|
|
|||
|
|
Run: `mvn clean compile -pl ccdi-project -am`
|
|||
|
|
|
|||
|
|
Expected: BUILD SUCCESS
|
|||
|
|
|
|||
|
|
**Step 3: Run the existing upload regression tests**
|
|||
|
|
|
|||
|
|
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
|||
|
|
|
|||
|
|
Expected: PASS with no regression on file-upload flow, no MyBatis binding error, and no missing mapper statements.
|
|||
|
|
|
|||
|
|
**Step 4: Fix the smallest failing point if verification breaks**
|
|||
|
|
|
|||
|
|
优先排查:
|
|||
|
|
|
|||
|
|
- `insertBatch` 的 XML 列顺序与实体字段不一致
|
|||
|
|
- `uploadFileName` / `downloadFileName` 回写逻辑遗漏空值判断
|
|||
|
|
- `FetchInnerFlowResponse` 取 `logId` 时未处理空列表
|
|||
|
|
- `Long userId` 转 `Integer uploadUserId` 时的空值或溢出保护
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java
|
|||
|
|
git commit -m "完成拉取本行信息后端实现与校验"
|
|||
|
|
```
|