# 招聘信息采集监测分析:外部岗位导入与指纹同步实施计划 ## 1. 文档目的与范围 本文定义以下两部分的完整实施方案: 1. 在 `/Users/lapuda/code/excel_upload` 建设供外部人员上传岗位 Excel 的独立系统; 2. 在 `shz-backend` 中建设岗位文件的定时拉取、批次导入、岗位指纹比对、增量新增、缺失逻辑删除,以及“招聘信息采集监测分析”菜单能力。 本方案以外部 Excel 的**全量快照**为前提。每个成功上传并通过校验的文件代表其声明来源范围内的当前全部有效岗位。 ### 1.1 本期明确的同步规则 外部导入岗位必须带有独立的外部标记,且只能在外部岗位集合内部进行指纹比对: ```text 上一成功批次的外部岗位指纹集合 = previous 本次已校验、已去重的外部岗位指纹集合 = current current ∩ previous:保留原岗位 ID;字段有变化时原地更新 current - previous:新增外部岗位 previous - current:逻辑删除外部岗位 ``` “删除”采用现有系统惯例的**逻辑删除**,即设置 `del_flag = '2'`,并同步下架岗位;不物理删除记录,不影响内部手工岗位、招聘会岗位或未标记为外部导入的历史岗位。 若一个已逻辑删除的外部岗位在后续文件中再次出现,则恢复原岗位记录和原岗位 ID,而不是再创建一条新岗位。 ### 1.2 不在本期自动执行的事项 - 不自动删除或合并既有历史 `job` 数据; - 不自动删除企业数据;岗位快照缺失只影响外部岗位,不影响 `company`; - 不将企业名称相近的记录自动视为同一企业; - 不部署、不重启服务、不执行生产数据库写操作。 ## 2. 已审查的数据现状 审查基于 2026-07-21 的生产 HighGo 数据库只读快照,以及 Excel 样例 `石河子市数据20260714-0720.xlsx`。 ### 2.1 数据量与关键风险 | 对象 | 数量 | 关键结论 | |---|---:|---| | `job` | 1,601 | 1,601 条均无 `job_url`,均无 `row_id` | | 有效 `job` | 1,582 | 269 条未关联 `company_id` | | `company` | 1,972 | 企业性质全部为空,存在 130 组同名企业 | | `row_work` | 2,230 | `Aae397`、`Collect_time` 两个日期字段均为空 | | Excel 样例 | 2,067 | 40 列、6 个来源、岗位链接和采集日期均完整 | 当前 `job` 表没有岗位链接和原始行关联,不能��于��靠判断新 Excel 中的岗位是否已经存在。当前 `company` 表中有 139 条超出首条的同名企业记录,也不能通过“企业名称取第一条”的方式安全关联。 ### 2.2 当前库质量指标 | 表 | 指标 | 数量 | 比例 | |---|---|---:|---:| | `job` | 最低薪资为空 | 303 | 18.93% | | `job` | 最高薪资为空 | 355 | 22.17% | | `job` | 薪资区间非法 | 6 | 0.37% | | `job` | 经纬度任一为空 | 1,595 | 99.63% | | `job` | 岗位链接为空 | 1,601 | 100% | | `job` | 原始行 ID 为空 | 1,601 | 100% | | `company` | 所在地为空 | 928 | 47.06% | | `company` | 行业为空 | 1,385 | 70.23% | | `company` | 规模为空 | 1,062 | 53.85% | | `company` | 企业性质为空 | 1,972 | 100% | | `company` | 企业简介为空 | 1,498 | 75.96% | 现有 `job` 表中有 318 组疑似重复岗位键组,涉及 657 条岗位、339 条超出首条的记录;`company` 表有 130 组同名企业,涉及 269 条企业、139 条超出首条的记录。 因此,本期外部导入不应将历史库无来源链接的数据纳入“删除比对”。首批导入只比较带外部导入标记、且已具备指纹的岗位。 ### 2.3 当前 Excel 样例摘要 样例工作表为 `合并数据`,共 2,067 行。来源分布如下: | `ORG` 来源 | 行数 | |---|---:| | 石河子招聘网 | 1,404 | | 智联招聘 | 541 | | 天互招聘网 | 79 | | 猎聘网 | 17 | | 乌鲁木齐快聘网 | 15 | | 前程无忧 | 11 | 样例中有 1,670 个不同的“来源 + 岗位链接”组合,397 行是批内重复组合。导入前必须先按岗位指纹去重,不能把同一岗位重复写入数据库。 样例字段质量: | Excel 字段 | 非空率 | 导入策略 | |---|---:|---| | `Aca112` 岗位名称 | 100% | 强制字段 | | `Acb22a` 岗位描述 | 100% | 强制字段 | | `SalaryLow` / `SalaryHight` | 100% | 强制数值和区间校验 | | `Aae397` 发布日期 | 100% | 解析并保存 | | `Collect_time` 采集日期 | 100% | 解析并保存 | | `AAB004` 企业名称 | 100% | 强制字段、企业匹配键 | | `ORG` 来源 | 100% | 强制字段、来源映射键 | | `ACE760` 岗位链接 | 100% | 强制字段、优先岗位指纹键 | | `AAE006` 岗位地址 | 31.79% | 非强制,按区县/城市回退 | | `Std_class` 岗位分类 | 94.82% | 保存原始分类并做标准化映射 | | `AAB092` 企业简介 | 32.08% | 有值时补充企业信息 | | `IndustryType` | 18.82% | 可选行业补充 | | `IndustrySub` | 5.37% | 可选二级行业补充 | | `Num_employers` | 99.27% | 企业规模原始值/回退值 | | `Num_OK` | 26.17% | 企业规模标准值优先 | | `AAB019_OK` | 26.17% | 企业性质候选值,须映射 | | `AAE004` 联系人 | 27.72% | 仅补充企业空联系人 | | `AAE005` 联系电话 | 0% | 本期不能补充联系电话 | ## 3. 目标架构 ```text 外部人员 │ 登录、上传 Excel、查看上传结果 ▼ excel_upload 外部上传系统 │ 文件元数据、SHA-256、校验状态、内部下载接口 ▼ shz-backend Quartz 定时任务 │ 获取最新文件清单 → 下载 → 暂存解析 → 校验/去重 ▼ 导入批次与原始行暂存 │ 计算 identity fingerprint 和 content fingerprint ▼ 外部岗位指纹集合比对 ├─ 指纹已存在:保留岗位 ID,按内容更新 ├─ 本次新指纹:新增岗位 └─ 历史指纹未出现:逻辑删除外部岗位 ▼ job / company / ES / 监测数据 ▼ 来源管理、入库监测、采集趋势、统计分析 ``` ## 4. 外部岗位特殊标记与指纹设计 ### 4.1 `job` 表新增字段 不复用现有含义不明的 `data_source='5'`,增加专用字段。 | 字段 | 类型建议 | 说明 | |---|---|---| | `external_import_flag` | `CHAR(1)` | `1` 外部文件导入岗位,`0` 非外部岗位;默认 `0` | | `external_source_code` | `VARCHAR(64)` | 稳定来源编码,如 `zhaopin`、`51job` | | `external_source_name` | `VARCHAR(200)` | Excel 原始 `ORG` 名称,用于审计展示 | | `job_fingerprint` | `VARCHAR(64)` | 岗位身份指纹,SHA-256 十六进制值 | | `job_content_fingerprint` | `VARCHAR(64)` | 岗位内容指纹,用于判断同一岗位是否需要更新 | | `external_batch_id` | `VARCHAR(64)` | 最近一次成功见到该岗位的导入批次 | | `external_first_seen_at` | `TIMESTAMP` | 首次外部导入时间 | | `external_last_seen_at` | `TIMESTAMP` | 最近一次在成功快照中出现的时间 | 在历史清洗完成后,增加仅针对有效外部岗位的部分唯一索引: ```sql CREATE UNIQUE INDEX uk_job_external_active_fingerprint ON shz.job (job_fingerprint) WHERE external_import_flag = '1' AND del_flag = '0'; ``` 指纹中已经包含来源编码,因此不同来源中相同 URL 或同名岗位不会冲突。 ### 4.2 岗位身份指纹 岗位身份指纹用于判断“这个岗位是否还是同一个岗位”,不应包含经常变化的描述、薪资、采集日期等内容。 #### 优先规则:来源 + 规范化岗位链接 ```text fingerprint_version = FP_V1 identity_payload = FP_V1 | external_source_code | normalized_job_url job_fingerprint = SHA-256(identity_payload) ``` `normalized_job_url` 的标准化规则: - 删除前后空白; - URL scheme 和 host 转小写; - 规范化末尾 `/`; - 去除已确认无业务含义的推广参数,如 `utm_*`; - 不删除可能影响岗位身份的路径、岗位 ID 或业务查询参数; - 解析失败时记录行错误,不静默生成错误指纹。 样例所有岗位都具备 `ACE760`,因此正常情况下均使用该规则。 #### 降级规则:无岗位链接时 只有历史文件或异常数据没有岗位链接时,才允许使用低置信度降级指纹: ```text FP_V1 | external_source_code | normalized_company_name | normalized_job_title | normalized_city_or_county | salary_low | salary_high ``` 降级指纹必须在导入结果中标记为 `LOW_CONFIDENCE`。若同一批次出现多个同指纹但内容不同的行,则阻止自动发布或进入人工确认,不允许任意覆盖。 ### 4.3 岗位内容指纹 身份指纹相同但岗位内容变化时,不应删除并重新创建岗位。应保留原 `job_id`,对允许同步的字段执行原地更新。 ```text content_payload = job_title | description | salary_low | salary_high | education | experience | vacancies | job_address | area_code | job_category | company_name | source_job_url | collect_date job_content_fingerprint = SHA-256(canonical_content_payload) ``` 处理规则: | 身份指纹 | 内容指纹 | 动作 | |---|---|---| | 已存在 | 相同 | 保留,更新 `external_last_seen_at` 和批次 ID | | 已存在 | 不同 | 保留 `job_id`,更新外部可同步字段,记录“内容更新” | | 不存在 | 任意 | 新增外部岗位 | | 历史存在但已逻辑删除 | 任意 | 恢复原岗位,再按内容更新 | ### 4.4 删除边界 删除比对只能作用于以下集合: ```text external_import_flag = '1' AND del_flag = '0' AND external_source_code IN 本批次完整来源范围 ``` 不允许比较或删除: - `external_import_flag = '0'` 的内部岗位; - 当前无来源、无链接、无指纹的历史岗位; - 招聘会、企业自行发布、后台人工录入等其他业务岗位; - 不属于本批次文件声明来源范围的外部岗位。 逻辑删除时统一设置: ```text del_flag = '2' job_status = '1' is_publish = 0 update_by = 'external-import' update_time = 当前时间 ``` 删除记录必须保留原 `job_id`、原指纹、最后出现批次和删除批次,支持后续恢复和审计。 ## 5. 导入批次与数据库设计 ### 5.1 新增表 #### `job_import_batch` 每次文件导入一条批次记录。 | 字段 | 说明 | |---|---| | `batch_id` | UUID 或雪花 ID | | `file_id` | 外部上传系统中的文件版本 ID | | `file_name` | 原始文件名 | | `file_sha256` | 文件摘要;成功导入后禁止重复处理 | | `snapshot_scope` | 本批次覆盖的来源编码集合 | | `fingerprint_version` | 如 `FP_V1` | | `status` | `DOWNLOADING`、`VALIDATING`、`STAGING`、`RECONCILING`、`SUCCESS`、`FAILED`、`BLOCKED` | | `total_rows` | Excel 有效行数 | | `valid_rows` | 可发布行数 | | `invalid_rows` | 解析/校验失败行数 | | `duplicate_rows` | 批内重复行数 | | `insert_count` | 新增岗位数 | | `update_count` | 同身份岗位内容更新数 | | `keep_count` | 指纹和内容都未变的保留数 | | `restore_count` | 已删除后重新出现、被恢复的岗位数 | | `delete_count` | 本次逻辑删除岗位数 | | `error_summary` | 批次级异常摘要 | | `started_at` / `completed_at` | 执行时间 | #### `job_import_row` 保存每一行的处理结果,支持错误下载、重复定位和审计。 | 字段 | 说明 | |---|---| | `batch_id` | 所属导入批次 | | `excel_row_number` | Excel 真实行号 | | `source_name` / `source_code` | 原始来源与标准来源 | | `job_fingerprint` | 岗位身份指纹 | | `content_fingerprint` | 岗位内容指纹 | | `raw_payload` | 原始字段 JSON 或文本快照 | | `validation_status` | `VALID`、`INVALID`、`DUPLICATE`、`LOW_CONFIDENCE` | | `result_status` | `INSERTED`、`UPDATED`、`KEPT`、`RESTORED`、`SKIPPED`、`FAILED` | | `error_code` / `error_message` | 行错误详情 | | `job_id` | 最终关联岗位 ID | #### 来源别名映射 建议新增 `job_import_source_alias`,避免把 Excel 的 `ORG` 字符串直接硬编码在 Java 中。 | 字段 | 说明 | |---|---| | `alias_name` | Excel 原始 `ORG`,如“智联招聘” | | `source_code` | 稳定来源编码,如 `zhaopin` | | `website_id` | 关联 `website_management` | | `is_active` | 是否允许导入 | | `snapshot_required` | 全量快照中是否必须存在该来源 | | `remark` | 映射依据与业务备注 | ### 5.2 数据迁移原则 - 所有 SQL 均采用增量 migration;不执行现有 `job_data_tables.sql` 中的 `DROP TABLE`; - 先建立批次、暂存和字段,再接通调度; - 历史岗位默认 `external_import_flag = '0'`,不参与首批删除; - 先在暂存层完成去重和校验,再写入 `job`、`company`; - 只有整个发布事务成功,批次才能标为 `SUCCESS`; - 发布失败时保留上一个成功快照和其可见岗位。 ## 6. Excel 字段映射与质量策略 ### 6.1 岗位映射 | Excel 字段 | 目标字段 | 规则 | |---|---|---| | `Aca112` | `job.job_title` | 强制;去首尾空白;空值拒绝该行 | | `Acb22a` | `job.description` | 强制;保留全文 | | `SalaryLow` | `job.min_salary` | 强制;转整数;必须大于等于 0 | | `SalaryHight` | `job.max_salary` | 强制;必须不小于最低薪资 | | `Salary` | 原始薪资文本 | 保留在原始行,供审计和异常兜底 | | `Aae397` | `job.posting_date` | 支持 Excel 日期数值与文本日期 | | `Collect_time` | 批次原始采集日期 | 支持 Excel 日期数值与文本日期 | | `Acb240` | `job.vacancies` | 优先解析;为空时回退 `Recruit_Num` | | `Recruit_Num` | `job.vacancies` | 兜底字段,不覆盖已解析的 `Acb240` | | `Aac011` | `job.education` | 学历别名标准化后写入字典编码 | | `Experience` | `job.experience` | 经验别名标准化后写入字典编码 | | `Std_class` | `job.job_category` | 记录原始分类,并按映射表生成标准分类 | | `AAE006` | `job.job_address` | 保存详细地址;可为空 | | `AAE006` / `County` / `City` | `job.job_location` | 按详细地址、区县、城市顺序回退 | | `County` | `job.job_location_area_code` | 匹配区域字典;不匹配时记录告警 | | `ORG` | 来源相关字段 | 必须通过来源别名表解析;未知来源阻止发布 | | `ACE760` | `job.job_url` | 强制;规范化后生成身份指纹 | ### 6.2 企业映射 | Excel 字段 | 目标字段 | 规则 | |---|---|---| | `AAB004` | `company.name`、`job.company_name` | 强制;参与企业标准化匹配 | | `AAE006` / `County` / `City` | `company.location` | 仅补充缺失字段,不覆盖人工维护数据 | | `AAB092` | `company.description` | 有值时补充;空值不得覆盖已有简介 | | `IndustrySub` / `IndustryType` | `company.industry` | 二级行业优先;必须通过行业映射或保留原值 | | `Num_OK` / `Num_employers` | `company.scale` | 标准规模优先;原始人数区间作为回退 | | `AAB019_OK` / `AAB019` | `company.company_nature` | 通过企业性质别名表映射;未知值待确认 | | `AAE004` | `company.contact_person` | 仅补充已有空值 | | `AAE005` | `company.contact_person_phone` | 本样例全空,不更新现有电话 | ### 6.3 必填、告警和拒绝规则 #### 行级拒绝 - 岗位名称为空; - 企业名称为空; - 来源为空或不在来源映射表; - 岗位链接为空且无法生成唯一的低置信度降级指纹; - 最低/最高薪资为空、非数值或最低薪资大于最高薪资; - 发布日期、采集日期无法解析; - 同一批次同指纹多条记录且无法判定最新/最完整行。 #### 允许导入但记录质量告警 - `AAE006` 为空,使用区县或城市回退; - 区县无法映射到区域字典; - 学历、经验、企业性质、行业无法映射标准字典; - 招聘人数无法解析; - 企业匹配到多个同名企业; - 无岗位链接导致使用低置信度指纹; - 经纬度为空,等待异步地理编码。 ## 7. 全量快照导入流程 ### 7.1 定时任务入口 沿用 RuoYi Quartz 的任务管理机制,新增可由 `sys_job` 调用的入口,例如: ```text jobImportCron.importLatestExternalSnapshot() ``` 默认延续旧逻辑的周三、周六凌晨 03:00 调度;实际 cron 表达式由 `sys_job` 初始化数据配置,并允许后台修改。迁移脚本会先以“暂停”状态创建任务,只有完成外部服务 URL、内部令牌和来源映射配置后才由管理员启用。任务必须设置为不允许并发执行。 ### 7.2 流程步骤 1. 获取外部系统的最新文件元数据:文件 ID、上传时间、SHA-256、来源范围、下载地址; 2. 若同一 SHA-256 已有成功批次,直接跳过,记录“重复文件”; 3. 获取分布式锁,防止多节点和人工触发并发导入; 4. 下载至临时目录,校验扩展名、真实文件类型、文件大小、工作表和表头; 5. 用流式解析器读取 Excel,逐行保存到暂存记录; 6. 解析来源别名、日期、薪资、学历、经验、区域、企业字段; 7. 计算岗位身份指纹和内容指纹; 8. 先做批内指纹去重:同一指纹保留采集时间更新且字段最完整的记录; 9. 校验批次覆盖范围。若要求全量文件包含的来源缺失、有效行数为 0、删除比例异常高,则将批次标记为 `BLOCKED`,不发布; 10. 在同一个发布事务中执行岗位比对、新增、更新、恢复和逻辑删除; 11. 更新企业的可补充字段,但不删除企业、不盲目覆盖人工维护字段; 12. 写入 `job_import_batch`、`job_import_row`、入库监测明细和趋势统计; 13. 提交事务后,异步刷新 ES、岗位分类缓存、岗位名称联想缓存; 14. 任务日志记录批次 ID、文件摘要、各类统计和异常摘要。 ### 7.3 集合比对伪代码 ```text current = staged_valid_rows .deduplicate_by(job_fingerprint) .where(source_code in snapshot_scope) previous = active jobs .where(external_import_flag = '1') .where(external_source_code in snapshot_scope) .index_by(job_fingerprint) for row in current: if previous contains row.job_fingerprint: keep existing job_id if content fingerprint changed: update external-synchronized fields set last_seen_batch_id = current batch remove fingerprint from previous else if logically-deleted external job exists for fingerprint: restore original job_id and update fields else: insert a new external job for each remaining job in previous: logically delete job ``` ### 7.4 删除保护阈值 为防止来源异常、错误文件或空文件导致批量下线,发布前必须执行保护规则: - 当前有效行数为 0:禁止发布; - 必填来源范围不完整:禁止发布; - 批内错误率超过可配置阈值:禁止发布; - 本次拟删除外部岗位比例超过可配置阈值(建议默认 30%):标记 `BLOCKED`,要求管理员确认; - 同一来源数据量相较上一成功批次异常下降时:生成预警并可阻止发布; - 未成功完成批次时,绝不执行删除阶段。 ## 8. 外部 Excel 上传系统 ### 8.1 部署目录与技术边界 在 `/Users/lapuda/code/excel_upload` 建立独立服务。推荐使用 FastAPI、持久化文件元数据和本地/对象存储上传目录;其职责仅限于文件接收、校验、保管和安全提供下载,不直接访问业务数据库。 ### 8.2 页面功能 - 登录; - 上传 Excel; - 显示模板要求、校验结果、来源范围和预计有效行数; - 查看历史上传记录; - 查看文件是否已被后端导入、导入批次状态和错误摘要; - 下载原始文件或错误报告; - 管理员可作废错误上传文件,但不可修改已成功导入文件内容。 ### 8.3 外部系统接口 | 接口 | 访问者 | 说明 | |---|---|---| | `POST /api/v1/uploads` | 外部登录用户 | 上传并基础校验 Excel | | `GET /api/v1/files` | 外部登录用户 | 查询本人/授权范围内历史文件 | | `GET /api/v1/files/{fileId}` | 外部登录用户 | 查询文件元数据和校验状态 | | `GET /api/v1/files/{fileId}/download` | 外部登录用户 | 下载自己的原始文件 | | `GET /api/internal/latest` | `shz-backend` | 返回最新可导入文件的元数据 | | `GET /api/internal/files/{fileId}/download` | `shz-backend` | 下载指定版本文件流 | | `GET /health` | 监控 | 健康检查 | 内部接口使用独立的 Bearer Token 或 HMAC 签名;令牌、登录密码散列、上传大小限制和文件目录全部通过环境变量或安全配置管理,不写死在代码、Dockerfile 或前端页面。 ### 8.4 文件安全要求 - 仅接收 `.xlsx`; - 校验文件签名和 ZIP 结构,不只依赖扩展名; - 设置上传大小、解压后大小和行数上限; - 拒绝加密文件、宏文件和异常压缩比文件; - 文件以服务端 UUID 命名,原始文件名仅作显示; - 保存 SHA-256;相同摘要文件不重复产生待导入版本; - 保存上传人、上传时间、来源范围和校验结果; - 上传文件目录使用独立 volume,不能暴露为静态目录浏览。 ## 9. 招聘信息采集监测分析菜单 父菜单:`招聘信息采集监测分析` 子菜单使用四个汉字名称: ```text 招聘信息采集监测分析 ├─ 来源管理 ├─ 入库监测 ├─ 采集趋势 └─ 统计分析 ``` ### 9.1 来源管理 复用并扩展现有 `website_management`: - 来源名称、来源编码、官网地址、所属单位、启停状态; - Excel `ORG` 别名映射; - 是否必须出现在全量快照中; - 最近上传时间、最近成功导入时间、最近数据量; - 禁止匿名访问,按后台权限管理。 ### 9.2 入库监测 以 `job_import_batch` 和 `job_import_row` 为准展示: - 文件名、摘要、来源范围、任务状态; - 总行数、有效行、错误行、批内重复行; - 新增、更新、保留、恢复、逻辑删除数量; - 各来源成功/失败/重复明细; - 错误行下载; - 管理员重试、作废、确认高删除比例批次; - 对接已有 `JobDataStorageDetection`、`JobDataStorageDetail` 数据结构,自动生成而非手工录入。 ### 9.3 采集趋势 展示 7/30/90 天及自定义时间范围内: - 文件上传数量; - 有效原始行数量; - 新增、更新、保留、恢复、删除、失败、重复数; - 全部来源总量和单来源趋势; - 来源数据量环比、异常下降预警; - 扩展当前仅 `total` 的趋势查询,支持 `source_code` 维度。 ### 9.4 统计分析 基于成功发布的外部岗位和现有岗位统计: - 行业、薪资、区域、学历、经验、岗位分类分布; - 外部来源筛选与时间范围筛选; - 地址缺失率、行业未映射率、学历/经验未映射率; - 来源质量对比; - 与现有 `StaticsController` 接口衔接,但生成统计数据的操作只能由任务或管理员触发。 ## 10. 权限与安全 建议新增权限: | 权限 | 说明 | |---|---| | `cms:recruit:source:list` | 查询来源 | | `cms:recruit:source:edit` | 维护来源和别名 | | `cms:recruit:monitor:list` | 查询导入批次和明细 | | `cms:recruit:monitor:retry` | 重试失败批次 | | `cms:recruit:monitor:confirm` | 确认高风险删除批次 | | `cms:recruit:trend:list` | 查询趋势 | | `cms:recruit:analysis:list` | 查询统计 | | `cms:recruit:import:manual` | 手动触发一次外部拉取 | 需要收口或替换当前的风险入口: - 不再向外部暴露按服务器路径导入文件的接口; - 现有导入接口不能在上传成功后直接清空 `row_work`; - 来源管理和入库监测接口不得保持 `@Anonymous`; - 文件下载、手动触发、重试、批次确认均必须留操作日志。 ## 11. 实施阶段 ### 阶段一:规则与数据准备 1. 确认 6 个 Excel 来源与来源管理表的映射; 2. 确认全量快照的来源范围和缺失来源处理规则; 3. 确认学历、经验、企业性质、规模、行业别名表; 4. 确认企业同名歧义的人工处理策略; 5. 以样例文件形成正式模板和表头版本规则。 ### 阶段二:外部上传系统 1. 创建 FastAPI 项目、登录、上传、文件元数据和文件存储; 2. 实现 Excel 基础校验、SHA-256 和文件历史; 3. 实现后端内部元数据与下载接口; 4. 编写上传、权限、重复文件和非法文件测试。 ### 阶段三:后端批次导入 1. 新增数据库 migration、批次表、原始行表和来源别名表; 2. 扩展 `job` 外部标记和指纹字段; 3. 实现流式 Excel 解析、日期转换、字段规范化和行错误记录; 4. 实现企业匹配、岗位身份指纹和内容指纹; 5. 实现新增、更新、保留、恢复、逻辑删除的发布事务; 6. 实现 Quartz 拉取任务、分布式锁和保护阈值; 7. 在成功提交后刷新 ES 和缓存。 ### 阶段四:监测与菜单 1. 自动写入入库监测、来源明细和趋势统计; 2. 扩展四个后台接口与权限 SQL; 3. 对接后台前端四个页面; 4. 增加错误报告下载、手工重试和异常确认操作。 ### 阶段五:验证与上线准备 1. 用样例 2,067 行文件验证解析、去重和发布结果; 2. 验证 397 个批内重复组合不会产生重复岗位; 3. 验证身份相同、内容改变时保留 `job_id` 并更新内容; 4. 验证新指纹新增岗位; 5. 验证旧指纹在当前快照消失时仅逻辑删除外部岗位; 6. 验证历史内部岗位完全不受删除比对影响; 7. 验证异常文件、缺失来源、空文件、高删除比例会阻止发布; 8. 验证重试、断点恢复、重复文件跳过和 ES 刷新。 ## 12. 验收标准 完成后必须满足以下标准: 1. 外部人员可登录上传符合模板的 `.xlsx` 文件,并可查看文件校验状态; 2. 后端能安全获取最新文件元数据和文件流,重复文件不会重复导入; 3. 每个成功导入文件都有唯一批次记录、SHA-256、来源范围和统计结果; 4. 每条外部岗位都有 `external_import_flag = '1'`、来源编码、岗位链接、身份指纹和最近批次; 5. 本次与上次指纹相同的岗位保留同一个 `job_id`; 6. 本次新增指纹新增岗位; 7. 上一快照存在、本次快照不存在的外部岗位被逻辑删除; 8. 已删除外部岗位重新出现时恢复原 `job_id`; 9. 内部岗位、手工岗位、招聘会岗位和历史无标记岗位不会被外部快照删除; 10. 监测、趋势、统计四个菜单可查询与导入批次一致的数据; 11. 批内重复、字段错误、来源缺失和高比例删除均可追踪、可下载、可阻止发布; 12. 不执行物理删除,不执行未授权的部署或生产服务重启。 ## 13. 当前需要业务确认的事项 1. “石河子招聘网”是否可视为现有“石河子人才网”的别名,还是必须新建独立来源; 2. 六个样例来源是否为每个全量文件都必须出现的固定来源范围; 3. 对高删除比例批次,默认是完全阻止发布,还是允许管理员在后台确认后继续; 4. 企业同名且地区也无法区分时,是创建待确认企业、沿用既有企业,还是禁止该行岗位发布; 5. 当前历史 `job.data_source = '5'` 的真实来源含义,是否存在旧系统文档可供补充。