Files
ccdi/docs/plans/backend/2026-04-23-staff-recruitment-dual-sheet-import-backend-implementation.md

321 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Staff Recruitment Dual-Sheet Import Backend Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将招聘信息管理后端导入链路收口为“招聘信息 + 历史工作经历”双 Sheet 单任务模式,并补齐失败 Sheet、失败行号、失败原因。
**Architecture:** 保留现有 `ccdi:staffRecruitment:*` 权限、`/ccdi/staffRecruitment/*` 路径和现有主从表结构,只收口控制器、服务接口和异步导入编排。导入任务先处理主信息 Sheet再按 `recruitId` 分组处理工作经历 Sheet工作经历匹配“本次主 Sheet 成功数据 + 数据库已有主信息”,若数据库中已存在旧工作经历则直接整组失败,不做覆盖。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis-Plus, EasyExcel, Redis, JUnit 5, Mockito
---
## File Map
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java`
- 收口双 Sheet 模板下载与统一导入入口,移除独立工作经历导入接口
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java`
- 暴露统一的双 Sheet 提交入口
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java`
- 暴露统一的双 Sheet 异步导入入口
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java`
- 初始化统一 Redis 任务状态并提交统一异步任务
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java`
- 实现主 Sheet 与工作经历 Sheet 两阶段编排、行号上下文、已有工作经历报错规则
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java`
- 新增 `sheetName``sheetRowNum`
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java`
- 锁定控制器与接口的双 Sheet 契约
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentImportServiceImplTest.java`
- 锁定异步导入编排、已有工作经历报错和失败定位行为
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java`
- 补模板双 Sheet 约束回归
- Create: `docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-implementation.md`
- 记录最终实施内容、验证结果与浏览器实测结论
### Task 1: 锁定双 Sheet 导入接口契约
**Files:**
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java`
- [ ] **Step 1: 写控制器与接口契约失败测试**
```java
@Test
void shouldExposeSingleDualSheetImportEntry() throws Exception {
String controller = Files.readString(
Path.of("src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java")
);
assertTrue(controller.contains("\"招聘信息\""));
assertTrue(controller.contains("\"历史工作经历\""));
assertFalse(controller.contains("workImportTemplate"));
assertFalse(controller.contains("importWorkData"));
}
```
- [ ] **Step 2: 运行测试确认失败**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest test`
Expected: FAIL提示控制器仍存在 `workImportTemplate` / `importWorkData` 或接口签名仍为双入口
- [ ] **Step 3: 最小化修改控制器与服务接口**
```java
String importRecruitment(
List<CcdiStaffRecruitmentExcel> recruitmentList,
List<CcdiStaffRecruitmentWorkExcel> workList
);
void importRecruitmentAsync(
List<CcdiStaffRecruitmentExcel> recruitmentList,
List<CcdiStaffRecruitmentWorkExcel> workList,
String taskId,
String userName
);
```
- [ ] **Step 4: 重跑契约测试**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest test`
Expected: PASS控制器仅保留双 Sheet 模板与单导入入口
- [ ] **Step 5: 提交这一小步**
```bash
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java
git commit -m "收口招聘双Sheet导入接口"
```
### Task 2: 接入双 Sheet 模板与失败 VO 字段
**Files:**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java`
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java`
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java`
- [ ] **Step 1: 先补失败 VO 与模板契约测试**
```java
assertHasField(
"com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO",
"sheetName"
);
assertHasField(
"com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO",
"sheetRowNum"
);
```
- [ ] **Step 2: 运行测试确认失败**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest,EasyExcelUtilTemplateTest test`
Expected: FAIL提示 `RecruitmentImportFailureVO` 缺字段或模板断言未通过
- [ ] **Step 3: 最小化实现字段与模板导出**
```java
EasyExcelUtil.importTemplateWithDictDropdown(
response,
CcdiStaffRecruitmentExcel.class,
"招聘信息",
CcdiStaffRecruitmentWorkExcel.class,
"历史工作经历",
"招聘信息管理导入模板"
);
```
- [ ] **Step 4: 重跑测试**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest,EasyExcelUtilTemplateTest test`
Expected: PASS模板输出双 Sheet失败 VO 具备新字段
- [ ] **Step 5: 提交这一小步**
```bash
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java
git commit -m "补齐招聘双Sheet模板与失败字段"
```
### Task 3: 收口服务层任务初始化与统一状态统计
**Files:**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java`
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java`
- [ ] **Step 1: 先写服务层统一任务初始化失败测试**
```java
@Test
void shouldInitializeSingleRedisTaskForTwoSheets() throws Exception {
String service = Files.readString(
Path.of("src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java")
);
assertTrue(service.contains("recruitmentList.size() + workList.size()"));
assertFalse(service.contains("importRecruitmentWork("));
}
```
- [ ] **Step 2: 运行测试确认失败**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest test`
Expected: FAIL提示服务层仍保留独立工作经历任务初始化
- [ ] **Step 3: 实现统一提交入口**
```java
public String importRecruitment(
List<CcdiStaffRecruitmentExcel> recruitmentList,
List<CcdiStaffRecruitmentWorkExcel> workList
) {
int totalCount = recruitmentList.size() + workList.size();
recruitmentImportService.importRecruitmentAsync(recruitmentList, workList, taskId, userName);
return taskId;
}
```
- [ ] **Step 4: 重跑契约测试**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest test`
Expected: PASS任务总数按双 Sheet 合并统计,接口只剩统一入口
- [ ] **Step 5: 提交这一小步**
```bash
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java
git commit -m "统一招聘双Sheet任务初始化"
```
### Task 4: 实现异步导入两阶段编排与失败定位
**Files:**
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentImportServiceImplTest.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java`
- [ ] **Step 1: 先写异步导入失败测试**
```java
@Test
void shouldFailWholeWorkGroupWhenExistingHistoryExists() {
// arrange: recruitmentWorkMapper.countByRecruitId("RC001") -> 1
// act: importRecruitmentAsync(mainRows, workRows, taskId, "admin")
// assert: recruitmentWorkMapper.insert(...) not called
// assert: failure.sheetName == "历史工作经历"
// assert: failure.sheetRowNum == "2"
}
```
- [ ] **Step 2: 运行测试确认失败**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentImportServiceImplTest test`
Expected: FAIL提示当前实现仍会删除旧工作经历或未记录 `sheetName` / `sheetRowNum`
- [ ] **Step 3: 以最小改动实现两阶段编排**
```java
List<MainImportRow> indexedMainRows = buildMainImportRows(recruitmentList);
List<WorkImportRow> indexedWorkRows = buildWorkImportRows(workList);
Map<String, CcdiStaffRecruitment> importedRecruitmentMap = importMainSheet(indexedMainRows, failures, userName);
importWorkSheet(indexedWorkRows, importedRecruitmentMap, failures, userName);
```
- [ ] **Step 4: 补充工作经历“已有旧记录即失败”与行号上下文**
```java
if (hasExistingWorkHistory(recruitId)) {
throw buildValidationException(
"历史工作经历",
extractWorkRowNums(workRows),
String.format("招聘记录编号[%s]已存在历史工作经历,不允许重复导入", recruitId)
);
}
```
- [ ] **Step 5: 重跑测试**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentImportServiceImplTest test`
Expected: PASS已有工作经历不覆盖失败记录带 Sheet 与行号
- [ ] **Step 6: 提交这一小步**
```bash
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentImportServiceImplTest.java
git commit -m "实现招聘双Sheet异步导入编排"
```
### Task 5: 做后端回归、补实施记录并交付联调入口
**Files:**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java`
- Create: `docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-implementation.md`
- [ ] **Step 1: 运行后端定向测试**
Run: `mvn -pl ccdi-info-collection -Dtest=CcdiStaffRecruitmentDualImportContractTest,CcdiStaffRecruitmentImportServiceImplTest,EasyExcelUtilTemplateTest test`
Expected: PASS双 Sheet 契约、异步编排、模板约束全部通过
- [ ] **Step 2: 运行模块编译**
Run: `mvn -pl ccdi-info-collection,ruoyi-admin -am -DskipTests compile`
Expected: BUILD SUCCESS
- [ ] **Step 3: 启动后端供前端联调**
Run: `sh bin/restart_java_backend.sh`
Expected: 后端正常重启,`/ccdi/staffRecruitment/importTemplate``/importData` 可访问
- [ ] **Step 4: 补实施记录**
```md
- 导入入口收口为双 Sheet 单任务
- 工作经历导入改为“已有旧记录时报错”
- 失败记录补齐失败 Sheet、失败行号、失败原因
- 已完成后端编译与定向测试
```
- [ ] **Step 5: 提交后端收尾**
```bash
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentDualImportContractTest.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffRecruitmentImportServiceImplTest.java \
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java \
docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-implementation.md
git commit -m "完成招聘双Sheet导入后端改造"
```