Files
obsidian/study/courses/pm/report.md

469 lines
16 KiB
Markdown
Raw Permalink 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.

# 基于手机的招聘代理平台系统设计说明书
## 1 引言
### 1.1 项目背景
在互联网招聘场景中,企业普遍面临以下问题:
* 试题缺乏针对性,无法准确考察岗位所需能力;
* 试题缺乏时效性,不能反映最新项目技术栈或业务需求;
* 试题的录入与维护成本高,复用性差,题库质量难以持续优化。
为此,设计并实现一个**招聘试题代理定制服务平台**(下称“本平台”),以“岗位需求 + 项目场景 + 应聘者背景”为驱动,自动生成个性化试题并持续优化题库质量,为多家招聘企业提供 SaaS 级服务。
### 1.2 设计目标
* 提供**多角色协同**的在线招聘试题定制与考试平台;
* 支持**岗位级别的试卷自动生成**与个性化定制;
* 支持**在线答题、自动评分、结果分析**
* 通过**统计与反馈驱动的算法**持续优化题库质量;
* 面向多租户企业,以云端部署形式提供服务。
## 2 业务概述与角色
### 2.1 业务角色
1. **招聘企业 HR**
* 维护岗位信息与能力要求;
* 配置试题策略(题量、题型比例、难度分布);
* 发布考试、查看统计报表;
* 对试题效果给出评价与反馈。
2. **应聘者 Candidate**
* 注册/登录平台;
* 选择目标岗位,领取并完成在线试卷;
* 查看成绩与解析;
* 对题目给出主观评价(难度、贴合度、清晰度等)。
3. **平台管理员 Admin**
* 审核企业接入与企业题库;
* 管理系统题库、试题标签、难度标注;
* 监控题目质量与淘汰策略执行;
* 管理模型训练与推荐策略。
4. **内部系统模块(逻辑角色)**
* **Question Service**:题库管理、题目 CRUD
* **Exam Service**:考试流程管理、会话控制;
* **Scoring Service**:自动评分与统计;
* **ML / Recommendation Service**:试题生成与质量评估。
## 3 UML 视图
### 3.1 用例图Use Case Diagram
<img src="use-case.png" style="zoom: 40%;" />
### 3.2 核心业务活动图Activity Diagram
<img src="activity.png" style="zoom: 20%;" />
### 3.3 时序图Sequence Diagram
<img src="sequence.png" style="zoom: 50%;" />
### 3.4 类图Class Diagram
<img src="class.png" style="zoom: 50%;" />
### 3.5 ER 图(实体–关系图)
<img src="er.png" style="zoom: 50%;" />
## 4 数据结构与数据库设计
### 4.1 主要数据表概述
#### 表 `user`
| 字段名 | 类型 | 说明 |
| ------------- | ------------------- | ---------------------------- |
| id (PK) | INT | 用户 ID |
| company_id | INT (FK company.id) | 所属公司(应聘者可为空) |
| name | VARCHAR | 姓名 |
| email | VARCHAR (UNIQUE) | 登录邮箱 |
| role | ENUM | `HR` / `CANDIDATE` / `ADMIN` |
| password_hash | VARCHAR | 加密后的密码 |
| created_at | DATETIME | 注册时间 |
#### 表 `company`
| 字段名 | 类型 | 说明 |
| -------- | ------- | -------- |
| id (PK) | INT | 公司 ID |
| name | VARCHAR | 公司名称 |
| industry | VARCHAR | 所属行业 |
| size | VARCHAR | 规模(人数区间) |
#### 表 `candidate_profile`
| 字段名 | 类型 | 说明 |
| ---------------- | ------------ | ------------ |
| id (PK) | INT | 主键 |
| user_id (FK) | INT | 对应 `user.id` |
| degree | VARCHAR | 学历 |
| years_experience | INT | 工作年限 |
| skill_tags | JSON/VARCHAR | 技能标签集合 |
| resume_url | VARCHAR | 简历文件存储地址 |
#### 表 `job_position`
| 字段名 | 类型 | 说明 |
| --------------- | ------------ | --------- |
| id (PK) | INT | 岗位 ID |
| company_id (FK) | INT | 所属公司 |
| title | VARCHAR | 岗位名称 |
| level | VARCHAR | 岗位级别 |
| required_skills | JSON/VARCHAR | 要求技能标签 |
| description | TEXT | 岗位详细说明 |
| exam_policy_id | VARCHAR(FK) | 对应考试策略 ID |
#### 表 `exam_policy`
| 字段名 | 类型 | 说明 |
| --------------- | ------- | --------------- |
| id (PK) | VARCHAR | 策略 ID |
| total_questions | INT | 总题量 |
| easy_ratio | FLOAT | 容易题比例 |
| medium_ratio | FLOAT | 中等题比例 |
| hard_ratio | FLOAT | 困难题比例 |
| type_ratio | JSON | 题型比例(单选/多选/编程等) |
#### 表 `question`
| 字段名 | 类型 | 说明 |
| ---------- | ------- | ------------ |
| id (PK) | INT | 题目 ID |
| content | TEXT | 题干 |
| type | VARCHAR | 题目类型 |
| skill_tag | VARCHAR | 技能标签 |
| difficulty | INT | 难度等级 15 |
| source | VARCHAR | 来源(企业题/通用题等) |
| is_active | BOOLEAN | 是否在用 |
#### 表 `question_stats`
| 字段名 | 类型 | 说明 |
| --------------- | -------- | ---------------- |
| question_id(PK) | INT | 对应 `question.id` |
| used_count | INT | 被使用次数 |
| avg_score | FLOAT | 平均得分 |
| correct_rate | FLOAT | 正确率 |
| discrimination | FLOAT | 区分度 |
| feedback_score | FLOAT | 反馈评分均值 |
| last_used_at | DATETIME | 最近使用时间 |
#### 表 `exam_template`
| 字段名 | 类型 | 说明 |
| -------------- | ------- | --------- |
| id (PK) | INT | 试卷模板 ID |
| job_id (FK) | INT | 对应岗位 |
| policy_id (FK) | VARCHAR | 对应策略 |
| question_slots | JSON | 题目占位或题目列表 |
#### 表 `exam_session`
| 字段名 | 类型 | 说明 |
| ------------ | -------- | ------------------ |
| id (PK) | INT | 考试会话 ID |
| candidate_id | INT | 应聘者(`user.id` |
| job_id | INT | 岗位 ID |
| template_id | INT | 使用的模板 ID |
| start_time | DATETIME | 开始时间 |
| submit_time | DATETIME | 提交时间 |
| total_score | FLOAT | 总分 |
| status | VARCHAR | `ONGOING` / `DONE` |
#### 表 `answer`
| 字段名 | 类型 | 说明 |
| ----------- | ----- | ------- |
| id (PK) | INT | 答案 ID |
| session_id | INT | 所属考试会话 |
| question_id | INT | 对应题目 |
| content | TEXT | 作答内容 |
| score | FLOAT | 得分 |
| time_taken | FLOAT | 作答耗时(秒) |
#### 表 `question_feedback`
| 字段名 | 类型 | 说明 |
| ------------ | -------- | -------- |
| id (PK) | INT | 反馈 ID |
| question_id | INT | 对应题目 |
| from_user_id | INT | 反馈人用户 ID |
| session_id | INT | 所在考试会话 |
| rating | INT | 评分15 |
| comment | TEXT | 文本评价 |
| created_at | DATETIME | 创建时间 |
## 5 系统架构与部署设计
### 5.1 分层架构说明
1. **客户端层Clients**
* HR Web 前端Vue/React SPA
* 应聘者 Web/H5/APP
* 通过 HTTPS 调用后端 REST API。
2. **接入层Gateway**
* Nginx 作为统一入口;
* 实现 HTTPS 终止、静态资源分发、反向代理和负载均衡。
3. **应用服务层Services**
* `Auth Service`登录、鉴权、Token 管理;
* `Job & Company Service`:企业与岗位管理;
* `Question Service`:题库管理、统计指标计算;
* `Exam Service`:考试流程、会话管理;
* `Scoring Service`:客观题 & 主观题自动评分;
* `ML / Recommend Service`:试题生成、质量打分、模糊定制;
* `Admin Console`:运营后台接口。
4. **数据层Data Layer**
* 关系型数据库MySQL/PostgreSQL
* Redis 缓存热点题目、会话状态;
* 对象存储存放题目附件、解析文档;
* 日志分析存储ELK/ClickHouse 等)。
### 5.2 部署/组件图
<img src="struct.png" style="zoom: 50%;" />
## 6 核心算法设计
### 6.1 自适应试题生成算法
#### 6.1.1 目标
在满足岗位需求与考试策略(题量、题型、难度分布)的前提下,为特定岗位与候选人生成一套“技能匹配度高、区分度好、不过时”的个性化试卷。
#### 6.1.2 关键输入
* `job`:岗位信息(`required_skills``level` 等)
* `policy`:试题策略(题量、难度比例、题型比例)
* `candidate_profile`:候选人画像(技能标签、经验)
* `question_bank`:题库(含 `QuestionStats` 统计信息)
#### 6.1.3 题目综合评分函数
对题目 `q` 计算综合得分 `score(q)`
* `skill_match`:技能匹配度;
* `difficulty_fit`:难度适配度;
* `freshness`:新鲜度;
* `discrimination`:区分度;
* `bad_feedback`:负反馈惩罚。
线性组合示意:
```text
score(q) = w1 * skill_match
+ w2 * difficulty_fit
+ w3 * freshness
+ w4 * discrimination
- w5 * bad_feedback
```
#### 6.1.4 伪代码示例
```python
def generate_exam_paper(job, policy, candidate_profile, question_bank):
# 1. 抽取岗位 & 候选人技能标签
job_skills = extract_skill_tags(job.required_skills)
cand_skills = extract_skill_tags(candidate_profile.skill_tags)
# 2. 候选题集合:技能相关 & 在用
candidates = [
q for q in question_bank
if q.is_active and overlap(q.skill_tag, job_skills | cand_skills)
]
scored_questions = []
for q in candidates:
stats = q.stats # QuestionStats
skill_match = jaccard(q.skill_tag, job_skills | cand_skills)
difficulty_fit = 1.0 - abs(q.difficulty - target_difficulty(job)) / 4.0
freshness = time_decay(now() - stats.last_used_at)
discrimination = stats.discrimination
bad_feedback = max(0, 1 - stats.feedback_score)
score = (
0.30 * skill_match +
0.25 * difficulty_fit +
0.15 * freshness +
0.25 * discrimination -
0.05 * bad_feedback
)
scored_questions.append((q, score))
# 3. 按得分排序
scored_questions.sort(key=lambda x: x[1], reverse=True)
# 4. 按策略约束(题型 + 难度 + 数量)筛选
selected = []
counters = init_counters(policy)
for q, s in scored_questions:
if not satisfy_policy(q, counters, policy):
continue
selected.append(q)
update_counters(q, counters)
if len(selected) >= policy.total_questions:
break
# 5. 生成试卷模板对象
return build_exam_paper_template(job.id, policy.id, selected)
```
### 6.2 自动评分算法
拆分为客观题与主观题评分。
#### 6.2.1 客观题评分
* 单选/判断:完全匹配得满分;
* 多选:集合比对,可支持“部分正确部分得分”;
* 填空:字符串归一化后精确匹配。
```python
def score_objective_question(question, candidate_answer):
correct = question.standard_answer
if question.type in ("single_choice", "true_false"):
return question.full_score if candidate_answer == correct else 0.0
if question.type == "multi_choice":
cand_set = set(candidate_answer)
corr_set = set(correct)
if cand_set == corr_set:
return question.full_score
if cand_set.issubset(corr_set):
return question.full_score * len(cand_set) / len(corr_set)
# 选错选项则记 0 分
return 0.0
if question.type == "blank":
return question.full_score if normalize(candidate_answer) == normalize(correct) else 0.0
return 0.0
```
#### 6.2.2 主观题评分(规则 + 关键词覆盖率)
简化实现:基于关键词覆盖率进行线性赋分。
```python
def score_subjective_question(question, candidate_answer):
tokens = tokenize(candidate_answer) # 分词+规范化
key_points = question.key_points # 关键点列表
hit = sum(1 for k in key_points if k in tokens)
coverage = hit / len(key_points) if key_points else 0.0
return question.full_score * coverage
```
#### 6.2.3 会话总分计算
```python
def score_exam_session(session):
total = 0.0
for answer in session.answers:
q = load_question(answer.question_id)
if q.type in ("single_choice", "multi_choice", "true_false", "blank"):
s = score_objective_question(q, answer.content)
else:
s = score_subjective_question(q, answer.content)
answer.score = s
total += s
session.total_score = total
persist_scores(session)
return total
```
### 6.3 试题质量评估与淘汰算法
#### 6.3.1 目标
根据题目使用情况、得分表现与主观反馈,动态计算题目质量分 `quality_score`。当样本量充足且质量分低于阈值时,将题目标记为淘汰候选。
#### 6.3.2 伪代码示例
```python
def update_question_statistics(question_id):
# 1. 从数据库汇总最新统计信息
stats = aggregate_stats_from_answers_and_feedback(question_id)
# 包含used_count, avg_score, correct_rate,
# discrimination, feedback_score, last_used_at
# 2. 计算质量分
quality = (
0.25 * stats.correct_rate +
0.35 * stats.discrimination +
0.25 * stats.feedback_score +
0.15 * freshness_factor(stats.last_used_at)
)
# 3. 更新统计表
save_question_stats(question_id, stats, quality)
# 4. 判定是否进入淘汰池
MIN_USED = 30
THRESHOLD = 0.4
if stats.used_count >= MIN_USED and quality < THRESHOLD:
mark_question_as_candidate_for_deprecation(question_id)
```
### 6.4 模糊定制题目推荐算法
#### 6.4.1 场景
HR 只输入自然语言岗位描述(如“做高并发订单系统的 Java 后端工程师”),系统自动推荐若干候选题目供其选择。
#### 6.4.2 思路
1. 使用文本向量化TF-IDF 或预训练 Embedding对岗位描述和题目文本编码
2. 计算岗位向量与题目向量间的余弦相似度;
3. 返回相似度最高的 Top-K 题目。
#### 6.4.3 伪代码
```python
def fuzzy_recommend_questions(job_free_text, question_bank, top_k=50):
job_vec = encode_text(job_free_text) # 向量化岗位描述
scored = []
for q in question_bank:
q_text = q.content + " " + q.skill_tag
q_vec = encode_text(q_text)
sim = cosine_similarity(job_vec, q_vec)
scored.append((q, sim))
scored.sort(key=lambda x: x[1], reverse=True)
return [q for q, _ in scored[:top_k]]
```
## 7 总结
本报告从业务角色与流程出发,给出了招聘试题定制平台的完整系统设计方案:
* **业务层面**:明确 HR、应聘者、平台管理员等角色及其用例
* **模型层面**:通过 UML 用例图、活动图、时序图、类图与 ER 图,刻画系统的结构与行为;
* **数据层面**:设计了围绕“岗位–试卷–试题–作答–反馈”的关系型数据库模型;
* **架构层面**:采用客户端–接入层–服务层–数据层的分层部署方案,便于扩展与运维;
* **算法层面**:给出自适应试题生成、自动评分、试题质量评估与模糊定制等核心算法的伪代码。