Initial commit: obsidian to gitea

This commit is contained in:
2026-05-07 15:04:41 +08:00
commit a57afa86b4
323 changed files with 42569 additions and 0 deletions

View File

@@ -0,0 +1,418 @@
# 招聘试题定制平台 · 手机 APP 页面与 Figma Prompt 设计
> 面向「业务场景 1基于手机的招聘代理平台」这里设计两端
>
> * **应聘者 AppCandidate App**
> * **招聘企业 HR AppHR App**
> 平台管理员一般用 Web 后台,这里不再设计移动端。
---
## 1. 手机 App 页面信息架构
### 1.1 应聘者 AppCandidate App
> 典型用户路径:登录 → 浏览岗位 → 参加考试 → 查看结果 → 反馈试题。
1. **C0 欢迎 / 登录 / 注册页**
* Tab登录 / 注册
* 支持第三方登录(可选)
2. **C1 岗位列表页(职位发现)**
* 顶部搜索栏
* 筛选条件:技术方向、城市、经验要求等
* 卡片:岗位名称、公司名、标签、一个「查看详情 / 去测试」按钮
3. **C2 岗位详情 & 考试入口页**
* 展示岗位描述、能力要求、薪资范围(可选)
* 显示「本岗位在线测试信息」(题量、时长、截止时间)
* 主按钮:「开始在线测试」或「继续测试」
4. **C3 我的考试列表页**
* Tab全部 / 未开始 / 进行中 / 已完成
* 每条:岗位名、考试批次、状态、截止时间、入口按钮
5. **C4 在线考试页(核心)**
* 顶部固定栏:返回按钮 + 考试标题 + 倒计时
* 中部:当前题目
* 题号、题型、分值
* 题干 + 附件区域
* 答题控件(单选/多选/文本/代码文本框等)
* 底部:
* 题目导航滑动条(题号圆点,不同状态颜色)
* 上一题 / 下一题按钮
* 悬浮主按钮:提交试卷(弹出确认对话框)
6. **C5 考试结果页**
* 大号分数 + 通过/未通过状态
* 子卡片:总分、平均分、排名/百分位(可选)
* 能力维度雷达图 / 条形图(可选)
* 按钮:「查看详细解析」/「查看题目列表」
7. **C6 题目解析与反馈页**
* 顶部:题目 + 正确答案 + 你的答案 + 解析
* 底部:反馈卡片
* 点评:题目是否清晰 / 难度是否合适(星级/表情)
* 文本输入框:补充意见
* 提交按钮
8. **C7 个人中心 / 设置页**
* 基本信息、技能标签展示
* 考试历史入口
* 通知设置、隐私、退出登录
---
### 1.2 招聘企业 HR AppHR App
> 偏「移动监控 & 轻量管理」,重分析 / 复盘 / 小改动,而不是在手机上做复杂配置。
1. **H0 HR 登录 / 切换企业页**
* 登录、忘记密码
* (多租户时)选择企业/组织
2. **H1 HR 首页仪表盘**
* 顶部:公司名称 + 通知图标
* 核心指标卡片(纵向列表):
* 今日参加考试人数
* 本周通过率
* 活跃岗位数
* 下方:
* 最近考试列表(横向卡片轮播)
* 「有问题的题目」提醒入口
3. **H2 岗位列表页**
* 搜索 + 筛选(状态:招聘中 / 暂停 / 已关闭)
* 岗位卡片:岗位名、级别、最近考试人数、通过率
* 右下角悬浮按钮:「新建岗位」(进入简化版编辑流程)
4. **H3 岗位详情页**
* 基本信息:岗位名称、级别、城市、要求技能标签
* 近期考试概览:最近 N 次考试的平均分/通过率
* 按钮:
* 查看全部考试
* 编辑岗位(跳 H4
* 预览试卷配置(跳 H5
5. **H4 岗位快速编辑页(移动简化版)**
* 可编辑字段:岗位名称、级别、标签、是否开放招聘等
* 考试策略部分仅支持选择已有策略,不在手机上做复杂编辑
* 底部:取消 / 保存 按钮
6. **H5 试卷预览页**
* 展示系统为该岗位生成的一套样卷
* 按题型分组(单选、多选、编程等)
* 可「锁定/解锁」某些题目(锁定后 PC 端配置时保留)
7. **H6 考试结果分析页(移动版)**
* 顶部筛选条:岗位下拉 + 考试批次 + 时间区间
* 上半部分KPI 卡片(平均分、通过率、参加人数)
* 下半部分:
* 简化图表(柱状图/折线图)
* 题目正确率列表Top 问题 / Bottom 问题)
8. **H7 题目质量列表页**
* 列表项:题干摘要、技能标签、质量分、使用次数、状态
* 支持按照质量分排序/筛选
* 点击进入 H8
9. **H8 题目质量详情页**
* 展示具体题目内容、标准答案、解析
* 统计:使用次数、正确率、区分度、反馈分
* 操作按钮:标记“待修改”、标记“建议淘汰”
10. **H9 HR 个人设置页**
* 个人信息、企业信息简略查看
* 通知开关
* 退出登录
---
## 2. 手机端关键页面草图(文字线框)
### 2.1 C4 在线考试页Candidate Exam
屏幕手机竖屏iPhone 15 / 390×844
* **顶部固定栏**
* 左:返回图标(←)
* 中:考试标题(单行,超出省略号)
* 右:倒计时(红色/醒目字体例如「53:21」
* **中部内容区Scroll**
* 题目信息行:
* 「第 3 题」+ 标签「单选」「3 分」
* 主卡片:
* 题干文本(可多行)
* 若有图片/代码块,在题干下方单独容器显示
* 答题控件:
* 单选Radio 列表,每个选项为卡片式按钮
* 多选Checkbox 列表
* 填空/主观题:多行文本输入框
* **底部固定区域**
* 上半:题号导航(水平滑动)
* 一排圆形按钮1 2 3 4 5 …
* 已答:填充主色;未答:边框灰色;标记:角落有小点
* 下半:底部按钮栏
* 左按钮:「上一题」(次要)
* 中按钮:标记/取消标记
* 右按钮:「下一题」(主要)
* 右上角悬浮按钮或顶部菜单中放「交卷」按钮。
---
### 2.2 C5 结果页Candidate Result
* 顶部大号分数如「83 分」)+ 状态标签(绿色「通过」/ 红色「未通过」)
* 下方两列/纵向卡片:
* 卡片 1考试信息岗位名、日期、用时
* 卡片 2得分拆分选择题分数、编程题分数等
* 中部:简化图表/可视化(可在 Figma 中用假图表示)
* 底部按钮:
* 主按钮:「查看题目解析」
* 次按钮:「返回岗位列表」
---
### 2.3 H1 HR 首页仪表盘Mobile
* 顶部 AppBar
* 左:企业 Logo/图标 + 公司名
* 右:通知铃铛 + 头像
* 主内容(纵向 Scroll
1. 指标卡片区域(左右滑动或纵向堆叠):
* 今日参加考试人数
* 本周通过率
* 活跃岗位数
2. 模块「最近考试」:
* 卡片列表:岗位名 + 时间 + 参加人数 + 通过率
3. 模块「有问题的题目」:
* 小卡片:题干摘要 + “低正确率”“低反馈分”标签
* 点击跳转到 H7/H8
---
### 2.4 H6 HR 考试结果分析页Mobile
* 顶部筛选条(横向滚动):
* 下拉:岗位
* 下拉:考试批次
* 日期选择按钮
* 「应用」按钮
* 主内容:
* KPI 卡片区域:平均分 / 通过率 / 参加人数3 个小卡)
* 小图表区:
* 迷你折线图(七天平均分)
* 柱状图(分数段人数)
* 底部:考生列表(简化表格样式卡片):
* 姓名、得分、提交时间、详情图标
---
## 3. Figma AI / Figma Make Prompt手机 App 版本)
> 建议用英文 Prompt让 Figma 的 AI 更稳定。下面提供:
> 1一个「多屏手机 App Flow」总 prompt
> 2一个「单屏模板」方便你逐屏生成。
---
### 3.1 全局样式片段(可直接粘在每个 prompt 前)
```text
Design a modern mobile app UI for a recruitment test platform called “SmartHire Test”.
Platform:
- iOS and Android, focus on iPhone 15 size (390 x 844) in portrait.
Visual style:
- Clean, professional, light theme.
- Primary color #2563EB (blue), neutral grays, lots of white space.
- Rounded corners (1016px), soft shadows for cards.
- Use a standard mobile tab bar or bottom navigation where it makes sense.
Typography similar to SF Pro / Inter / Roboto.
```
---
### 3.2 多屏 Candidate & HR App Flow Prompt推荐
```text
You are designing a mobile app for a customizable recruitment test platform called “SmartHire Test”.
Use iPhone 15 portrait frames (390 x 844).
Apply a modern, clean, light theme with primary color #2563EB and rounded cards.
Create the following screens. Name each frame with the given screen name.
1. Screen: "C0 Candidate Welcome & Login"
- A welcome illustration or simple brand panel at the top.
- Tabs or segmented control for "Sign in" and "Sign up".
- Email and password fields, "Sign in" primary button, and "Forgot password" link.
- Optional third-party login buttons at the bottom.
2. Screen: "C1 Candidate Job List"
- Top app bar with logo "SmartHire Test" and a small profile avatar.
- Search bar at the top of the content.
- Horizontal chips for filters (e.g. Skills, City, Level).
- Vertical list of job cards: each card shows job title, company, tags, and a primary button "View details" or "Take test".
3. Screen: "C2 Candidate Job Detail"
- Job title, company, level, location at the top.
- Section for job description and required skills (tags).
- Section "Online test for this job" with chips indicating number of questions, duration, and difficulty.
- A primary CTA button at the bottom: "Start test" or "Continue test".
4. Screen: "C3 Candidate Exam List"
- Simple tab bar at the top for "All", "Active", "Completed".
- List items showing: job title, exam name, status badge, deadline, and a chevron or button to open the exam.
5. Screen: "C4 Candidate Online Exam"
- Fixed top bar with back arrow, exam title in the center, and a countdown timer on the right.
- Main scroll area:
- Row with "Question 3" label, pill badges for type (Single choice) and score (3 pts).
- Card with question text and optional image placeholder.
- Answer area: radio buttons or selection cards for options.
- Bottom fixed area:
- First row: horizontally scrollable question number chips (1, 2, 3, ...), with different states for answered / not answered / flagged.
- Second row: three buttons "Previous", "Flag", "Next" (primary on "Next").
- A secondary icon or button to "Submit exam" that opens a confirmation dialog.
6. Screen: "C5 Candidate Result"
- Large score display at the top (e.g., "83/100") and a badge indicating Pass or Fail.
- Summary cards for "Total score", "Average score", "Percentile" or "Rank".
- A simple chart area (score distribution or skill radar) using placeholder graphics.
- A section "Question review" with a list of questions and icons for correct / incorrect.
- Bottom primary button: "View detailed explanations".
7. Screen: "C6 Candidate Question Review & Feedback"
- Top shows the current question text, correct answer, and the candidate's answer.
- Below, a small explanation text block.
- At the bottom, a feedback card:
- Star rating or 15 scale for "Clarity" and "Difficulty".
- A multiline text field for additional comments.
- Primary button "Submit feedback".
8. Screen: "H0 HR Login"
- Simple login screen for HR users, consistent style with candidate login but with "HR" emphasis in the title.
9. Screen: "H1 HR Home Dashboard"
- Top app bar with company name and notification icon.
- A vertical stack of KPI cards: "Todays test takers", "This week pass rate", "Active positions".
- Section "Recent exams": small cards with job title, date, participants, pass rate.
- Section "Problematic questions": list items with a short question snippet and a warning badge.
10. Screen: "H2 HR Job List"
- List of job cards with job title, level, status badge, and small stats (last exam participants / pass rate).
- Floating action button at the bottom-right: "+" to create a new job.
11. Screen: "H3 HR Job Detail"
- Job header with title, level, and status tag.
- Sections:
- Basic info (location, department).
- Required skills (tags).
- Latest exam summary (average score, pass rate).
- Buttons at the bottom: "View exams", "Quick edit".
12. Screen: "H6 HR Exam Analytics"
- Filter bar at the top with dropdowns for job and exam batch and a date-range pill.
- KPI cards for average score, pass rate, number of candidates.
- Simple mini chart (histogram or line chart) to show score distribution.
- List of candidate results as cards (name, score, submitted time, chevron for details).
13. Screen: "H7 HR Question Quality List"
- Search bar and filter chips (by skill, difficulty, quality score).
- Vertical list of question items: each with a short question text, skill tag, usage count, quality score, and status.
- Tapping an item leads to H8.
14. Screen: "H8 HR Question Quality Detail"
- Top: full question text and answer options.
- Below: a stats card with usage count, average score, correct rate, discrimination, feedback score.
- Actions at the bottom: outlined button "Mark for review", primary button "Suggest deprecation".
Across all screens:
- Use consistent mobile navigation and components.
- Use clear hierarchy, spacing, and tappable elements sized for touch.
- Create a simple prototype flow: candidate screens connected by a logical path, and HR screens connected separately.
```
---
### 3.3 单屏 Mobile Prompt 模板(可按需复用)
```text
Design a single mobile screen for the “SmartHire Test” app.
Target device:
- iPhone 15 in portrait (390 x 844).
Visual style:
- Modern, clean, light theme.
- Primary color #2563EB, neutral gray background, rounded cards.
- Typography similar to SF Pro / Inter.
Screen name: {{SCREEN_NAME}}
Screen description:
{{DETAILED_DESCRIPTION_HERE}}
Use clear sections and card components. Make all interactive elements touch friendly (at least 44x44 points).
```
示例如果你只想生成「C4 Candidate Online Exam」这一屏就把 `{{SCREEN_NAME}}` 换掉,然后写一段 description你可以直接用我上面 C4 的说明)。
---
如果你确认这套 **手机端页面列表 + 草图 + Figma Prompt** 没问题,我可以下一步帮你把:
-「Web + App 前端设计章节」
* 和前面系统设计UML + 数据库 + 算法)
统一成一份完整的课程项目系统设计报告。

View File

@@ -0,0 +1,85 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.8 Chrome/140.0.7339.240 Electron/38.4.0 Safari/537.36" version="28.2.8">
<diagram name="Page-1" id="page-1">
<mxGraphModel dx="784" dy="671" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="6" value="配置岗位需求" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="120" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="7" value="配置试题策略" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="180" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="9" value="在线答题" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="360" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="10" value="查看成绩与统计" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="240" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="11" value="提交试题反馈" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="300" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="12" value="题库管理与题目淘汰" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="420" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="13" value="企业接入与运营配置" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="490" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="14" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="6" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="15" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="7" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="16" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="10" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="17" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="11" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="18" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-23" target="9" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="650" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="19" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-23" target="10" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="650" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="20" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-23" target="11" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="650" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="21" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-27" target="12" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="410" y="490" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="22" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-27" target="13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="410" y="490" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="dYsEw_TpROWio0KKOCtD-22" value="招聘企业 HR" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="130" y="210" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="dYsEw_TpROWio0KKOCtD-23" value="应聘者" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#f0a30a;fontColor=#000000;strokeColor=#BD7000;" vertex="1" parent="1">
<mxGeometry x="440" y="295" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="dYsEw_TpROWio0KKOCtD-27" value="平台管理员" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#f0a30a;fontColor=#000000;strokeColor=#BD7000;" vertex="1" parent="1">
<mxGeometry x="440" y="450" width="30" height="60" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,43 @@
flowchart TD
%% ======== 样式定义 ========
classDef roleHR fill:#fff7e6,stroke:#fa8c16,stroke-width:1px;
classDef roleCandidate fill:#e6fffb,stroke:#13c2c2,stroke-width:1px;
classDef roleSystem fill:#f0f5ff,stroke:#2f54eb,stroke-width:1px;
%% ======== 开始与结束 ========
Start([开始])
End([结束])
%% ======== HR 泳道 ========
subgraph HRLane[招聘企业 HR]
HR1[填写岗位需求<br/>与能力要求]:::roleHR
HR2[配置试题策略]:::roleHR
HR3[查看考试统计报表]:::roleHR
HR4[评价试题质量与效果]:::roleHR
end
%% ======== 平台后端 泳道 ========
subgraph SystemLane[平台后端]
S1[解析岗位并抽取技能标签]:::roleSystem
S2[生成试卷模板]:::roleSystem
S3[为候选人实例化试卷]:::roleSystem
S4[记录作答过程与答案]:::roleSystem
S5[自动评分并生成结果]:::roleSystem
S6[更新题目统计指标]:::roleSystem
S7[判定题目是否进入淘汰池]:::roleSystem
end
%% ======== 应聘者 泳道 ========
subgraph CandLane[应聘者 Candidate]
C1[注册 / 登录]:::roleCandidate
C2[选择岗位并领取试卷]:::roleCandidate
C3[在线作答]:::roleCandidate
C4[查看成绩与解析]:::roleCandidate
C5[填写试题反馈]:::roleCandidate
end
%% ======== 活动流转 ========
Start --> HR1 --> S1 --> S2 --> HR2 --> S3
S3 --> C1 --> C2 --> C3 --> S4 --> S5 --> C4 --> C5 --> S6
S6 --> HR3 --> HR4 --> S6
S6 --> S7 --> End

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

View File

@@ -0,0 +1,114 @@
classDiagram
class User {
+int id
+string name
+string email
+string role // HR, CANDIDATE, ADMIN
+datetime createdAt
}
class Company {
+int id
+string name
+string industry
+string size
}
class CandidateProfile {
+int id
+int userId
+string degree
+int yearsExperience
+string skillTags
+string resumeUrl
}
class JobPosition {
+int id
+int companyId
+string title
+string level
+string requiredSkills
+string description
+string examPolicyId
}
class ExamPolicy {
+string id
+int totalQuestions
+float easyRatio
+float mediumRatio
+float hardRatio
+string typeRatio // JSON: 单选/多选/编程 等比例
}
class Question {
+int id
+string content
+string type
+string skillTag
+int difficulty
+string source // 企业题/通用题
+bool isActive
}
class QuestionStats {
+int questionId
+int usedCount
+float avgScore
+float correctRate
+float discrimination
+float feedbackScore
+datetime lastUsedAt
}
class ExamTemplate {
+int id
+int jobId
+string policyId
+string questionSlots // JSON: 题目占位信息
}
class ExamSession {
+int id
+int candidateId
+int jobId
+int templateId
+datetime startTime
+datetime submitTime
+float totalScore
+string status // ONGOING / DONE
}
class Answer {
+int id
+int sessionId
+int questionId
+string content
+float score
+float timeTaken
}
class QuestionFeedback {
+int id
+int questionId
+int fromUserId
+int sessionId
+int rating // 1~5
+string comment
+datetime createdAt
}
Company "1" o-- "0..*" User : employs
User "1" o-- "0..1" CandidateProfile : has
Company "1" o-- "0..*" JobPosition : posts
JobPosition "1" o-- "0..*" ExamTemplate : uses
ExamPolicy "1" o-- "0..*" ExamTemplate : governs
Question "1" o-- "1" QuestionStats : has
ExamTemplate "1" o-- "0..*" ExamSession : instantiates
CandidateProfile "1" o-- "0..*" ExamSession : takes
ExamSession "1" o-- "0..*" Answer : contains
Question "1" o-- "0..*" Answer : answeredIn
Question "1" o-- "0..*" QuestionFeedback : receives
User "1" o-- "0..*" QuestionFeedback : writes

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

View File

@@ -0,0 +1,8 @@
#!/bin/bash
for f in *.mmd; do
base=$(basename "$f" .mmd)
echo "Converting $f → figs/${base}.png"
mmdc -i "$f" -o "${base}.png" -s 3
done

View File

@@ -0,0 +1,113 @@
erDiagram
USER {
int id
int company_id
string name
string email
string role
}
COMPANY {
int id
string name
string industry
string size
}
CANDIDATE_PROFILE {
int id
int user_id
string degree
int years_experience
string skill_tags
string resume_url
}
JOB_POSITION {
int id
int company_id
string title
string level
string required_skills
string description
string exam_policy_id
}
EXAM_POLICY {
string id
int total_questions
float easy_ratio
float medium_ratio
float hard_ratio
string type_ratio
}
QUESTION {
int id
string content
string type
string skill_tag
int difficulty
string source
bool is_active
}
QUESTION_STATS {
int question_id
int used_count
float avg_score
float correct_rate
float discrimination
float feedback_score
datetime last_used_at
}
EXAM_TEMPLATE {
int id
int job_id
string policy_id
string question_slots
}
EXAM_SESSION {
int id
int candidate_id
int job_id
int template_id
datetime start_time
datetime submit_time
float total_score
string status
}
ANSWER {
int id
int session_id
int question_id
string content
float score
float time_taken
}
QUESTION_FEEDBACK {
int id
int question_id
int from_user_id
int session_id
int rating
string comment
datetime created_at
}
COMPANY ||--o{ USER : has
USER ||--o{ CANDIDATE_PROFILE : has
COMPANY ||--o{ JOB_POSITION : posts
EXAM_POLICY ||--o{ JOB_POSITION : configures
JOB_POSITION ||--o{ EXAM_TEMPLATE : uses
EXAM_TEMPLATE ||--o{ EXAM_SESSION : instantiates
USER ||--o{ EXAM_SESSION : takes
EXAM_SESSION ||--o{ ANSWER : contains
QUESTION ||--o{ ANSWER : answered_in
QUESTION ||--|| QUESTION_STATS : stats
QUESTION ||--o{ QUESTION_FEEDBACK : receives
USER ||--o{ QUESTION_FEEDBACK : writes

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

View File

@@ -0,0 +1,31 @@
sequenceDiagram
autonumber
actor HR as 招聘企业HR
participant Web as HR Web前端
participant ExamSvc as 考试服务
participant QSvc as 试题生成服务
participant Cand as 应聘者客户端
participant Score as 评分服务
participant DB as 数据库
HR ->> Web: 配置岗位与试题策略
Web ->> QSvc: 创建/更新试卷模板
QSvc ->> DB: 读取题库与统计数据
QSvc -->> Web: 返回模板ID
HR ->> Web: 发布在线考试
Cand ->> Cand: 注册 / 登录
Cand ->> ExamSvc: 请求领取试卷(jobId, candidateId)
ExamSvc ->> QSvc: 实例化个性化试卷(templateId, profile)
QSvc -->> ExamSvc: 返回题目列表
ExamSvc -->> Cand: 下发试卷内容
Cand ->> ExamSvc: 提交全部答案
ExamSvc ->> Score: 发送作答数据
Score ->> DB: 读取标准答案与评分规则
Score -->> ExamSvc: 返回评分结果
ExamSvc ->> DB: 保存成绩与答案明细
Cand -->> ExamSvc: 查询成绩与解析
ExamSvc -->> Cand: 返回成绩、解析与评价入口

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View File

@@ -0,0 +1,91 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.8 Chrome/140.0.7339.240 Electron/38.4.0 Safari/537.36" version="28.2.8">
<diagram name="Page-1" id="KaNmNEmE_Qx5MvW_SbqB">
<mxGraphModel dx="1009" dy="863" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="40E4A47yQaY0a98WVuZL-61" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;" vertex="1" parent="1">
<mxGeometry x="280" y="140" width="680" height="110" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-57" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="280" y="250" width="680" height="110" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-56" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad9d5;strokeColor=#ae4132;" vertex="1" parent="1">
<mxGeometry x="280" y="570" width="680" height="130" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-55" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="280" y="360" width="680" height="210" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-1" value="客户端层" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="295" y="175" width="20" height="40" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-2" value="接入层&amp;nbsp;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="300" y="285" width="10" height="40" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-3" value="应用服务层&amp;nbsp;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="290" y="442" width="30" height="46" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-4" value="数据层" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="295" y="617.5" width="20" height="35" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-5" value="HR APP 前端&lt;br&gt;Vue / React" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e6fffb;strokeColor=#13c2c2;strokeWidth=2;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="410" y="165" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-6" value="应聘者 APP&lt;br&gt;Candidate App" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e6fffb;strokeColor=#13c2c2;strokeWidth=2;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="770" y="165" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-7" value="API Gateway / ALB&#xa;HTTPS 终止 + 负载均衡" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff7e6;strokeColor=#fa8c16;strokeWidth=2;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="570" y="275" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-8" value="Auth Service&#xa;认证授权" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="360" y="380" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-10" value="Question Service&#xa;题库管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="480" y="380" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-54" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="40E4A47yQaY0a98WVuZL-11" target="40E4A47yQaY0a98WVuZL-10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-11" value="Exam Service&#xa;考试流程管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="720" y="380" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-12" value="Scoring Service&#xa;自动评分" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="720" y="490" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-13" value="ML / Recommend&#xa;智能选题与评估" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="480" y="490" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-15" value="关系型数据库&lt;br&gt;MySQL / PostgreSQL" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff1f0;strokeColor=#f5222d;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="380" y="600" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-16" value="Redis 缓存" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff1f0;strokeColor=#f5222d;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="520" y="600" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-17" value="S3 对象存储&lt;br&gt;题目附件" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff1f0;strokeColor=#f5222d;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="660" y="600" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-18" value="日志 / 分析存储" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff1f0;strokeColor=#f5222d;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="800" y="600" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;" edge="1" parent="1" source="40E4A47yQaY0a98WVuZL-11" target="40E4A47yQaY0a98WVuZL-12">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;" edge="1" parent="1" source="40E4A47yQaY0a98WVuZL-10" target="40E4A47yQaY0a98WVuZL-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-14" value="Admin Console&#xa;运营后台" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="840" y="380" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-9" value="Job &amp; Company&#xa;岗位与企业管理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0f5ff;strokeColor=#2f54eb;strokeWidth=2;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="600" y="380" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="40E4A47yQaY0a98WVuZL-62" value="" style="endArrow=none;html=1;rounded=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="330" y="700" as="sourcePoint" />
<mxPoint x="330" y="140" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,60 @@
flowchart LR
%% ======== 样式定义 ========
classDef client fill:#e6fffb,stroke:#13c2c2,stroke-width:1px;
classDef gateway fill:#fff7e6,stroke:#fa8c16,stroke-width:1px;
classDef service fill:#f0f5ff,stroke:#2f54eb,stroke-width:1px;
classDef data fill:#fff1f0,stroke:#f5222d,stroke-width:1px;
%% ======== 客户端层 ========
subgraph Clients[客户端层]
HRWeb[HR Web 前端<br/>Vue / React]:::client
CandApp[应聘者 Web / APP]:::client
end
%% ======== 接入层 ========
subgraph Gateway[接入层]
Nginx[Nginx 反向代理<br/>HTTPS 终止 + 负载均衡]:::gateway
end
%% ======== 应用服务层 ========
subgraph Services[应用服务层]
AuthSvc[Auth Service<br/>认证授权]:::service
JobSvc[Job & Company Service<br/>岗位与企业管理]:::service
QSvc[Question Service<br/>题库管理]:::service
ExamSvc[Exam Service<br/>考试流程管理]:::service
ScoreSvc[Scoring Service<br/>自动评分]:::service
MLSvc[ML / Recommend Service<br/>智能选题与评估]:::service
AdminSvc[Admin Console<br/>运营后台]:::service
end
%% ======== 数据层 ========
subgraph DataLayer[数据层]
DB[(关系型数据库<br/>MySQL / PostgreSQL)]:::data
Cache[(Redis 缓存)]:::data
ObjStore[(对象存储<br/>题目附件)]:::data
LogStore[(日志 / 分析存储)]:::data
end
%% ======== 连接关系 ========
HRWeb --> Nginx
CandApp --> Nginx
Nginx --> AuthSvc
Nginx --> JobSvc
Nginx --> QSvc
Nginx --> ExamSvc
Nginx --> AdminSvc
ExamSvc --> ScoreSvc
ExamSvc --> QSvc
QSvc --> MLSvc
AuthSvc --> DB
JobSvc --> DB
QSvc --> DB
ExamSvc --> DB
ScoreSvc --> DB
Services --> Cache
Services --> ObjStore
Services --> LogStore

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -0,0 +1,85 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.8 Chrome/140.0.7339.240 Electron/38.4.0 Safari/537.36" version="28.2.8">
<diagram name="Page-1" id="page-1">
<mxGraphModel dx="706" dy="604" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="6" value="配置岗位需求" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="120" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="7" value="配置试题策略" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="180" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="9" value="在线答题" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="360" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="10" value="查看成绩与统计" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="240" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="11" value="提交试题反馈" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="300" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="12" value="题库管理与题目淘汰" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="430" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="13" value="企业接入与运营配置" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;fontColor=#232F3E;fontSize=12;strokeColor=#3399FF;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="490" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="14" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="6" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="15" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="7" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="16" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="10" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="17" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=0;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-22" target="11" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="18" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-23" target="9" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="650" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="19" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-23" target="10" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="650" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="20" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-23" target="11" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="650" y="180" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="21" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-27" target="12" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="410" y="490" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="22" style="endArrow=none;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=0.5;exitY=0.5;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="dYsEw_TpROWio0KKOCtD-27" target="13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="410" y="490" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="dYsEw_TpROWio0KKOCtD-22" value="招聘企业 HR" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="130" y="210" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="dYsEw_TpROWio0KKOCtD-23" value="应聘者" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#f0a30a;fontColor=#000000;strokeColor=#BD7000;" parent="1" vertex="1">
<mxGeometry x="440" y="295" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="dYsEw_TpROWio0KKOCtD-27" value="平台管理员" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#f0a30a;fontColor=#000000;strokeColor=#BD7000;" parent="1" vertex="1">
<mxGeometry x="440" y="450" width="30" height="60" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,47 @@
graph LR
%% ======== 样式定义 ========
classDef actor fill:#fff7e6,stroke:#fa8c16,stroke-width:1px;
classDef system fill:#f0f5ff,stroke:#2f54eb,stroke-width:1px;
classDef usecase fill:#ffffff,stroke:#595959,stroke-width:1px,rx:20,ry:20;
%% ======== 系统边界 ========
System[招聘试题定制平台]:::system
%% ======== 参与者Actors ========
HR(招聘企业 HR):::actor
Candidate(应聘者):::actor
Admin(平台管理员):::actor
%% ======== 用例Use Cases ========
UC_ConfigJob(配置岗位需求):::usecase
UC_ConfigPolicy(配置试题策略):::usecase
UC_GenerateExam(自动生成试卷):::usecase
UC_TakeExam(在线答题):::usecase
UC_ViewResult(查看成绩与统计):::usecase
UC_Feedback(提交试题反馈):::usecase
UC_ManageBank(题库管理与题目淘汰):::usecase
UC_ManageTenant(企业接入与运营配置):::usecase
%% ======== 系统与用例关系 ========
System --- UC_ConfigJob
System --- UC_ConfigPolicy
System --- UC_GenerateExam
System --- UC_TakeExam
System --- UC_ViewResult
System --- UC_Feedback
System --- UC_ManageBank
System --- UC_ManageTenant
%% ======== Actor 与 Use Case 关系 ========
HR --- UC_ConfigJob
HR --- UC_ConfigPolicy
HR --- UC_ViewResult
HR --- UC_Feedback
Candidate --- UC_TakeExam
Candidate --- UC_ViewResult
Candidate --- UC_Feedback
Admin --- UC_ManageBank
Admin --- UC_ManageTenant

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

468
study/courses/pm/report.md Normal file
View File

@@ -0,0 +1,468 @@
# 基于手机的招聘代理平台系统设计说明书
## 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 图,刻画系统的结构与行为;
* **数据层面**:设计了围绕“岗位–试卷–试题–作答–反馈”的关系型数据库模型;
* **架构层面**:采用客户端–接入层–服务层–数据层的分层部署方案,便于扩展与运维;
* **算法层面**:给出自适应试题生成、自动评分、试题质量评估与模糊定制等核心算法的伪代码。

View File

@@ -0,0 +1,17 @@
找一个业务场景业务流程10+ 业务步骤
找出每个步骤:什么角色(组织单元)、做什么事
画两种流程模型petri 网、带泳道的活动图
数据库设计:主数据、业务数据、状态数据
alpha 算法
排队论计算Little 公式
流程优化(去掉活动依赖、合并活动)
> 三类活动:主要活动、支持活动、质量控制活动(可以不画,假设质量理想)