# 基于手机的招聘代理平台系统设计说明书

## 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     | 难度等级 1–5     |
| 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      | 评分（1–5）  |
| 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 图，刻画系统的结构与行为；
* **数据层面**：设计了围绕“岗位–试卷–试题–作答–反馈”的关系型数据库模型；
* **架构层面**：采用客户端–接入层–服务层–数据层的分层部署方案，便于扩展与运维；
* **算法层面**：给出自适应试题生成、自动评分、试题质量评估与模糊定制等核心算法的伪代码。
