From ff9627d0d9a56c40bd92acacf8ca6a46ad628cbf Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Thu, 23 Apr 2026 09:40:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8B=9B=E8=81=98=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=8F=8CSheet=E5=AF=BC=E5=85=A5=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ff-recruitment-dual-sheet-import-design.md | 408 ++++++++++++++++++ ...uitment-dual-sheet-import-design-record.md | 23 + 2 files changed, 431 insertions(+) create mode 100644 docs/design/2026-04-23-staff-recruitment-dual-sheet-import-design.md create mode 100644 docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-design-record.md diff --git a/docs/design/2026-04-23-staff-recruitment-dual-sheet-import-design.md b/docs/design/2026-04-23-staff-recruitment-dual-sheet-import-design.md new file mode 100644 index 00000000..14d42d82 --- /dev/null +++ b/docs/design/2026-04-23-staff-recruitment-dual-sheet-import-design.md @@ -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)` +2. `importRecruitmentWork(List)` + +对应的异步实现也拆成: + +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` +- `历史工作经历` -> `List` + +若两个 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、失败行号和失败原因 diff --git a/docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-design-record.md b/docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-design-record.md new file mode 100644 index 00000000..42a019bf --- /dev/null +++ b/docs/reports/implementation/2026-04-23-staff-recruitment-dual-sheet-import-design-record.md @@ -0,0 +1,23 @@ +# 招聘信息管理双 Sheet 导入设计记录 + +## 本次产出 + +- 新增设计文档:`docs/design/2026-04-23-staff-recruitment-dual-sheet-import-design.md` + +## 设计结论 + +- 招聘信息管理导入入口收口为单按钮 +- 导入模板调整为 `招聘信息` + `历史工作经历` 双 Sheet +- 后端采用单异步任务统一处理整份文件 +- 工作经历支持独立导入 +- 若数据库中已存在某招聘记录的历史工作经历,则再次导入时直接失败,不覆盖旧数据 +- 失败列表统一展示 `失败Sheet`、`失败行号`、`失败原因` + +## 影响范围 + +- 当前仅新增设计文档与设计记录 +- 本轮未修改后端业务代码、前端页面代码、数据库脚本 + +## 后续动作 + +- 待用户确认设计文档后,分别输出后端与前端实施计划