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

14 KiB
Raw Blame History

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
    • 新增 sheetNamesheetRowNum
  • 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: 写控制器与接口契约失败测试

@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: 最小化修改控制器与服务接口
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: 提交这一小步
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 与模板契约测试

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: 最小化实现字段与模板导出
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: 提交这一小步
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: 先写服务层统一任务初始化失败测试

@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: 实现统一提交入口
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: 提交这一小步
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: 先写异步导入失败测试

@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: 以最小改动实现两阶段编排
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: 补充工作经历“已有旧记录即失败”与行号上下文
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: 提交这一小步
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: 补实施记录
- 导入入口收口为双 Sheet 单任务
- 工作经历导入改为“已有旧记录时报错”
- 失败记录补齐失败 Sheet、失败行号、失败原因
- 已完成后端编译与定向测试
  • Step 5: 提交后端收尾
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导入后端改造"