Files
offerpai_backend/.kiro/steering/数据清洗方案.md
T
2026-03-26 20:14:45 +08:00

203 lines
5.9 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.
---
inclusion: manual
---
# 数据清洗方案
## 总体架构
```
爬虫(公司网络) → app_job_data(原始数据)
Java定时任务读取(多线程)
三次AI调用:结构化 → 专业匹配 → 技能提取
写入业务表(bg_company + bg_job + 关联表 + bg_skill_tag)
公司信息不完整的 → 调AI补充
```
所有清洗逻辑放在 `manager` 模块,通过 `@Scheduled` 定时任务触发。
---
## 一、岗位清洗触发逻辑
### 1.1 状态管理
`app_job_data.clean_status`
- 0=待清洗:新爬到的数据,默认值
- 1=清洗中:定时任务已锁定,正在处理
- 2=已入库:清洗成功,已写入 bg_job
- 3=已丢弃:AI判定为无效数据
### 1.2 两个定时任务
#### 任务A:岗位清洗(每5分钟)
1. 事务内 `SELECT FOR UPDATE` + `UPDATE clean_status=1` 批量锁定
2. 丢入线程池,多线程并发处理
3. 每条独立更新状态
#### 任务B:僵尸恢复(每30分钟)
将超时10分钟仍在清洗中的数据重置为待清洗。
---
## 二、岗位清洗逻辑(三次AI调用)
### 2.1 前置校验
- `description` 为空或长度 < 20 → 标记丢弃,跳过
### 2.2 参考数据缓存(DictCacheService
启动时加载:
- `bg_job_category` 叶子节点(level=3),格式 `id:name(一级/二级)`
- `bg_industry` 叶子节点(level=2),格式 `id:name(一级)`
- `bg_major_category` 叶子节点(level=3),格式 `id:name(一级/二级)`
- `bg_china_regions_code` 省市级,供城市名匹配
### 2.3 第一次AI:提取岗位结构化信息
#### 输入
- 原始字段:job_title、salary、location、company、experience、education、description
- 参考列表:岗位分类、行业
#### 返回JSON
```json
{
"valid": true,
"title": "Java高级开发工程师",
"salary": "15-25K",
"education": 2,
"minExperience": 3,
"employmentType": 0,
"categoryId": 12,
"requiredIndustryId": 5,
"description": "岗位职责...",
"requirement": "任职要求...",
"bonus": "加分项...",
"tags": ["数据分析", "产品策略"],
"skillTags": ["Java", "Spring Boot"],
"companyShortName": "字节跳动",
"cities": ["北京", "上海"]
}
```
#### Java处理流程
1. valid=false → 丢弃
2. source_id 去重检查
3. 公司处理(查或创建 bg_company
4. 地区匹配(cities → region_code
5. 写入 bg_job + bg_job_region_relation + 更新 clean_status=2
### 2.4 第二次AI:专业匹配
岗位入库后,单独调用AI匹配专业要求。
#### 输入
- 岗位标题、职责、要求
- 三级专业分类列表(845条,格式 `id:name(一级/二级)`
#### 返回JSON
```json
{
"requiredMajorIds": [101, 203],
"majorSensitivity": 1
}
```
#### 规则
- requiredMajorIds:从专业列表中选最相关的,最多3个,无明确要求则空数组
- majorSensitivity0=不限 1=优先 2=强制
- majorSensitivity=0 时,requiredMajorIds 应为空数组
- 结果更新到 bg_job 的 required_major_ids 和 major_sensitivity 字段
### 2.5 第三次AI:技能提取
自由提取岗位核心技能,不再依赖预定义标签库。
#### 输入
- 岗位标题、职责、要求
#### 返回JSON
```json
["java", "spring boot", "mysql", "redis"]
```
#### prompt 规则
- 统一小写字母
- 尽量简短,使用业界通用缩写
- 提取范围:技术栈、专业领域知识、行业工具、专业资质能力
- 不提取纯软技能(沟通、协作、学习能力等)
- 无专业能力要求的岗位(销售、行政等)返回空数组
- 最多15个
#### 技能入库流程
1. 遍历AI返回的技能名,统一转小写
2. `INSERT IGNORE INTO bg_skill_tag`(依靠 name 唯一索引去重,ID 由 IdWorker 生成)
3. `SELECT id FROM bg_skill_tag WHERE name=?` 拿到ID
4. 写入 bg_job_skill_tag_relation
#### 并发安全
依靠数据库唯一索引保证,不加应用层锁。多线程同时插入相同技能名时,INSERT IGNORE 自动忽略重复。
### 2.6 容错设计
- 第二次、第三次AI调用失败不影响岗位入库
- 每次调用独立 try-catch,仅日志记录
---
## 三、公司数据触发逻辑
### 3.1 状态
`bg_company.status`:0=待完善 1=已完善 2=禁用 3=补充中 4=补充失败
### 3.2 定时任务
- 任务C:公司补充(每小时),SELECT FOR UPDATE 锁定 status=0 的数据
- 任务D:僵尸恢复(每小时,与C错开),重置超时10分钟的 status=3 数据
---
## 四、公司数据补充逻辑(AI补充)
### 4.1 流程
1. 拿 short_name 调AI
2. prompt 附带行业列表,AI直接返回 industryId
3. valid=false → status=4(补充失败)
4. regionCodeAI返回城市名,Java侧匹配
5. 回填 bg_company 各字段,status=1
### 4.2 AI返回JSON
```json
{
"valid": true,
"name": "北京字节跳动科技有限公司",
"city": "北京",
"companyType": "独角兽",
"industryId": 5,
"tags": ["短视频", "人工智能"],
"summary": "全球领先的内容平台和科技公司",
"description": "字节跳动成立于2012年...",
"foundedYear": "2012",
"address": "北京市海淀区...",
"scale": "10000人以上",
"website": "https://www.bytedance.com",
"financingStage": "已上市",
"latestValuation": "2200亿美元",
"news": ["新闻1", "新闻2", "新闻3"]
}
```
---
## 五、关键设计决策
| 决策点 | 结论 | 原因 |
|--------|------|------|
| 技能标签来源 | AI自由提取,自动入库 bg_skill_tag | 去掉预定义标签限制,覆盖面更广 |
| 技能并发去重 | INSERT IGNORE + 唯一索引 | 不加应用层锁,性能好 |
| 专业匹配 | 单独一次AI调用 | 专业列表845条,和第一次prompt合并会超token |
| AI调用次数 | 三次(结构化 + 专业 + 技能) | 各维度独立,容错互不影响 |
| 公司数据来源 | AI补充,不用工商API | 公司简称查API不精确,AI覆盖率高 |