2026-05-12
This commit is contained in:
110
training/context/03-功能模块/01-认证模块.md
Normal file
110
training/context/03-功能模块/01-认证模块.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 认证模块(auth)
|
||||
|
||||
> 证据来源:[SRC-FEAT-01] [SRC-API-01] [SRC-CODE-01:module/auth/]
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块定位
|
||||
|
||||
- **模块目标**:负责用户身份验证(企业微信OAuth + 账号密码备选)和 JWT Token 的签发、校验,为所有其他模块提供鉴权基础。
|
||||
- **解决问题**:500人规模的企业内部系统,需要统一身份验证入口,并将身份与角色绑定,为下游数据隔离提供 `userId + role + departmentId` 上下文。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能清单
|
||||
|
||||
[SRC-API-01:四、API 接口设计 / SRC-CODE-01:module/auth/]
|
||||
|
||||
| 功能名称 | 功能描述 | 输入 | 输出 | 依赖模块 |
|
||||
|---------|---------|------|------|---------|
|
||||
| 企业微信 OAuth 登录 | 通过企业微信授权 code 换取用户身份,查找系统用户,签发 JWT | wx_code | JWT Token + 用户信息(LoginResponse) | system(查sys_user) |
|
||||
| 账号密码登录(备选) | 用用户名+密码登录,BCrypt 验证密码 | username, password | JWT Token + 用户信息 | system(查sys_user) |
|
||||
| 获取当前用户信息 | 解析 Token 返回用户基本信息 | Authorization Header | UserInfoResponse | - |
|
||||
| 微信小程序登录(预留) | 通过小程序 code 获取用户信息 | WechatLoginRequest | JWT Token | system |
|
||||
| Token 携带与拦截 | AuthInterceptor 拦截所有受保护请求,提取 Token 注入 UserContext | Authorization: Bearer {token} | UserContext(ThreadLocal) | - |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心逻辑
|
||||
|
||||
### 3.1 业务规则
|
||||
|
||||
- 企业微信登录路径:前端传 code → 后端调 weixin-java-cp SDK 换 `access_token` → 获取 `wx_userid` → 查 `sys_user.wx_userid` 匹配 → 用户不存在则返回错误(不自动注册)[SRC-FEAT-01]
|
||||
- JWT Payload 包含:userId、role、departmentId,有效期由配置决定
|
||||
- 密码存储:BCrypt 加密(Spring Security 6.x),初始管理员密码 `admin123` 已预置 [SRC-SQL-01:5.4]
|
||||
|
||||
### 3.2 校验逻辑
|
||||
|
||||
| 校验项 | 规则 | 失败响应 |
|
||||
|-------|------|---------|
|
||||
| wx_userid 是否存在于 sys_user | 必须已由管理员录入 | 返回错误:用户未录入,请联系管理员 |
|
||||
| sys_user.status | 必须为 0(启用) | 返回错误:账号已禁用 |
|
||||
| Token 有效性 | 未过期、签名正确 | 返回 401 |
|
||||
| 受保护接口 Token 缺失 | 无 Authorization Header | 返回 401 |
|
||||
|
||||
### 3.3 状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 未认证
|
||||
未认证 --> 已认证 : 登录成功(JWT签发)
|
||||
已认证 --> 未认证 : Token过期 / 主动登出
|
||||
已认证 --> 已认证 : Token续期(如有配置)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据结构
|
||||
|
||||
### 4.1 涉及数据表
|
||||
|
||||
**sys_user**(读取,不由本模块创建)[SRC-SQL-01]
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 用户主键 |
|
||||
| wx_userid | VARCHAR(100) | 企业微信用户ID(唯一索引 uk_wx_userid) |
|
||||
| username | VARCHAR(50) | 账号密码登录用账号(唯一索引 uk_username) |
|
||||
| password | VARCHAR(100) | BCrypt加密密码 |
|
||||
| role | TINYINT | 0-管理员 / 1-讲师 / 2-学员 |
|
||||
| department_id | BIGINT | 所属部门ID(会注入 JWT Payload) |
|
||||
| status | TINYINT | 0-启用 / 1-禁用 |
|
||||
|
||||
### 4.2 DTO/VO 定义
|
||||
|
||||
[SRC-CODE-01:module/auth/dto/]
|
||||
|
||||
| 类名 | 方向 | 关键字段 |
|
||||
|------|------|---------|
|
||||
| LoginRequest | 入参 | username, password |
|
||||
| WechatLoginRequest | 入参 | code(企业微信授权码) |
|
||||
| LoginResponse | 出参 | token, userId, realName, role, departmentId |
|
||||
| UserInfoResponse | 出参 | 同 LoginResponse(从 Token 解析) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 对外接口
|
||||
|
||||
[SRC-API-01]
|
||||
|
||||
| API 名称 | 方法 | 路径 | 权限 |
|
||||
|---------|------|------|------|
|
||||
| 企业微信登录 | POST | /api/auth/wechat/login | 公开 |
|
||||
| 账号密码登录 | POST | /api/auth/login | 公开 |
|
||||
| 获取当前用户 | GET | /api/auth/me | 需 Token |
|
||||
|
||||
**事件机制**:登录成功后,UserContext(ThreadLocal)持有用户信息,供同请求链路的所有 Service 使用,请求结束后清除。[SRC-CODE-01:common/context/UserContextHolder.java]
|
||||
|
||||
---
|
||||
|
||||
## 6. 异常与边界处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 企业微信 API 调用超时/失败 | 返回 503,提示"企业微信服务暂时不可用,请稍后重试" |
|
||||
| 企业微信 code 已使用(重放) | 企业微信 SDK 返回错误,转化为 400 |
|
||||
| 用户 wx_userid 存在但账号被禁用(status=1) | 返回 403,提示"账号已禁用,请联系管理员" |
|
||||
| sys_user 中无对应 wx_userid | 返回 403,提示"用户未录入系统,请联系管理员" |
|
||||
| Token 格式错误(非JWT) | AuthInterceptor 捕获,返回 401 |
|
||||
| Token 过期 | AuthInterceptor 捕获,返回 401,前端跳转登录页 |
|
||||
| 并发登录(同一账号多端) | 当前设计无限制,多端独立 Token 有效 |
|
||||
165
training/context/03-功能模块/02-系统管理模块.md
Normal file
165
training/context/03-功能模块/02-系统管理模块.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# 系统管理模块(system)
|
||||
|
||||
> 证据来源:[SRC-FEAT-01] [SRC-SQL-01] [SRC-CODE-01:module/system/]
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块定位
|
||||
|
||||
- **模块目标**:管理企业的组织架构(中心→部门→小组三级)和用户生命周期(录入、角色分配、状态管理),为所有业务模块的部门数据隔离提供基础。
|
||||
- **解决问题**:3-5名管理员管理约500人的组织结构,人员分配到正确部门和角色,是知识库/考试/培训数据隔离的前置条件。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能清单
|
||||
|
||||
[SRC-FEAT-01:模块一] [SRC-CODE-01:module/system/]
|
||||
|
||||
| 功能名称 | 功能描述 | 输入 | 输出 | 依赖模块 |
|
||||
|---------|---------|------|------|---------|
|
||||
| 中心管理(CRUD) | 创建/修改/删除最高级组织单元"中心" | CenterDTO(name, sort_order) | CenterVO | - |
|
||||
| 部门管理(CRUD) | 在中心下创建/修改/删除部门,每中心最多8个部门 | DepartmentDTO(name, center_id) | DepartmentVO | - |
|
||||
| 小组管理(CRUD) | 在部门下创建/修改/删除小组,每部门最多10个小组 | GroupDTO(name, department_id) | GroupVO | - |
|
||||
| 组织树查询 | 一次性返回完整组织树(中心→部门→小组层级) | - | OrgTreeVO(树形结构) | - |
|
||||
| 员工列表查询 | 分页查询员工,支持按部门/角色/姓名筛选 | UserQueryDTO | PageResult<UserVO> | - |
|
||||
| 员工录入 | 管理员手动录入员工信息,绑定企业微信 userid | UserDTO(wx_userid, real_name, role, department_id…) | UserVO | - |
|
||||
| 员工信息修改 | 修改员工姓名、部门、角色、小组归属 | UserDTO | UserVO | - |
|
||||
| 员工禁用/启用 | 切换员工账号状态(0启用/1禁用) | userId, status | - | auth(下次登录校验) |
|
||||
| 密码重置 | 管理员重置员工的账号密码(BCrypt) | PasswordDTO(userId, newPassword) | - | auth |
|
||||
| 讲师查看本部门员工 | 讲师可查看本部门员工列表(只读,无编辑权限) | department_id(从Token注入) | PageResult<UserVO> | auth |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心逻辑
|
||||
|
||||
### 3.1 业务规则
|
||||
|
||||
[SRC-FEAT-01:模块一]
|
||||
|
||||
- **层级约束**:中心 → 部门(一个中心最多8个部门)→ 小组(一个部门最多10个小组),超限不允许创建
|
||||
- **角色约束**:用户角色只能为 ADMIN(0) / LECTURER(1) / STUDENT(2) 三种,创建时必填
|
||||
- **部门归属**:LECTURER 和 STUDENT 必须归属某部门(department_id 不可为空),ADMIN 可无部门
|
||||
- **逻辑删除**:所有实体使用 `deleted=1` 软删除,禁止物理删除,防止外键依赖失效
|
||||
- **员工录入方式**:V1 仅支持管理员手动逐条录入,不支持批量导入(V2 规划 EasyExcel 导入)
|
||||
|
||||
### 3.2 校验逻辑
|
||||
|
||||
| 校验项 | 规则 | 失败响应 |
|
||||
|-------|------|---------|
|
||||
| 部门数量上限 | 同一 center_id 下 deleted=0 的部门数 ≤ 8 | 400:该中心部门数已达上限(8个) |
|
||||
| 小组数量上限 | 同一 department_id 下 deleted=0 的小组数 ≤ 10 | 400:该部门小组数已达上限(10个) |
|
||||
| wx_userid 唯一 | sys_user.wx_userid 全局唯一(uk_wx_userid 索引) | 400:该企业微信账号已录入 |
|
||||
| username 唯一 | sys_user.username 全局唯一(uk_username 索引) | 400:用户名已存在 |
|
||||
| 讲师数据隔离 | LECTURER 调用员工列表时,强制追加 department_id = 当前用户部门 | 自动过滤,不报错 |
|
||||
| 删除有子项的上级 | 删除中心前,其下所有部门必须已删除;删除部门前,其下所有小组和用户必须已处理 | 400:请先删除部门下的小组和员工 |
|
||||
|
||||
### 3.3 状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 启用 : 员工录入时默认 status=0
|
||||
启用 --> 禁用 : 管理员操作禁用
|
||||
禁用 --> 启用 : 管理员操作启用
|
||||
禁用 --> 已删除 : 逻辑删除(deleted=1)
|
||||
启用 --> 已删除 : 逻辑删除(deleted=1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据结构
|
||||
|
||||
[SRC-SQL-01:一、系统管理模块]
|
||||
|
||||
### 4.1 涉及数据表
|
||||
|
||||
**sys_center**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| name | VARCHAR(100) | 中心名称 |
|
||||
| sort_order | INT DEFAULT 0 | 排序 |
|
||||
| deleted | TINYINT | 逻辑删除(0/1) |
|
||||
|
||||
**sys_department**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| name | VARCHAR(100) | 部门名称 |
|
||||
| center_id | BIGINT NOT NULL | 所属中心(idx_center_id 索引) |
|
||||
| sort_order | INT | 排序 |
|
||||
| deleted | TINYINT | 逻辑删除 |
|
||||
|
||||
**sys_group**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| name | VARCHAR(100) | 小组名称 |
|
||||
| department_id | BIGINT NOT NULL | 所属部门(idx_department_id 索引) |
|
||||
| sort_order | INT | 排序 |
|
||||
| deleted | TINYINT | 逻辑删除 |
|
||||
|
||||
**sys_user**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| wx_userid | VARCHAR(100) | 企业微信 userid(唯一) |
|
||||
| username | VARCHAR(50) NOT NULL | 账号(唯一) |
|
||||
| password | VARCHAR(100) | BCrypt 加密密码 |
|
||||
| real_name | VARCHAR(50) NOT NULL | 真实姓名 |
|
||||
| phone | VARCHAR(20) | 手机号 |
|
||||
| avatar | VARCHAR(255) | 头像 URL |
|
||||
| role | TINYINT NOT NULL | 0-管理员 / 1-讲师 / 2-学员 |
|
||||
| department_id | BIGINT | 所属部门(可空,ADMIN 用) |
|
||||
| group_id | BIGINT | 所属小组(可空) |
|
||||
| status | TINYINT DEFAULT 0 | 0-启用 / 1-禁用 |
|
||||
| deleted | TINYINT | 逻辑删除 |
|
||||
|
||||
### 4.2 数据关联
|
||||
|
||||
- `sys_user.department_id` → `sys_department.id`(无外键约束,逻辑关联)
|
||||
- `sys_user.group_id` → `sys_group.id`(可空)
|
||||
- `sys_department.center_id` → `sys_center.id`
|
||||
|
||||
---
|
||||
|
||||
## 5. 对外接口
|
||||
|
||||
[SRC-CODE-01:module/system/controller/]
|
||||
|
||||
| API 名称 | 方法 | 路径 | 权限 |
|
||||
|---------|------|------|------|
|
||||
| 获取组织树 | GET | /api/system/org/tree | ADMIN |
|
||||
| 查询中心列表 | GET | /api/system/center | ADMIN |
|
||||
| 创建中心 | POST | /api/system/center | ADMIN |
|
||||
| 修改中心 | PUT | /api/system/center/{id} | ADMIN |
|
||||
| 删除中心 | DELETE | /api/system/center/{id} | ADMIN |
|
||||
| 查询部门列表 | GET | /api/system/department | ADMIN |
|
||||
| 创建部门 | POST | /api/system/department | ADMIN |
|
||||
| 修改部门 | PUT | /api/system/department/{id} | ADMIN |
|
||||
| 删除部门 | DELETE | /api/system/department/{id} | ADMIN |
|
||||
| 查询小组列表 | GET | /api/system/group | ADMIN |
|
||||
| 创建小组 | POST | /api/system/group | ADMIN |
|
||||
| 修改小组 | PUT | /api/system/group/{id} | ADMIN |
|
||||
| 删除小组 | DELETE | /api/system/group/{id} | ADMIN |
|
||||
| 查询员工列表 | GET | /api/system/user | ADMIN(全部) / LECTURER(本部门只读) |
|
||||
| 创建员工 | POST | /api/system/user | ADMIN |
|
||||
| 修改员工 | PUT | /api/system/user/{id} | ADMIN |
|
||||
| 禁用/启用员工 | PUT | /api/system/user/{id}/status | ADMIN |
|
||||
| 重置密码 | PUT | /api/system/user/{id}/password | ADMIN |
|
||||
|
||||
---
|
||||
|
||||
## 6. 异常与边界处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 删除有下级的中心/部门 | 校验子项是否全部删除,否则返回 400 + 提示 |
|
||||
| 部门/小组数量超限 | 严格校验上限(8/10),超出返回 400 |
|
||||
| 员工 wx_userid 重复 | 唯一索引捕获 DuplicateKeyException,友好提示 |
|
||||
| LECTURER 访问其他部门员工 | AuthInterceptor / Service 层强制 department_id 过滤,返回空列表 |
|
||||
| 管理员误操作禁用自己 | [ASSUMPTION] PRD 未明确说明是否限制,建议禁止禁用最后一个 ADMIN |
|
||||
| 员工转移部门 | 修改 department_id 即可,但其历史知识进度/考试记录中的 department_id 为冗余字段不随之更新 |
|
||||
205
training/context/03-功能模块/03-知识库模块.md
Normal file
205
training/context/03-功能模块/03-知识库模块.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# 知识库模块(knowledge)
|
||||
|
||||
> 证据来源:[SRC-FEAT-01] [SRC-FEAT-02] [SRC-SQL-01] [SRC-SQL-02] [SRC-API-01] [SRC-CODE-01:module/knowledge/]
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块定位
|
||||
|
||||
- **模块目标**:管理企业的结构化知识资产,支持文档和视频两种内容类型的上传、分类管理、状态发布,并记录学员的个人学习进度。
|
||||
- **解决问题**:解决救援知识依赖口口相传的问题;同时为培训计划提供可引用的学习内容,并跟踪学员是否真实完成学习(防刷机制)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能清单
|
||||
|
||||
[SRC-FEAT-01:模块二] [SRC-FEAT-02:3.2 知识学习]
|
||||
|
||||
| 功能名称 | 功能描述 | 输入 | 输出 | 依赖模块 |
|
||||
|---------|---------|------|------|---------|
|
||||
| 知识分类管理 | 创建/修改/删除多级分类树,按部门隔离 | CategoryDTO(name, parent_id, department_id) | CategoryVO | system(部门) |
|
||||
| 知识分类树查询 | 返回树形结构的分类目录 | department_id | List<CategoryVO>(树) | - |
|
||||
| 知识内容 CRUD | 创建/修改/删除知识(文档/视频),状态为草稿时可修改 | KnowledgeDTO(title, type, file_url, category_id…) | KnowledgeVO | system |
|
||||
| 文件上传 | 上传 PDF/Word/Excel/PPT/视频文件,返回文件 URL | MultipartFile | FileDTO(url, fileName, size, type) | 文件存储服务 |
|
||||
| 知识发布/下架 | 变更知识状态(草稿→发布/发布→下架/下架→再发布)| knowledgeId, action | - | - |
|
||||
| 知识列表查询(讲师端) | 管理端分页查询,可查所有状态,含草稿 | KnowledgeQueryDTO(category_id, status, keyword) | PageResult<KnowledgeVO> | - |
|
||||
| 知识列表查询(学员端) | 只查 PUBLISHED 状态,携带个人学习状态 | department_id, KnowledgeQueryDTO | PageResult<KnowledgeVO + 学习状态> | auth |
|
||||
| 知识在线预览 | 文档在线预览(PDF/Word等)、视频在线播放 | knowledgeId | 文件 URL / 预览链接 | 文件存储/文档预览服务 |
|
||||
| 开始学习 | 创建或获取学习进度记录,记录首次学习时间 | knowledgeId, [source, planId] | - | auth |
|
||||
| 更新学习进度 | 定时上报学习进度(每60秒),累加时长,判断完成 | ProgressUpdateDTO(duration, progress, videoPosition, source, planId) | - | training(若来源=TRAINING) |
|
||||
| 完成学习 | 前端显式标记完成(文档)| knowledgeId | - | - |
|
||||
| 查询学习进度 | 查询学员对某知识/某批知识的学习状态 | userId/knowledgeIds | List<KnowledgeProgressVO> | - |
|
||||
| 引用保护检查 | 检查知识是否被培训计划引用,下架前警告 | knowledgeId | Boolean + 引用计划数 | training |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心逻辑
|
||||
|
||||
### 3.1 业务规则
|
||||
|
||||
[SRC-FEAT-01:4.5 引用保护规则] [SRC-API-01:5.1 学习进度更新流程]
|
||||
|
||||
**内容状态规则**:
|
||||
- 草稿(DRAFT=0):只有讲师和管理员可见,可自由修改
|
||||
- 已发布(PUBLISHED=1):全部门学员可见,处于此状态时不可修改内容(需先下架)
|
||||
- 已下架(OFFLINE=2):学员不可见,可重新上架或继续修改后再发布
|
||||
|
||||
**学习进度完成判定规则**:
|
||||
- **视频类**:播放进度 ≥ 90% 时,status 自动变更为 COMPLETED
|
||||
- **文档类**:累计学习时长 ≥ 预估阅读时间(按文件大小估算,具体算法在 `calculateEstimatedTime()`)时,status 变更为 COMPLETED
|
||||
- 两种类型均支持多次学习,完成后不会重置;时长可继续累加
|
||||
|
||||
**防刷机制(活跃检测)**:
|
||||
- 前端 ActivityDetector 监听 mousemove/click/scroll/keypress 事件
|
||||
- 60秒无活动判定为非活跃,停止本地计时器,不上报进度
|
||||
- 恢复活跃后重新计时
|
||||
|
||||
**多标签页防重**:
|
||||
- BroadcastChannel API 实现:同一知识在多个 Tab 打开时,检测到重复后关闭后来的 Tab [SRC-API-01:6.2]
|
||||
|
||||
**部门数据隔离**:
|
||||
- 所有知识查询一律追加 `WHERE department_id = [当前用户部门]`
|
||||
- 分类也按 department_id 隔离
|
||||
|
||||
**引用保护**:
|
||||
- 知识下架前,需检查 `tr_plan_knowledge` 中是否有关联;若有,提示警告,用户确认后方可下架
|
||||
|
||||
### 3.2 校验逻辑
|
||||
|
||||
| 校验项 | 规则 | 失败响应 |
|
||||
|-------|------|---------|
|
||||
| 文件类型 | 文档:PDF/Word/Excel/PPT;视频:MP4等常见格式 | 400:不支持的文件格式 |
|
||||
| 文件大小 | [ASSUMPTION] PRD 提及限制大文件,具体上限需配置,建议文档 ≤100MB,视频 ≤2GB | 400:文件超出大小限制 |
|
||||
| 知识状态变更 | PUBLISHED 状态下不允许修改内容字段(标题/文件),只允许下架操作 | 400:已发布知识不可修改,请先下架 |
|
||||
| 学习进度上报频率 | 前端每60秒上报一次,后端不做频率限制(允许补报) | - |
|
||||
| 视频播放进度合理性 | `video_position ≤ 视频总时长`([ASSUMPTION] 后端当前未做服务端校验,PRD 明确属于风险项) | 暂无服务端拦截 |
|
||||
| 分类隶属 | 知识的 category_id 必须属于同一 department_id 的分类 | 400:分类不存在或无权限 |
|
||||
|
||||
### 3.3 状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> DRAFT : 创建知识(可新增/修改/删除)
|
||||
DRAFT --> PUBLISHED : 讲师发布
|
||||
PUBLISHED --> OFFLINE : 讲师下架(引用警告确认)
|
||||
OFFLINE --> PUBLISHED : 讲师重新上架
|
||||
OFFLINE --> DRAFT : 重新编辑后回到草稿 [ASSUMPTION]
|
||||
DRAFT --> [*] : 逻辑删除
|
||||
|
||||
note right of PUBLISHED : 学员可见\n不可修改内容
|
||||
note right of DRAFT : 仅讲师/管理员可见
|
||||
note right of OFFLINE : 学员不可见\n可编辑
|
||||
```
|
||||
|
||||
**学习进度状态流转**:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> NOT_STARTED : 知识发布,学员尚未打开
|
||||
NOT_STARTED --> IN_PROGRESS : 调用"开始学习"接口
|
||||
IN_PROGRESS --> IN_PROGRESS : 每次上报进度(时长累加)
|
||||
IN_PROGRESS --> COMPLETED : 视频≥90% 或 文档时长达标
|
||||
COMPLETED --> IN_PROGRESS : 再次学习(时长继续累加,状态不回退)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据结构
|
||||
|
||||
[SRC-SQL-01:二、知识库模块] [SRC-SQL-02]
|
||||
|
||||
### 4.1 涉及数据表
|
||||
|
||||
**km_category**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| name | VARCHAR(100) NOT NULL | 分类名称 |
|
||||
| parent_id | BIGINT DEFAULT 0 | 父分类(0=顶级) |
|
||||
| department_id | BIGINT NOT NULL | 所属部门(部门隔离键) |
|
||||
| sort_order | INT | 排序 |
|
||||
| deleted | TINYINT | 逻辑删除 |
|
||||
|
||||
**km_knowledge**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| title | VARCHAR(200) NOT NULL | 标题 |
|
||||
| description | TEXT | 描述/摘要 |
|
||||
| category_id | BIGINT | 所属分类(可空) |
|
||||
| type | TINYINT NOT NULL | 0-文档 / 1-视频 |
|
||||
| file_name | VARCHAR(255) | 文件名 |
|
||||
| file_url | VARCHAR(500) | 文件 URL |
|
||||
| file_size | BIGINT | 文件大小(字节) |
|
||||
| file_type | VARCHAR(20) | 文件后缀(pdf/mp4…) |
|
||||
| department_id | BIGINT NOT NULL | 所属部门(隔离键,有 idx) |
|
||||
| status | TINYINT DEFAULT 0 | 0-草稿 / 1-已发布 / 2-已下架 |
|
||||
| creator_id | BIGINT NOT NULL | 创建人 |
|
||||
| publish_time | DATETIME | 发布时间 |
|
||||
| view_count | INT | 浏览次数 |
|
||||
| deleted | TINYINT | 逻辑删除 |
|
||||
|
||||
**km_knowledge_progress**(学员学习进度)[SRC-SQL-02]
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| user_id | BIGINT NOT NULL | 学员ID |
|
||||
| knowledge_id | BIGINT NOT NULL | 知识ID |
|
||||
| department_id | BIGINT NOT NULL | 部门(冗余,用于分区查询) |
|
||||
| status | VARCHAR(20) | NOT_STARTED / IN_PROGRESS / COMPLETED |
|
||||
| progress | INT | 进度百分比(0~100) |
|
||||
| duration | BIGINT | 累计学习时长(秒) |
|
||||
| video_position | BIGINT | 视频播放位置(秒,仅视频) |
|
||||
| source | VARCHAR(20) | FREE / TRAINING |
|
||||
| plan_id | BIGINT | 关联培训计划(source=TRAINING 时有值) |
|
||||
| start_time | DATETIME | 首次学习时间 |
|
||||
| complete_time | DATETIME | 完成时间 |
|
||||
| 唯一约束 | uk_user_knowledge | (user_id, knowledge_id) 唯一 |
|
||||
|
||||
### 4.2 数据关联
|
||||
|
||||
- `km_knowledge.category_id` → `km_category.id`
|
||||
- `km_knowledge.department_id` = `km_category.department_id`(同部门隔离)
|
||||
- `km_knowledge_progress.user_id` → `sys_user.id`
|
||||
- `km_knowledge_progress.plan_id` → `tr_plan.id`(可空)
|
||||
|
||||
---
|
||||
|
||||
## 5. 对外接口
|
||||
|
||||
[SRC-API-01:4.3 知识学习接口] [SRC-CODE-01:module/knowledge/controller/]
|
||||
|
||||
| API 名称 | 方法 | 路径 | 权限 |
|
||||
|---------|------|------|------|
|
||||
| 查询知识分类树 | GET | /api/knowledge/category/tree | LECTURER/ADMIN |
|
||||
| 创建分类 | POST | /api/knowledge/category | LECTURER/ADMIN |
|
||||
| 修改分类 | PUT | /api/knowledge/category/{id} | LECTURER/ADMIN |
|
||||
| 删除分类 | DELETE | /api/knowledge/category/{id} | LECTURER/ADMIN |
|
||||
| 查询知识列表(管理端) | GET | /api/knowledge | LECTURER/ADMIN |
|
||||
| 创建知识 | POST | /api/knowledge | LECTURER/ADMIN |
|
||||
| 修改知识 | PUT | /api/knowledge/{id} | LECTURER/ADMIN |
|
||||
| 发布/下架知识 | PUT | /api/knowledge/{id}/status | LECTURER/ADMIN |
|
||||
| 删除知识 | DELETE | /api/knowledge/{id} | LECTURER/ADMIN |
|
||||
| 文件上传 | POST | /api/knowledge/upload | LECTURER/ADMIN |
|
||||
| 学员-知识列表 | GET | /api/student/knowledge | STUDENT |
|
||||
| 学员-知识详情 | GET | /api/student/knowledge/{id} | STUDENT |
|
||||
| 学员-开始学习 | POST | /api/student/knowledge/{id}/start | STUDENT |
|
||||
| 学员-更新进度 | POST | /api/student/knowledge/{id}/progress | STUDENT |
|
||||
| 学员-标记完成 | POST | /api/student/knowledge/{id}/complete | STUDENT |
|
||||
|
||||
---
|
||||
|
||||
## 6. 异常与边界处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 已发布知识被下架,同时培训计划正引用 | 弹出确认警告,显示引用该知识的培训计划数量,用户确认后方可下架 |
|
||||
| 文件上传失败(存储服务不可用) | 返回 503,前端提示"文件上传失败,请稍后重试" |
|
||||
| 学员学习已下架的知识(在培训计划中) | [ASSUMPTION] PRD 未明确,建议:关联进培训计划的知识下架后,学员仍可查看历史进度但不可重新打开 |
|
||||
| km_knowledge_progress 并发 UPSERT(同一学员多标签页) | 唯一约束 uk_user_knowledge 防止重复插入;多标签页同步由前端 BroadcastChannel 控制关闭重复 Tab |
|
||||
| 视频播放中途退出 | 下次进入从 video_position 断点续播 |
|
||||
| 学习进度上报时网络中断 | 前端重试,后端幂等(UPSERT 语义,重复上报取最大值或继续累加) |
|
||||
| 分类下有知识时删除分类 | 检查该分类下是否有 deleted=0 的知识,若有则拒绝:400 请先移除分类下的知识 |
|
||||
253
training/context/03-功能模块/04-考试模块.md
Normal file
253
training/context/03-功能模块/04-考试模块.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# 考试模块(exam)
|
||||
|
||||
> 证据来源:[SRC-FEAT-01] [SRC-FEAT-02] [SRC-SQL-01] [SRC-API-01] [SRC-CODE-01:module/exam/]
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块定位
|
||||
|
||||
- **模块目标**:管理从出题到考试全链路:题目→试卷→考试→作答记录,支持在线作答、自动判分、成绩排名。
|
||||
- **解决问题**:替代纸质考试,提升出卷效率(自动组卷),实现客观题自动判分,并对考试行为(时间窗口、次数限制、超时交卷)进行严格管控。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能清单
|
||||
|
||||
[SRC-FEAT-01:模块三/四/五] [SRC-API-01:4.4 在线考试接口]
|
||||
|
||||
| 功能名称 | 功能描述 | 输入 | 输出 | 依赖模块 |
|
||||
|---------|---------|------|------|---------|
|
||||
| 题目分类管理 | 按分类管理题目,多级目录,按部门隔离 | QuestionCategoryDTO | QuestionCategoryVO | system |
|
||||
| 题目 CRUD | 创建/修改/删除题目,支持单选/多选/判断三种题型,必须填写解析 | QuestionDTO(type, content, options, answer, analysis) | QuestionVO | - |
|
||||
| 题目状态管理 | 草稿→发布→下架,下架前检查引用 | questionId, action | - | - |
|
||||
| 手动组卷 | 讲师从题库手动选题,配置每题分值 | PaperDTO(title, questions[]) | PaperVO | - |
|
||||
| 自动组卷 | 按规则(单选X题+多选Y题+判断Z题)随机抽题 | AutoPaperDTO(规则配置) | PaperVO | - |
|
||||
| 试卷配置 | 设置总分、各题分值、考试时长、及格分 | PaperDTO | PaperVO | - |
|
||||
| 试卷预览 | 组卷后预览试卷效果(随机题目顺序) | paperId | 完整试卷预览 | - |
|
||||
| 试卷状态管理 | 草稿→发布→下架 | paperId, action | - | - |
|
||||
| 创建考试 | 选择试卷,设置考试名称、时间窗口、最大次数 | ExamQueryDTO + ExamTarget | ExamVO | - |
|
||||
| 指定考试对象 | 指定部门/小组/个人参加考试 | exam_id, target_type, target_id[] | - | system |
|
||||
| 发布考试 | 将考试状态从 NOT_STARTED 转为 IN_PROGRESS(时间到自动) | examId | - | - |
|
||||
| 学员-我的考试列表 | 查询分配给当前学员的考试(在时间窗口内) | - | PageResult<ExamVO + 我的记录> | auth |
|
||||
| 学员-开始考试 | 校验资格,创建 exam_record,返回试题 | examId | 试题 + 时长 + recordId | - |
|
||||
| 学员-断点续考 | 恢复进行中的考试记录,返回已保存答案 | examId | 试题 + 已保存答案 + 剩余时间 | - |
|
||||
| 学员-保存答案 | 每30秒自动保存答案(增量保存) | recordId, answers(JSON) | - | - |
|
||||
| 学员-提交试卷 | 自动判分,保存成绩,返回排名+解析 | recordId, answers(JSON) | 成绩 + 排名 + 答案解析 | - |
|
||||
| 超时自动交卷 | 定时任务每分钟扫描超时记录,系统代提交 | @Scheduled | - | - |
|
||||
| 查看考试结果 | 查看某次考试的详细成绩和答案解析 | recordId | ExamResultVO | - |
|
||||
| 成绩/排名查询 | 讲师查看某考试的所有学员成绩 | examId | PageResult<成绩列表> | - |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心逻辑
|
||||
|
||||
### 3.1 业务规则
|
||||
|
||||
[SRC-FEAT-01:4.3 考试规则] [SRC-API-01:5.2/5.3]
|
||||
|
||||
**题型规则**:
|
||||
- 单选题(SINGLE=0):options 为 JSON 数组,answer 为单字母(A/B/C/D)
|
||||
- 多选题(MULTIPLE=1):answer 为多字母组合(如"AB"/"BCD"),按字母排序存储
|
||||
- 判断题(JUDGE=2):options 为空,answer 为"T"(正确)或"F"(错误)
|
||||
- 每道题必须填写 `analysis`(答案解析),否则不允许发布
|
||||
|
||||
**考试时间窗口**:
|
||||
- 只有在 `exam.start_time ≤ NOW() ≤ exam.end_time` 时,学员才能进入考试
|
||||
- 考试到期后,`exam.status` 自动(或手动)变为 ENDED
|
||||
|
||||
**次数限制**:
|
||||
- 达到 `max_attempts` 后,学员不可再次开始考试
|
||||
- 历史成绩取**最高分**作为最终成绩
|
||||
- 每次考试创建 `exam_record.attempt_no = 历史次数 + 1`
|
||||
|
||||
**自动判分规则**:
|
||||
- 仅客观题(单选/多选/判断)自动判分
|
||||
- 单选/判断:答案完全匹配得满分
|
||||
- 多选:全部选项匹配(顺序无关)得满分;部分正确得 0 分(V1 不分项给分)
|
||||
- 总分 = 各题得分之和
|
||||
|
||||
**超时自动交卷**:
|
||||
- `@Scheduled(cron="0 */1 * * * ?")` 每分钟扫描
|
||||
- 查询:`ex_exam_record.status=IN_PROGRESS` 且 `start_time + paper.duration 分钟 < NOW()`
|
||||
- 使用乐观锁(`version` 字段)防止与用户主动交卷并发冲突 [SRC-API-01:5.2]
|
||||
|
||||
**排名计算**:
|
||||
- 计算时机:用户交卷后立即计算本次提交对应的排名
|
||||
- 统计范围:同一 `exam_id` 下,所有 `status=SUBMITTED` 记录,取每用户最高分
|
||||
- 排名规则:`DENSE_RANK() OVER (ORDER BY MAX(score) DESC, MIN(submit_time) ASC)`(同分按提交时间早排前)[SRC-API-01:5.3]
|
||||
|
||||
**题目顺序**:
|
||||
- 自动组卷时,返回给学员的题目顺序随机打乱(前端处理或后端随机返回)
|
||||
|
||||
### 3.2 校验逻辑
|
||||
|
||||
| 校验项 | 规则 | 失败响应 |
|
||||
|-------|------|---------|
|
||||
| 题目 analysis 必填 | 发布时 analysis 不可为空 | 400:请填写答案解析后再发布 |
|
||||
| 开始考试-时间窗口 | NOW() 在 [start_time, end_time] 范围内 | 400:考试尚未开始 / 考试已结束 |
|
||||
| 开始考试-次数限制 | 已有考试记录数 < max_attempts | 400:已达到最大考试次数 |
|
||||
| 开始考试-状态 | exam.status = IN_PROGRESS(1) | 400(状态不符) |
|
||||
| 开始考试-进行中记录 | 若已有 IN_PROGRESS 记录,返回续考而非新建 | 自动转入断点续考流程 |
|
||||
| 答案格式 | 多选题答案字母需排序后匹配 | 自动处理,不返回错误 |
|
||||
| 超时交卷并发 | 乐观锁冲突时(用户已提交),忽略定时任务提交 | 静默忽略 OptimisticLockException |
|
||||
| 试卷引用保护 | 已被考试引用的试卷下架前需确认警告 | 400 + 警告 + 引用考试数量 |
|
||||
| 题目引用保护 | 已被试卷引用的题目下架前需确认警告 | 400 + 警告 + 引用试卷数量 |
|
||||
|
||||
### 3.3 状态流转
|
||||
|
||||
**内容状态(题目/试卷)**:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> DRAFT : 创建
|
||||
DRAFT --> PUBLISHED : 发布(题目需有 analysis)
|
||||
PUBLISHED --> OFFLINE : 下架(引用保护确认)
|
||||
OFFLINE --> PUBLISHED : 重新上架
|
||||
DRAFT --> [*] : 逻辑删除
|
||||
```
|
||||
|
||||
**考试状态**:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> NOT_STARTED : 创建考试(start_time 未到)
|
||||
NOT_STARTED --> IN_PROGRESS : 到达 start_time(自动或手动)
|
||||
IN_PROGRESS --> ENDED : 到达 end_time(自动或手动结束)
|
||||
ENDED --> [*] : 逻辑删除
|
||||
```
|
||||
|
||||
**考试记录状态**:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IN_PROGRESS : 学员点击"开始考试"创建记录
|
||||
IN_PROGRESS --> SUBMITTED : 学员交卷(submit_source=USER)
|
||||
IN_PROGRESS --> SUBMITTED : 定时任务超时(submit_source=SYSTEM_TIMEOUT)
|
||||
SUBMITTED --> [*] : 永久保留
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据结构
|
||||
|
||||
[SRC-SQL-01:三、考试模块]
|
||||
|
||||
### 4.1 涉及数据表(7张)
|
||||
|
||||
**ex_question_category**:题目分类(同知识库分类结构,按 department_id 隔离)
|
||||
|
||||
**ex_question**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| type | TINYINT | 0-单选 / 1-多选 / 2-判断 |
|
||||
| content | TEXT NOT NULL | 题干 |
|
||||
| options | JSON | 选项数组(判断题为 NULL) |
|
||||
| answer | VARCHAR(50) NOT NULL | 正确答案(单选/判断单字母,多选合并字母) |
|
||||
| analysis | TEXT | 答案解析(发布前必填) |
|
||||
| category_id | BIGINT | 所属分类 |
|
||||
| department_id | BIGINT NOT NULL | 部门隔离键 |
|
||||
| status | TINYINT | 0-草稿 / 1-发布 / 2-下架 |
|
||||
| creator_id | BIGINT NOT NULL | 创建人 |
|
||||
|
||||
**ex_paper**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| title | VARCHAR(200) NOT NULL | 试卷标题 |
|
||||
| total_score | INT DEFAULT 100 | 总分 |
|
||||
| duration | INT DEFAULT 60 | 考试时长(分钟) |
|
||||
| pass_score | INT DEFAULT 60 | 及格分 |
|
||||
| department_id | BIGINT NOT NULL | 部门隔离键 |
|
||||
| status | TINYINT | 0-草稿 / 1-发布 / 2-下架 |
|
||||
|
||||
**ex_paper_question**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| paper_id | BIGINT NOT NULL | 试卷ID |
|
||||
| question_id | BIGINT NOT NULL | 题目ID |
|
||||
| score | INT DEFAULT 5 | 该题分值 |
|
||||
| sort_order | INT | 排序(大题号顺序) |
|
||||
|
||||
**ex_exam**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| title | VARCHAR(200) NOT NULL | 考试标题 |
|
||||
| paper_id | BIGINT NOT NULL | 关联试卷 |
|
||||
| start_time / end_time | DATETIME | 时间窗口 |
|
||||
| max_attempts | INT DEFAULT 1 | 最大考试次数 |
|
||||
| department_id | BIGINT NOT NULL | 部门隔离键 |
|
||||
| status | TINYINT | 0-未开始 / 1-进行中 / 2-已结束 |
|
||||
|
||||
**ex_exam_target**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| exam_id | BIGINT NOT NULL | 考试ID |
|
||||
| target_type | TINYINT | 0-部门 / 1-小组 / 2-个人 |
|
||||
| target_id | BIGINT NOT NULL | 对应 dept/group/user 的ID |
|
||||
|
||||
**ex_exam_record**(含 V1.0.1 扩展字段)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| exam_id | BIGINT NOT NULL | 考试ID |
|
||||
| user_id | BIGINT NOT NULL | 学员ID |
|
||||
| attempt_no | INT NOT NULL | 第几次考试 |
|
||||
| score | INT | 得分 |
|
||||
| passed | TINYINT | 0-未通过 / 1-通过 |
|
||||
| start_time | DATETIME NOT NULL | 开始时间 |
|
||||
| submit_time | DATETIME | 提交时间 |
|
||||
| answers | JSON | 答题详情 |
|
||||
| status | VARCHAR(20) | IN_PROGRESS / SUBMITTED |
|
||||
| last_save_time | DATETIME | 最后保存时间 |
|
||||
| version | INT DEFAULT 0 | 乐观锁版本号 |
|
||||
| submit_source | VARCHAR(20) | USER / SYSTEM_TIMEOUT |
|
||||
|
||||
### 4.2 数据关联
|
||||
|
||||
- `ex_paper_question.paper_id` → `ex_paper.id`
|
||||
- `ex_paper_question.question_id` → `ex_question.id`
|
||||
- `ex_exam.paper_id` → `ex_paper.id`
|
||||
- `ex_exam_target` 多态关联:target_type=0 → `sys_department.id`;=1 → `sys_group.id`;=2 → `sys_user.id`
|
||||
- `ex_exam_record.exam_id` → `ex_exam.id`;`user_id` → `sys_user.id`
|
||||
|
||||
---
|
||||
|
||||
## 5. 对外接口
|
||||
|
||||
[SRC-API-01:4.4]
|
||||
|
||||
| API 名称 | 方法 | 路径 | 权限 |
|
||||
|---------|------|------|------|
|
||||
| 题目分类 CRUD | GET/POST/PUT/DELETE | /api/exam/question-category/** | LECTURER/ADMIN |
|
||||
| 题目 CRUD | GET/POST/PUT/DELETE | /api/exam/question/** | LECTURER/ADMIN |
|
||||
| 试卷 CRUD + 自动组卷 | GET/POST/PUT/DELETE | /api/exam/paper/** + /api/exam/paper/auto | LECTURER/ADMIN |
|
||||
| 考试 CRUD | GET/POST/PUT/DELETE | /api/exam/** | LECTURER/ADMIN |
|
||||
| 我的考试列表 | GET | /api/student/exam | STUDENT |
|
||||
| 考试详情 | GET | /api/student/exam/{id} | STUDENT |
|
||||
| 开始考试 | POST | /api/student/exam/{id}/start | STUDENT |
|
||||
| 断点续考 | GET | /api/student/exam/{id}/continue | STUDENT |
|
||||
| 保存答案 | POST | /api/student/exam/{id}/save | STUDENT |
|
||||
| 提交试卷 | POST | /api/student/exam/{id}/submit | STUDENT |
|
||||
| 查看考试结果 | GET | /api/student/exam/record/{recordId} | STUDENT |
|
||||
|
||||
---
|
||||
|
||||
## 6. 异常与边界处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 学员在时间窗口外访问考试 | 400 + 明确提示是"未开始"还是"已结束" |
|
||||
| 次数用完后再次开始 | 400:已达最大考试次数,如有异议请联系讲师 |
|
||||
| 交卷时 exam_record 已是 SUBMITTED(并发/重复提交) | 乐观锁冲突或状态判断,幂等返回已有成绩,不重复判分 |
|
||||
| 定时任务超时提交时用户同时提交 | 乐观锁 version 冲突,定时任务忽略,用户提交成功 |
|
||||
| 试卷总分与题目分值之和不一致 | [ASSUMPTION] PRD 未明确,建议前端组卷时实时计算并提示差额 |
|
||||
| 自动组卷时题库数量不足 | 400:题库中相应题型数量不足,请手动添加题目或降低要求 |
|
||||
| 学员中途关闭浏览器 | answers 按最后一次 save 记录恢复,重新进入走断点续考流程 |
|
||||
196
training/context/03-功能模块/05-培训计划模块.md
Normal file
196
training/context/03-功能模块/05-培训计划模块.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# 培训计划模块(training)
|
||||
|
||||
> 证据来源:[SRC-FEAT-01] [SRC-FEAT-02] [SRC-SQL-01] [SRC-API-01] [SRC-CODE-01:module/training/]
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块定位
|
||||
|
||||
- **模块目标**:将知识内容和考试任务组合成有时间范围的培训计划,分配给指定学员,并跟踪每位学员的完成进度。
|
||||
- **解决问题**:组织端无法跟踪员工是否真正完成学习目标;培训计划模块实现"分配→执行→进度可视"的闭环管理。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能清单
|
||||
|
||||
[SRC-FEAT-01:模块六] [SRC-FEAT-02:3.4 我的培训] [SRC-API-01:4.5]
|
||||
|
||||
| 功能名称 | 功能描述 | 输入 | 输出 | 依赖模块 |
|
||||
|---------|---------|------|------|---------|
|
||||
| 创建培训计划 | 设置计划基本信息(名称/描述/时间范围/所属部门) | TrainingPlanQueryDTO(title, start_date, end_date) | TrainingPlanVO | system |
|
||||
| 关联知识内容 | 在培训计划中添加多个知识,设置必修/选修和排序 | plan_id, [{knowledge_id, required, sort_order}] | - | knowledge |
|
||||
| 关联考试任务 | 在培训计划中添加多个考试,设置必考/选考和排序(V1.0.1+)| plan_id, [{exam_id, required, sort_order}] | - | exam |
|
||||
| 分配培训对象 | 指定培训计划的参与者(部门/小组/个人) | plan_id, [{target_type, target_id}] | - | system |
|
||||
| 培训计划列表(讲师端) | 查询本部门的培训计划,含状态和统计信息 | 分页参数 | PageResult<TrainingPlanVO> | - |
|
||||
| 培训计划详情(讲师端) | 查看计划详情,含关联知识/考试/对象/进度汇总 | planId | TrainingPlanDetailVO | - |
|
||||
| 更新/删除培训计划 | 修改计划信息(未开始时可修改,进行中时受限)| planId, TrainingPlanQueryDTO | - | - |
|
||||
| 学员-我的培训列表 | 查询分配给当前学员的培训计划(进行中优先) | - | PageResult<TrainingPlanVO + 进度> | auth |
|
||||
| 学员-培训详情 | 查看某培训计划详情:课程列表+学习状态+考试列表+通过状态 | planId | TrainingDetailVO | knowledge, exam |
|
||||
| 进度计算 | 按必修知识完成数 + 必考通过数 / 总必修项 计算进度% | planId, userId | Integer(0~100) | knowledge, exam |
|
||||
| 知识进度同步 | 学员学习某知识时,若 source=TRAINING 则同步更新 tr_plan_progress | planId, userId, knowledgeId | - | knowledge |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心逻辑
|
||||
|
||||
### 3.1 业务规则
|
||||
|
||||
[SRC-FEAT-01:模块六] [SRC-API-01:5.4]
|
||||
|
||||
**培训计划状态自动流转**:
|
||||
- `tr_plan.status` 根据 `start_date` / `end_date` 与当前日期比较自动判定:
|
||||
- `NOW() < start_date` → NOT_STARTED
|
||||
- `start_date ≤ NOW() ≤ end_date` → IN_PROGRESS
|
||||
- `NOW() > end_date` → ENDED
|
||||
- [ASSUMPTION] 状态字段在查询时实时计算,或由定时任务定期刷新(PRD 未明确)
|
||||
|
||||
**培训进度计算公式**(V1.0.1)[SRC-API-01:5.4]:
|
||||
```
|
||||
progress% = (必修知识完成数 + 必考考试通过数) / (必修知识总数 + 必考考试总数) × 100
|
||||
```
|
||||
- 必修知识完成:`km_knowledge_progress.status = COMPLETED` 且 `plan_id = 当前计划`
|
||||
- 必考通过:在 `tr_plan_exam` 中 `required=1` 的考试,学员在 `ex_exam_record` 中存在 `passed=1` 的记录
|
||||
- 选修知识/选考考试不计入进度(但在详情页展示)
|
||||
- 总必修项为 0 时,进度 = 100%(无内容的计划视为自动完成)
|
||||
|
||||
**知识完成与培训进度联动**:
|
||||
- 学员调用 `knowledge.updateProgress()` 时,若 `source=TRAINING` 且 `planId != null`:
|
||||
1. 更新 `km_knowledge_progress`
|
||||
2. 若 status 变为 COMPLETED,则更新 `tr_plan_progress`(plan_id, user_id, knowledge_id 唯一记录)
|
||||
|
||||
**考试通过与培训进度联动**:
|
||||
- 学员考试提交后,`TrainingProgressService` 查询该 exam_id 被哪些计划引用(`tr_plan_exam`),若有关联且 `passed=1`,则重新计算相关计划的进度(或标记为需要刷新)
|
||||
|
||||
**多对象分配逻辑**:
|
||||
- 分配对象类型支持 DEPARTMENT(0) / GROUP(1) / USER(2) 混合
|
||||
- 当指定 DEPARTMENT 时,该部门下所有 STUDENT 账号都被纳入
|
||||
- 当指定 GROUP 时,该小组所有成员纳入
|
||||
- 已被纳入的学员在查询时去重(多路径覆盖同一人不重复统计)
|
||||
|
||||
### 3.2 校验逻辑
|
||||
|
||||
| 校验项 | 规则 | 失败响应 |
|
||||
|-------|------|---------|
|
||||
| 培训时间范围 | start_date < end_date | 400:培训结束日期必须晚于开始日期 |
|
||||
| 关联知识状态 | 关联到计划的知识必须是 PUBLISHED 状态 | 400:请先发布知识再关联到培训计划 |
|
||||
| 关联考试状态 | 关联到计划的考试必须是 IN_PROGRESS 或 NOT_STARTED | 400:已结束的考试不可关联 |
|
||||
| 进行中计划修改 | [ASSUMPTION] IN_PROGRESS 状态下,不允许修改培训对象和时间范围 | 400:培训进行中,不可修改 |
|
||||
| 知识引用保护 | 已关联计划的知识被下架时触发警告 | knowledge 模块负责,见知识库模块 6 |
|
||||
| 学员查看非自己的计划 | Service 层校验该学员是否在 tr_plan_target 覆盖范围内 | 403:无权访问该培训计划 |
|
||||
|
||||
### 3.3 状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> NOT_STARTED : 创建培训计划(start_date 未到)
|
||||
NOT_STARTED --> IN_PROGRESS : start_date 到达(自动/定时)
|
||||
IN_PROGRESS --> ENDED : end_date 到达(自动/定时)
|
||||
NOT_STARTED --> [*] : 逻辑删除(未开始可删除)
|
||||
ENDED --> [*] : 归档
|
||||
|
||||
note right of IN_PROGRESS : 学员可学习和考试
|
||||
note right of ENDED : 仅供历史查阅
|
||||
```
|
||||
|
||||
**学员培训进度状态(非独立表字段,实时计算)**:
|
||||
- 0%:尚未开始学习
|
||||
- 1%~99%:学习中
|
||||
- 100%:全部必修项完成(培训通过)
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据结构
|
||||
|
||||
[SRC-SQL-01:四、培训模块]
|
||||
|
||||
### 4.1 涉及数据表
|
||||
|
||||
**tr_plan**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| title | VARCHAR(200) NOT NULL | 计划标题 |
|
||||
| description | TEXT | 计划描述 |
|
||||
| start_date | DATE NOT NULL | 开始日期 |
|
||||
| end_date | DATE NOT NULL | 结束日期 |
|
||||
| department_id | BIGINT NOT NULL | 所属部门(数据隔离键) |
|
||||
| status | TINYINT | 0-未开始 / 1-进行中 / 2-已结束 |
|
||||
| creator_id | BIGINT NOT NULL | 创建人 |
|
||||
|
||||
**tr_plan_knowledge**(培训计划-知识关联)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| plan_id | BIGINT NOT NULL | 培训计划 ID |
|
||||
| knowledge_id | BIGINT NOT NULL | 知识 ID |
|
||||
| required | TINYINT DEFAULT 1 | 1-必修 / 0-选修 |
|
||||
| sort_order | INT | 排序 |
|
||||
|
||||
**tr_plan_exam**(培训计划-考试关联,V1.0.1)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| plan_id | BIGINT NOT NULL | 培训计划 ID |
|
||||
| exam_id | BIGINT NOT NULL | 考试 ID |
|
||||
| required | TINYINT DEFAULT 1 | 1-必考 / 0-选考 |
|
||||
| sort_order | INT | 排序 |
|
||||
| 唯一约束 | uk_plan_exam | (plan_id, exam_id) |
|
||||
|
||||
**tr_plan_target**(培训计划-分配对象)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| plan_id | BIGINT NOT NULL | 培训计划 ID |
|
||||
| target_type | TINYINT NOT NULL | 0-部门 / 1-小组 / 2-个人 |
|
||||
| target_id | BIGINT NOT NULL | 对应实体 ID |
|
||||
|
||||
**tr_plan_progress**(知识级别进度,供快速查询)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| plan_id | BIGINT NOT NULL | 培训计划 ID |
|
||||
| user_id | BIGINT NOT NULL | 学员 ID |
|
||||
| knowledge_id | BIGINT NOT NULL | 知识 ID |
|
||||
| completed | TINYINT DEFAULT 0 | 0-未完成 / 1-已完成 |
|
||||
| complete_time | DATETIME | 完成时间 |
|
||||
|
||||
### 4.2 数据关联
|
||||
|
||||
- `tr_plan_knowledge.plan_id` → `tr_plan.id`
|
||||
- `tr_plan_knowledge.knowledge_id` → `km_knowledge.id`
|
||||
- `tr_plan_exam.plan_id` → `tr_plan.id`
|
||||
- `tr_plan_exam.exam_id` → `ex_exam.id`
|
||||
- `tr_plan_target` 多态关联:type=0→`sys_department.id`;type=1→`sys_group.id`;type=2→`sys_user.id`
|
||||
- 进度计算同时读取 `km_knowledge_progress`(source=TRAINING, plan_id)和 `ex_exam_record`(passed=1)
|
||||
|
||||
---
|
||||
|
||||
## 5. 对外接口
|
||||
|
||||
[SRC-API-01:4.5] [SRC-CODE-01:module/training/controller/]
|
||||
|
||||
| API 名称 | 方法 | 路径 | 权限 |
|
||||
|---------|------|------|------|
|
||||
| 培训计划列表(讲师) | GET | /api/training | LECTURER/ADMIN |
|
||||
| 创建培训计划 | POST | /api/training | LECTURER/ADMIN |
|
||||
| 修改培训计划 | PUT | /api/training/{id} | LECTURER/ADMIN |
|
||||
| 删除培训计划 | DELETE | /api/training/{id} | LECTURER/ADMIN |
|
||||
| 关联知识 | POST | /api/training/{id}/knowledge | LECTURER/ADMIN |
|
||||
| 关联考试 | POST | /api/training/{id}/exam | LECTURER/ADMIN |
|
||||
| 分配对象 | POST | /api/training/{id}/target | LECTURER/ADMIN |
|
||||
| 学员-培训列表 | GET | /api/student/training | STUDENT |
|
||||
| 学员-培训详情 | GET | /api/student/training/{id} | STUDENT |
|
||||
|
||||
---
|
||||
|
||||
## 6. 异常与边界处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 培训计划结束后学员查看 | 状态=ENDED,页面展示历史进度和成绩,不可继续学习/考试 |
|
||||
| 学员尚未被分配到某计划就访问 | 403:无权访问该培训计划 |
|
||||
| 相关考试时间窗口已过,但培训计划仍进行中 | 考试显示"已结束",无法参加,该考试通过状态默认为"未通过"(评估影响进度) |
|
||||
| 同一学员多次被添加(部门+个人双重覆盖) | tr_plan_target 可能有重复记录,进度计算时去重(按 user_id 聚合) |
|
||||
| 培训计划关联的知识被删除 | tr_plan_knowledge 记录仍在,显示时标注"知识已下架" [ASSUMPTION] |
|
||||
| 总必修项为 0 的计划 | 进度直接返回 100%(calculateProgress 中已处理:totalRequired=0 return 100)[SRC-API-01:5.4] |
|
||||
Reference in New Issue
Block a user