新增招聘信息双Sheet导入设计文档

This commit is contained in:
wkc
2026-04-23 09:40:15 +08:00
parent 2d1b02474c
commit ff9627d0d9
2 changed files with 431 additions and 0 deletions

View File

@@ -0,0 +1,408 @@
# 招聘信息管理双 Sheet 导入设计文档
## 1. 背景
当前招聘信息管理已经具备以下能力:
- 主信息维护:`ccdi_staff_recruitment`
- 历史工作经历维护:`ccdi_staff_recruitment_work`
- 页面编辑态支持手工维护历史工作经历
- 导入能力仍拆分为两条独立入口:
- `招聘信息导入`
- `历史工作经历导入`
对比已经完成改造的招投标信息维护,招聘信息管理当前导入模式存在三个问题:
1. 导入入口与当前主从结构不一致,用户需要自行判断先后顺序
2. 双 Sheet 场景无法在同一个文件中同时提交主信息和工作经历
3. 失败记录无法直接定位失败来源 Sheet 和 Excel 行号
本次需求要求参考招投标信息维护的双 Sheet 模式,将招聘信息管理改造成单入口双 Sheet 导入,并补齐失败定位信息。
## 2. 目标与范围
### 2.1 目标
1. 招聘信息管理页面只保留一个导入按钮
2. 导入模板改为双 Sheet
- `招聘信息`
- `历史工作经历`
3. 后端统一用一个异步任务处理整份文件
4. 支持以下三类导入方式:
- 只导 `招聘信息` Sheet
- 只导 `历史工作经历` Sheet
- 两个 Sheet 同时导入
5. 失败列表必须展示:
- 失败 Sheet
- 失败行号
- 失败原因
### 2.2 已确认业务口径
1. 页面导入入口合并为单按钮
2. `历史工作经历` Sheet 允许独立导入
3. `历史工作经历` 独立导入时,继续按库内已存在的招聘主信息匹配
4. 如果某个招聘记录已经存在历史工作经历,再次导入该招聘记录的工作经历时直接报错,不允许覆盖旧数据
5. 失败展示统一为一个列表,通过 `失败Sheet``失败行号``失败原因` 定位问题
### 2.3 非目标
1. 不调整招聘信息手工新增、编辑、详情、列表查询主体逻辑
2. 不调整编辑页中手工维护历史工作经历的交互
3. 不新增数据库表,不修改现有表结构
4. 不保留旧的“导入工作经历”按钮或旧接口作为兼容入口
5. 不新增独立任务中心或额外状态体系
## 3. 现状分析
### 3.1 前端现状
招聘信息管理页面当前有两个导入按钮:
1. `导入`
2. `导入工作经历`
两个按钮分别对应不同模板和不同上传接口:
- `/ccdi/staffRecruitment/importTemplate`
- `/ccdi/staffRecruitment/workImportTemplate`
- `/ccdi/staffRecruitment/importData`
- `/ccdi/staffRecruitment/importWorkData`
页面当前仅维护一套失败记录弹窗,但会根据 `currentImportType` 切换展示字段,本质仍然是两条独立导入链路。
### 3.2 后端现状
当前招聘导入服务拆成两条独立任务:
1. `importRecruitment(List<CcdiStaffRecruitmentExcel>)`
2. `importRecruitmentWork(List<CcdiStaffRecruitmentWorkExcel>)`
对应的异步实现也拆成:
1. `importRecruitmentAsync(...)`
2. `importRecruitmentWorkAsync(...)`
其中历史工作经历导入现有语义为:
- 允许独立导入
-`recruitId` 匹配招聘主信息
- 若同一 `recruitId` 任一行失败,则整组不落库
- 导入前会删除该 `recruitId` 旧工作经历,再导入新数据
本次要调整的是最后一条覆盖语义:改为“已有旧记录则失败,不覆盖”。
## 4. 方案对比
### 4.1 方案 A单入口 + 单任务编排 + 单失败列表
做法:
- 页面只保留一个导入按钮
- 后端一次读取双 Sheet
- 用一个异步任务统一处理主信息和工作经历
- 用一个失败列表展示全部失败记录
优点:
1. 与招投标信息维护双 Sheet 模式一致
2. 支持同文件主从联动导入
3. 失败展示口径最统一
4. 页面交互最简单
缺点:
1. 需要把当前两条招聘导入任务重构为一条统一任务
### 4.2 方案 B单入口 + 双任务复用 + 前端聚合失败
做法:
- 上传一个双 Sheet 文件
- 后端仍拆成主信息任务和工作经历任务
- 前端再聚合两个任务的失败记录
缺点:
1. 工作经历 Sheet 无法天然引用“本次主 Sheet 刚成功导入的数据”
2. 会引入跨任务依赖和前端拼装逻辑
3. 导入状态与失败记录口径不够干净
### 4.3 方案 C仅合并模板和按钮不做真实双 Sheet 联动
做法:
- 页面只有一个导入按钮
- 模板做成双 Sheet
- 但工作经历仍只能引用库内已有招聘主信息
缺点:
1. 无法完成同文件主从联动
2. 不能真正对齐招投标信息维护的双 Sheet 模式
3. 业务理解成本仍然偏高
### 4.4 最终选择
采用方案 A`单入口 + 单异步任务 + 单失败列表`
理由:
1. 最符合“参考招投标信息维护双 Sheet 导入”的目标
2. 业务链路闭环最完整
3. 用户侧交互最清晰
4. 后续维护成本最低
## 5. 总体设计
### 5.1 模板设计
统一模板文件名:`招聘信息管理导入模板`
模板包含两个 Sheet
1. `招聘信息`
2. `历史工作经历`
约束:
1. 两个 Sheet 都允许为空表头,但整份文件至少一个 Sheet 有数据
2. `历史工作经历` Sheet 保持当前字段结构,不新增业务字段
3. `招聘信息` Sheet 保持当前字段结构,不新增业务字段
### 5.2 页面交互设计
页面顶部仅保留一个 `导入` 按钮。
上传弹窗说明文案明确提示:
- 模板包含 `招聘信息``历史工作经历` 两个 Sheet
- 支持只填写一个 Sheet也支持两个 Sheet 同时填写
上传成功后:
1. 只返回一个 `taskId`
2. 前端只启动一套轮询
3. 页面只保留一个“查看导入失败记录”入口
### 5.3 接口设计
保留并调整以下接口:
1. `POST /ccdi/staffRecruitment/importTemplate`
- 输出双 Sheet 模板
2. `POST /ccdi/staffRecruitment/importData`
- 一次接收整份双 Sheet 文件
- 返回统一 `taskId`
3. `GET /ccdi/staffRecruitment/importStatus/{taskId}`
- 查询统一任务状态
4. `GET /ccdi/staffRecruitment/importFailures/{taskId}`
- 查询统一失败记录
移除以下独立导入入口:
1. `POST /ccdi/staffRecruitment/workImportTemplate`
2. `POST /ccdi/staffRecruitment/importWorkData`
## 6. 后端处理流程设计
### 6.1 任务编排
`/importData` 收到文件后,一次读取两个 Sheet
- `招聘信息` -> `List<CcdiStaffRecruitmentExcel>`
- `历史工作经历` -> `List<CcdiStaffRecruitmentWorkExcel>`
若两个 Sheet 都为空,直接返回错误:`至少需要一条数据`
否则初始化一条统一导入任务,并异步执行以下两个阶段:
1. 阶段一:处理 `招聘信息` Sheet
2. 阶段二:处理 `历史工作经历` Sheet
### 6.2 阶段一:招聘信息 Sheet
沿用现有主信息导入规则:
1. 招聘记录编号不能为空
2. 招聘项目名称、职位名称、职位类别、职位描述、候选人姓名、学历、证件号码、毕业院校、专业、毕业年月、录用情况不能为空
3. 证件号码格式必须合法
4. 毕业年月必须为 `YYYYMM`
5. 录用情况只能填写合法枚举
6. 招聘类型继续按现有逻辑推断
7. 数据库中已存在的 `recruitId` 直接失败
8. 文件内重复 `recruitId` 直接失败
阶段一成功后,建立“本次主 Sheet 成功导入的招聘记录映射”,供阶段二匹配使用。
### 6.3 阶段二:历史工作经历 Sheet
#### 6.3.1 匹配规则
工作经历按 `recruitId` 分组处理。
每组匹配顺序固定为:
1. 先匹配本次 `招聘信息` Sheet 中成功导入的招聘主信息
2. 再匹配数据库中已存在的招聘主信息
若都匹配不到,整组失败。
#### 6.3.2 基础校验
沿用并收敛现有规则:
1. `recruitId` 不能为空
2. 候选人姓名不能为空
3. 招聘项目名称不能为空
4. 职位名称不能为空
5. 排序号不能为空且必须大于 0
6. 工作单位不能为空
7. 岗位不能为空
8. 入职年月不能为空,格式必须为 `YYYY-MM`
9. 离职年月若有值,格式必须为 `YYYY-MM`
10. 候选人姓名、招聘项目名称、职位名称必须与匹配到的招聘主信息一致
11. 匹配到的招聘主信息 `recruitType` 必须为 `SOCIAL`
12. 同一 `recruitId` 下,文件内 `sortOrder` 不能重复
#### 6.3.3 已有工作经历报错规则
若数据库中某个 `recruitId` 已经存在任意历史工作经历记录,则本次再导入该 `recruitId` 的工作经历时:
1. 直接整组失败
2. 不删除旧记录
3. 不做覆盖
4. 不做部分追加
失败原因统一返回明确文案,例如:
`招聘记录编号[xxx]已存在历史工作经历,不允许重复导入`
#### 6.3.4 分组成功与失败策略
`recruitId` 维度整组处理:
1. 若该组任意一行失败,则整组不落库
2. 若该组全部通过校验,则整组批量插入
这样可以保证同一招聘记录下的工作经历要么整组成功,要么整组失败,不会出现排序半成功半失败的问题。
### 6.4 失败记录结构
统一失败记录对象新增以下字段:
1. `sheetName`
2. `sheetRowNum`
3. `errorMessage`
并继续保留必要业务定位字段:
1. `recruitId`
2. `recruitName`
3. `posName`
4. `candName`
5. `candId`
6. `companyName`
7. `positionName`
其中:
- 主 Sheet 失败时,`sheetName = 招聘信息`
- 工作经历 Sheet 失败时,`sheetName = 历史工作经历`
- `sheetRowNum` 返回 Excel 实际数据行号
## 7. 前端展示设计
### 7.1 导入入口
页面只保留一个导入按钮,不再保留 `导入工作经历`
### 7.2 轮询状态
前端本地只维护一条最近导入任务状态:
1. `taskId`
2. `status`
3. `hasFailures`
4. `totalCount`
5. `successCount`
6. `failureCount`
不再按导入类型区分任务状态。
### 7.3 失败弹窗
失败弹窗统一展示一个列表,至少包含以下列:
1. `失败Sheet`
2. `失败行号`
3. `招聘记录编号`
4. `招聘项目名称`
5. `职位名称`
6. `候选人姓名`
7. `工作单位`
8. `失败原因`
展示规则:
1. 主 Sheet 失败时,`工作单位` 为空
2. 工作经历 Sheet 失败时,`工作单位` 有值
3. `失败行号` 展示为 `第X行`
## 8. 影响范围
### 8.1 后端
- `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`
### 8.2 前端
- `ruoyi-ui/src/views/ccdiStaffRecruitment/index.vue`
- `ruoyi-ui/src/api/ccdiStaffRecruitment.js`
### 8.3 不影响范围
1. 招聘详情页展示结构
2. 招聘手工新增、编辑、删除接口
3. 数据库结构与 SQL 迁移
4. 路由、菜单、权限标识
## 9. 验证方案
### 9.1 后端验证
1. `mvn -pl ccdi-info-collection,ruoyi-admin -am -DskipTests compile`
### 9.2 前端验证
1. `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && cd ruoyi-ui && npm run build:prod`
### 9.3 页面实测
必须通过真实页面下载模板后再构造测试文件,至少覆盖:
1. 只导 `招聘信息` Sheet 成功
2. 只导 `历史工作经历` Sheet匹配库内已有主信息成功
3. 双 Sheet 同时导入,工作经历引用本次主 Sheet 成功
4. 工作经历命中已存在旧记录时报错
5. 工作经历命中非社招主信息时报错
6. 文件内工作经历排序号重复时报错
7. 失败弹窗正确展示 `失败Sheet``失败行号``失败原因`
测试结束后需关闭测试过程中启动的前后端进程。
## 10. 结论
本次采用“单入口 + 双 Sheet 模板 + 单异步任务 + 单失败列表”的最短路径实现:
1. 页面导入入口统一为一个按钮
2. 双 Sheet 模板统一为 `招聘信息` + `历史工作经历`
3. 工作经历继续允许独立导入
4. 工作经历匹配顺序为“本次主 Sheet 成功数据优先,其次数据库已有主信息”
5. 数据库里已存在工作经历时,本次重复导入直接报错,不覆盖旧数据
6. 失败列表统一展示失败 Sheet、失败行号和失败原因

View File

@@ -0,0 +1,23 @@
# 招聘信息管理双 Sheet 导入设计记录
## 本次产出
- 新增设计文档:`docs/design/2026-04-23-staff-recruitment-dual-sheet-import-design.md`
## 设计结论
- 招聘信息管理导入入口收口为单按钮
- 导入模板调整为 `招聘信息` + `历史工作经历` 双 Sheet
- 后端采用单异步任务统一处理整份文件
- 工作经历支持独立导入
- 若数据库中已存在某招聘记录的历史工作经历,则再次导入时直接失败,不覆盖旧数据
- 失败列表统一展示 `失败Sheet``失败行号``失败原因`
## 影响范围
- 当前仅新增设计文档与设计记录
- 本轮未修改后端业务代码、前端页面代码、数据库脚本
## 后续动作
- 待用户确认设计文档后,分别输出后端与前端实施计划