diff --git a/training/codes/training-system/PROJECT_RULE.md b/training/codes/training-system/PROJECT_RULE.md new file mode 100644 index 0000000..ccdf82a --- /dev/null +++ b/training/codes/training-system/PROJECT_RULE.md @@ -0,0 +1,127 @@ +# Java Maven 项目 AI 开发最高准则(必须遵守) + +## 一、技术栈(不可更改) +- 后端语言:Java +- JDK:17 +- 构建工具:Maven +- 后端框架:Spring Boot 3.1.2 +- 前端框架: Vue3 + TypeScript + Vite + Pinia + Router +- ORM:MyBatis Plus +- 数据库:MySQL 8 +- API 文档:Springdoc OpenAPI +- 序列化:Jackson +- 安全框架:Spring Security(用于BCrypt密码加密) +- 认证方式:JWT Token(使用 java-jwt) + +- 前端目录架构示例 +vue3-admin-template/ +│ +├── public/ +│ +├── src/ +│ │ +│ ├── main.ts +│ ├── App.vue +│ │ +│ ├── router/ +│ │ ├── index.ts +│ │ └── modules/ +│ │ ├── dashboard.ts +│ │ └── knowledge.ts +│ │ +│ ├── layouts/ +│ │ ├── BasicLayout.vue +│ │ └── components/ +│ │ ├── Sidebar.vue +│ │ ├── Header.vue +│ │ └── TagsView.vue +│ │ +│ ├── views/ +│ │ ├── dashboard/ +│ │ │ └── Dashboard.vue +│ │ │ +│ │ └── knowledge/ +│ │ ├── KnowledgeList.vue +│ │ ├── components/ +│ │ │ ├── CategoryTree.vue +│ │ │ ├── KnowledgeTable.vue +│ │ │ ├── KnowledgeForm.vue +│ │ │ └── BatchDialog.vue +│ │ │ +│ │ └── hooks/ +│ │ ├── useKnowledgeList.ts +│ │ └── useKnowledgeForm.ts +│ │ +│ ├── stores/ +│ │ ├── index.ts +│ │ ├── user.ts +│ │ └── modules/ +│ │ └── knowledge.ts +│ │ +│ ├── services/ +│ │ ├── request.ts +│ │ ├── modules/ +│ │ │ └── knowledge.ts +│ │ └── types.ts +│ │ +│ ├── types/ +│ │ ├── global.d.ts +│ │ └── knowledge.ts +│ │ +│ ├── composables/ +│ │ ├── usePagination.ts +│ │ └── useTableSelection.ts +│ │ +│ ├── components/ +│ │ ├── BaseTable/ +│ │ ├── BaseDialog/ +│ │ └── BaseForm/ +│ │ +│ ├── utils/ +│ │ ├── auth.ts +│ │ ├── validator.ts +│ │ ├── constants.ts +│ │ └── storage.ts +│ │ +│ ├── styles/ +│ │ ├── index.scss +│ │ ├── variables.scss +│ │ └── element-reset.scss +│ │ +│ └── config/ +│ └── env.ts +│ +├── .env.development +├── .env.production +├── tsconfig.json +├── vite.config.ts +└── package.json + +## 二、强制约束(最高优先级) +- ❌ 不允许引入未声明的新技术栈 +- ❌ 不允许更换框架或大版本 +- ❌ 不允许使用过时 API +- ❌ 不允许在本项目任何位置使用 Python(包括代码、脚本、Notebook 等) + +## 三、编码规范 +- 遵循《阿里 Java 开发规范》 +- 必须使用 Lombok +- Controller(RESTful) 层不写业务逻辑 +- Service + Impl 负责业务 +- Mapper 只做数据访问 + +## 四、通用要求 +- 所有代码必须: + - 可读 + - 可维护 + - 有必要注释 +- 不生成 Demo / 示例 / 伪代码 +- 生成代码必须可直接运行 + +## 五、当需求与以上规则冲突时 +👉 **以本文件为最高准则,拒绝执行冲突需求** + +## 六、业务逻辑要求 +- 实现部门ID逻辑: + - ADMIN:使用前端传入的 departmentId,若为空则报错 + - 非ADMIN:强制使用当前登录用户的 departmentId,忽略前端传值 diff --git a/training/codes/training-system/README.md b/training/codes/training-system/README.md new file mode 100644 index 0000000..c3a1640 --- /dev/null +++ b/training/codes/training-system/README.md @@ -0,0 +1,252 @@ +# 道路救援企业培训系统 + +> 版本:V1.0.1 | 基于 Spring Boot 3.1.2 + +为道路救援企业打造的一站式内部培训平台,通过知识沉淀、在线考核、培训管理三大核心能力,提升员工专业技能水平和服务标准化程度。 + +--- + +## 技术栈 + +| 类别 | 技术 | 版本 | +|-----|------|------| +| 语言 | Java | 17 | +| 构建 | Maven | 3.6+ | +| 框架 | Spring Boot | 3.1.2 | +| ORM | MyBatis Plus | 3.5.3.1 | +| 数据库 | MySQL | 8.0+ | +| 认证 | JWT (java-jwt) | 4.4.0 | +| API文档 | Springdoc OpenAPI | 2.2.0 | +| 工具库 | Lombok、Hutool | - | +| 文档预览 | kkFileView | 4.3.0 | + +--- + +## 功能模块 + +| 模块 | 功能说明 | +|-----|---------| +| **人员管理** | 组织架构(中心→部门→小组)、员工管理、角色权限 | +| **知识库** | 文档/视频上传、在线预览、分类管理、状态流转 | +| **考题管理** | 单选/多选/判断题、题目解析、题库分类 | +| **试卷管理** | 手动组卷、自动组卷、试卷预览 | +| **考试管理** | 发布考试、指定对象、在线答题、自动阅卷 | +| **培训计划** | 关联多个知识+多个考试、分配学员、进度跟踪 | + +--- + +## 快速开始 + +### 环境要求 + +- JDK 17+ +- Maven 3.6+ +- MySQL 8.0+ +- Docker(用于 kkFileView 文档预览服务) + +### 1. 克隆项目 + +```bash +git clone +cd training-system +``` + +### 2. 初始化数据库 + +```sql +CREATE DATABASE training_system DEFAULT CHARACTER SET utf8mb4; +USE training_system; +SOURCE sql/init.sql; +``` + +### 3. 修改配置 + +编辑 `src/main/resources/application.yml`,配置数据库连接: + +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/training_system + username: root + password: 你的密码 +``` + +### 4. 启动 kkFileView(文档预览服务) + +```bash +cd docker +docker-compose up -d +``` + +### 5. 启动应用 + +```bash +mvn spring-boot:run +``` + +### 6. 访问系统 + +- 系统地址:http://localhost:5173 +- API文档:http://localhost:8080/swagger-ui.html +- 默认账号:`admin` / `admin123` + +--- + +## 生产部署 + +### 打包 + +```bash +mvn clean package -DskipTests +``` + +生成文件:`target/training-system-1.0.0.jar` + +### Linux部署 + +```bash +# 上传JAR包和SQL脚本到服务器 +scp target/training-system-1.0.0.jar user@server:/opt/training/ +scp sql/init.sql user@server:/opt/training/ + +# 创建生产配置 /opt/training/application-prod.yml +# 启动应用 +java -jar training-system-1.0.0.jar --spring.profiles.active=prod +``` + +详细部署指南见 [部署文档](docs/Deploy.md) + +--- + +## kkFileView 文档预览服务 + +kkFileView 用于在线预览 Word、Excel、PPT 等 Office 文档。 + +### 功能说明 + +| 文件类型 | 预览方式 | +|---------|---------| +| PDF | 浏览器原生渲染 | +| JPG/PNG/GIF | 直接显示 | +| DOC/DOCX/XLS/XLSX/PPT/PPTX | kkFileView 转换预览 | +| 其他格式 | 提供下载 | + +### 安装部署 + +**方式一:Docker Compose(推荐)** + +```bash +cd docker +docker-compose up -d +``` + +**方式二:Docker 命令** + +```bash +docker run -d \ + --name kkfileview \ + -p 8012:8012 \ + -v /opt/kkfileview/files:/opt/kkFileView-4.0.0/file \ + --restart unless-stopped \ + keking/kkfileview:4.3.0 +``` + +### 配置说明 + +`application.yml` 相关配置: + +```yaml +training: + upload: + path: ./uploads/ # 文件存储目录 + preview: + enabled: true # 是否启用预览 + server-url: http://localhost:8012 # kkFileView 服务地址 + file-base-url: http://localhost:8080/uploads # 文件访问基础URL +``` + +### 生产环境配置 + +`application-prod.yml`: + +```yaml +training: + upload: + path: /data/training-system/uploads/ + preview: + enabled: true + server-url: http://kkfileview:8012 + file-base-url: http://your-domain.com/uploads +``` + +### 常用命令 + +```bash +# 查看服务状态 +docker ps | grep kkfileview + +# 查看日志 +docker logs -f kkfileview + +# 重启服务 +docker restart kkfileview + +# 停止服务 +docker stop kkfileview +``` + +### 降级方案 + +如需禁用文档预览功能,设置 `training.preview.enabled=false`,前端将自动降级为下载模式。 + +--- + +## 项目结构 + +``` +training-system/ +├── src/main/java/com/sino/training/ +│ ├── common/ # 公共模块(配置、异常、工具类) +│ ├── module/ +│ │ ├── system/ # 系统模块(用户、部门、小组) +│ │ ├── knowledge/ # 知识库模块 +│ │ ├── exam/ # 考试模块(题目、试卷、考试) +│ │ └── training/ # 培训计划模块 +│ └── TrainingApplication.java +├── src/main/resources/ +│ ├── static/ # 前端静态资源 +│ ├── application.yml # 配置文件 +│ └── mapper/ # MyBatis XML +├── sql/ +│ └── init.sql # 数据库初始化脚本 +├── docs/ # 项目文档 +└── pom.xml +``` + +--- + +## 编码规范 + +- 遵循《阿里 Java 开发规范》 +- Controller 层不写业务逻辑 +- Service + Impl 负责业务处理 +- Mapper 只做数据访问 +- 必须使用 Lombok 简化代码 + +--- + +## 相关文档 + +- [产品需求文档 (PRD)](docs/PRD.md) +- [低层级需求文档 (LLR)](docs/LLR.md) +- [测试计划](docs/test-reports/TestPlan.md) + +--- + +## 版本记录 + +| 版本 | 日期 | 更新内容 | +|-----|------|---------| +| V1.0.2 | 2026-01-21 | 新增 kkFileView 文档预览功能,修复静态资源路径配置 | +| V1.0.1 | 2026-01-13 | 培训计划支持多考试任务 | +| V1.0.0 | 2026-01-08 | 初始版本发布 | diff --git a/training/codes/training-system/context/01-产品文档/PRD.md b/training/codes/training-system/context/01-产品文档/PRD.md new file mode 100644 index 0000000..dcf2883 --- /dev/null +++ b/training/codes/training-system/context/01-产品文档/PRD.md @@ -0,0 +1,881 @@ +# 道路救援企业培训系统 - 产品需求文档 (PRD) + +> 版本:V1.0.1 +> 更新日期:2026-01-13 +> 状态:已确认 + +--- + +## 版本更新记录 + +### V1.0.1 (2026-01-13) + +**【培训计划】多考试任务支持** + +| 变更项 | 变更前 | 变更后 | +|-------|-------|-------| +| 考试任务数量 | 最多关联1个考试 | 支持关联多个考试 | +| 考试属性 | 无 | 支持设置必考/选考 | +| 考试排序 | 无 | 支持自定义排序 | + +**功能说明:** +- 创建/编辑培训计划时,可添加多个考试任务 +- 每个考试可设置为"必考"或"选考" +- 学员端展示所有关联考试及通过状态 +- 列表页显示考试任务总数 + +**数据库变更:** +- 新增 `tr_plan_exam` 关联表 +- 移除 `tr_plan` 表的 `exam_id` 字段 +- ⚠️ 不兼容历史数据,需重新初始化数据库 + +--- + +### V1.0.0 (2026-01-08) + +- 初始版本发布 +- 包含:系统基础、人员管理、知识库、考题管理、试卷管理、考试管理、培训计划六大模块 + +--- + +## 一、核心目标 (Mission) + +**为道路救援企业打造一站式内部培训平台,通过知识沉淀、在线考核、培训管理三大核心能力,提升500名员工的专业技能水平和服务标准化程度。** + +--- + +## 二、用户画像 (Persona) + +| 角色 | 人数估算 | 核心痛点 | 使用场景 | +|------|----------|----------|----------| +| **管理员** | 3-5人 | 人员管理分散,培训效果难追踪 | 配置系统、管理组织架构、分配权限 | +| **讲师(技师)** | 30-50人 | 知识传承依赖口口相传,出题组卷效率低 | 上传知识文档、创建题库、发布考试 | +| **学员** | 450人左右 | 学习资料分散、考试不便、进度不清晰 | 查阅知识库、完成培训任务、参加考试 | + +--- + +## 三、版本规划 + +### V1:最小可行产品 (MVP) + +#### 模块一:系统基础与人员管理 + +| 功能 | 说明 | +|----------|-----------------------------| +| 企业微信授权登录 | 企业微信授权登录 | +| 组织架构管理 | 中心 → 部门(≤8)→ 小组(≤10/部门)三级结构 | +| 员工管理 | 管理员手动录入员工信息,分配角色(管理员/讲师/学员) | +| 权限控制 | 基于角色的权限隔离,数据按部门隔离 | + +#### 模块二:知识库(重点) + +| 功能 | 说明 | +|------|------| +| 分类目录 | 支持多级分类(如:安全规范 > 高速救援 > 操作手册)| +| 文档管理 | 支持上传 PDF/Word/Excel/PPT 等常见文档格式 | +| 视频管理 | 支持上传视频文件,在线播放 | +| 在线预览 | 文档、视频无需下载即可在线查看 | +| 状态管理 | 草稿 → 已发布 → 已下架 | +| 部门隔离 | 各部门只能查看本部门的知识内容 | + +#### 模块三:考题管理 + +| 功能 | 说明 | +|------|------| +| 题型支持 | 单选题、多选题、判断题 | +| 题目解析 | 每道题必须填写答案解析 | +| 题库分类 | 按分类/章节管理题目 | +| 状态管理 | 草稿 → 已发布 → 已下架 | +| 部门隔离 | 讲师只能管理本部门题库 | + +#### 模块四:试卷管理 + +| 功能 | 说明 | +|------|------| +| 手动组卷 | 讲师手动从题库选题组成试卷 | +| 自动组卷 | 设置规则(单选X题+多选Y题+判断Z题),系统随机抽题 | +| 试卷配置 | 设置总分、题目分值、考试时长 | +| 试卷预览 | 组卷完成后可预览试卷效果 | +| 状态管理 | 草稿 → 已发布 → 已下架 | + +#### 模块五:考试管理 + +| 功能 | 说明 | +|------|------| +| 发布考试 | 选择试卷,设置考试名称、时间窗口(开始~结束时间)| +| 指定对象 | 支持指定部门 / 小组 / 个人参加考试 | +| 考试规则 | 设置及格线、限制考试次数、考试时长 | +| 在线答题 | 学员在线作答,自动计时,超时自动交卷 | +| 成绩查看 | 交卷后立即显示成绩和答案解析 | + +#### 模块六:培训计划 + +| 功能 | 说明 | +|------|------| +| 创建培训计划 | 设置计划名称、培训周期、培训目标 | +| 关联知识/考试 | 一个培训计划可包含多个知识文档 + 多场考试(V1.0.1+) | +| 考试属性设置 | 每个考试可设置必考/选考、自定义排序(V1.0.1+) | +| 分配学员 | 指定部门/小组/个人参加培训计划 | +| 进度跟踪 | 学员可查看自己的学习进度和考试状态 | +| 计划状态 | 未开始 / 进行中 / 已结束 | + +### V2 及以后版本 (Future Releases) + +| 版本 | 功能 | 价值 | +|------|------|------| +| **V2** | 统计报表中心 | 培训完成率、考试通过率、部门排名等数据看板 | +| **V2** | 线下培训签到 | 扫码签到/签退,记录实际培训时长 | +| **V2** | 错题本 | 学员自动收集错题,巩固薄弱知识点 | +| **V3** | 知识库增强 | 收藏、点赞、评论、全文搜索 | +| **V3** | 奖励体系 | 积分、勋章、学习排行榜 | +| **V3** | 证书管理 | 培训/考试 +--- + +## 四、关键业务逻辑 (Business Rules) + +### 4.1 权限规则 + +``` +管理员: + ├── 管理所有中心、部门、小组 + ├── 管理所有用户(增删改查、角色分配) + ├── 查看全平台数据 + └── 系统配置 + +讲师: + ├── 管理本部门知识库(上传/编辑/删除) + ├── 管理本部门题库(增删改查) + ├── 创建/管理本部门试卷和考试 + ├── 创建/管理本部门培训计划 + └── 查看本部门学员成绩 + +学员: + ├── 查看本部门知识库(只读) + ├── 参加分配给自己的培训计划 + ├── 参加分配给自己的考试 + └── 查看个人学习记录和成绩 +``` + +### 4.2 内容状态流转 + +``` +┌─────────┐ 发布 ┌─────────┐ 下架 ┌─────────┐ +│ 草稿 │ ───────────▶ │ 已发布 │ ───────────▶ │ 已下架 │ +│ DRAFT │ │PUBLISHED │ │ OFFLINE │ +└─────────┘ └─────────┘ └─────────┘ + ▲ │ │ + │ │ 重新上架 │ + │ ◀────────────────────────┘ + │ + └──────── 可继续编辑 ─────────────────────────────┘ +``` + +| 状态 | 说明 | 学员可见 | +|------|------|----------| +| **草稿 (DRAFT)** | 讲师编辑中,未完成 | ❌ 不可见 | +| **已发布 (PUBLISHED)** | 正式生效,可被使用 | ✅ 可见 | +| **已下架 (OFFLINE)** | 临时隐藏,保留数据 | ❌ 不可见 | + +### 4.3 考试规则 + +| 规则 | 说明 | +|------|------| +| 时间窗口 | 只有在考试开放时间内才能进入考试 | +| 限时作答 | 超过考试时长自动交卷 | +| 限制次数 | 达到最大次数后不可再考,取最高分 | +| 及格判定 | 分数 ≥ 及格线 为通过 | +| 答题顺序 | 自动组卷时题目顺序随机打乱 | + +### 4.4 数据隔离规则 + +| 数据类型 | 隔离方式 | +|----------|----------| +| 知识库 | 按部门隔离,只能看本部门 | +| 题库 | 按部门隔离,讲师只能操作本部门 | +| 试卷/考试 | 按部门隔离 | +| 培训计划 | 按部门隔离 | +| 员工信息 | 管理员可见全部,讲师可见本部门 | + +### 4.5 引用保护规则 + +| 场景 | 规则 | +|------|------| +| 题目下架 | 已被试卷引用的题目,下架前需确认警告 | +| 试卷下架 | 已被考试引用的试卷,下架前需确认警告 | +| 知识下架 | 已被培训计划引用的知识,下架前需确认警告 | + +--- + +## 五、数据契约 (Data Contract) + +### 5.1 核心实体 + +#### 用户 (sys_user) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| wx_userid | String | 企业微信用户ID | +| real_name | String | 姓名 | +| phone | String | 手机号 | +| role | Enum | 角色:ADMIN/LECTURER/STUDENT | +| department_id | Long | 所属部门 | +| group_id | Long | 所属小组(可空) | +| status | Enum | 状态:ENABLED/DISABLED | +| create_time | DateTime | 创建时间 | + +#### 组织架构 (sys_center / sys_department / sys_group) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| name | String | 名称 | +| parent_id | Long | 上级ID(部门关联中心,小组关联部门) | +| sort_order | Integer | 排序 | +| create_time | DateTime | 创建时间 | + +#### 知识分类 (km_category) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| name | String | 分类名称 | +| parent_id | Long | 父分类ID | +| department_id | Long | 所属部门 | +| sort_order | Integer | 排序 | + +#### 知识 (km_knowledge) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 标题 | +| category_id | Long | 所属分类 | +| type | Enum | 类型:DOCUMENT/VIDEO | +| file_url | String | 文件地址 | +| file_size | Long | 文件大小 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:DRAFT/PUBLISHED/OFFLINE | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | +| publish_time | DateTime | 发布时间 | + +#### 题目 (ex_question) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| type | Enum | 题型:SINGLE/MULTIPLE/JUDGE | +| content | String | 题干 | +| options | JSON | 选项列表(判断题为空) | +| answer | String | 正确答案 | +| analysis | String | 解析 | +| category_id | Long | 所属分类 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:DRAFT/PUBLISHED/OFFLINE | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | +| publish_time | DateTime | 发布时间 | + +#### 试卷 (ex_paper) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 试卷名称 | +| total_score | Integer | 总分 | +| duration | Integer | 考试时长(分钟) | +| pass_score | Integer | 及格分 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:DRAFT/PUBLISHED/OFFLINE | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | +| publish_time | DateTime | 发布时间 | + +#### 试卷题目关联 (ex_paper_question) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| paper_id | Long | 试卷ID | +| question_id | Long | 题目ID | +| score | Integer | 该题分值 | +| sort_order | Integer | 排序 | + +#### 考试 (ex_exam) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 考试名称 | +| paper_id | Long | 关联试卷 | +| start_time | DateTime | 开始时间 | +| end_time | DateTime | 结束时间 | +| max_attempts | Integer | 最大考试次数 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:NOT_STARTED/IN_PROGRESS/ENDED | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | + +#### 考试对象 (ex_exam_target) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| exam_id | Long | 考试ID | +| target_type | Enum | 对象类型:DEPARTMENT/GROUP/USER | +| target_id | Long | 对象ID | + +#### 考试记录 (ex_exam_record) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| exam_id | Long | 考试ID | +| user_id | Long | 学员ID | +| attempt_no | Integer | 第几次考试 | +| score | Integer | 得分 | +| passed | Boolean | 是否通过 | +| start_time | DateTime | 开始时间 | +| submit_time | DateTime | 提交时间 | +| answers | JSON | 答题详情 | + +#### 培训计划 (tr_plan) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 计划名称 | +| description | String | 计划描述 | +| start_date | Date | 开始日期 | +| end_date | Date | 结束日期 | +| department_id | Long | 所属部门 | +| exam_id | Long | 关联考试(可空) | +| status | Enum | 状态:NOT_STARTED/IN_PROGRESS/ENDED | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | + +#### 培训计划-知识关联 (tr_plan_knowledge) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| plan_id | Long | 计划ID | +| knowledge_id | Long | 知识ID | +| required | Boolean | 是否必修 | +| sort_order | Integer | 排序 | + +#### 培训计划-对象 (tr_plan_target) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| plan_id | Long | 计划ID | +| target_type | Enum | 对象类型:DEPARTMENT/GROUP/USER | +| target_id | Long | 对象ID | + +#### 培训进度 (tr_plan_progress) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| plan_id | Long | 计划ID | +| user_id | Long | 学员ID | +| knowledge_id | Long | 知识ID | +| completed | Boolean | 是否完成 | +| complete_time | DateTime | 完成时间 | + +--- + +## 六、原型设计(方案A - 经典管理后台风格) + +### 6.1 设计理念 + +左侧固定导航 + 右侧内容区,层级分明,适合功能复杂的企业管理系统。学习成本低,用户容易上手。 + +### 6.2 布局结构 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 │ 内容区域 │ +│ │ │ +│ 👥 人员管理 ▶ │ │ +│ ├ 组织架构 │ │ +│ ├ 员工管理 │ │ +│ └ 讲师管理 │ │ +│ │ │ +│ 📚 知识库 ▶ │ │ +│ ├ 知识分类 │ │ +│ └ 知识列表 │ │ +│ │ │ +│ ✏️ 考题管理 ▶ │ │ +│ ├ 题库分类 │ │ +│ └ 题目列表 │ │ +│ │ │ +│ 📄 试卷管理 │ │ +│ │ │ +│ 📝 考试管理 │ │ +│ │ │ +│ 📅 培训计划 │ │ +│ │ │ +│ ────────────── │ │ +│ ⚙️ 系统设置 │ │ +│ │ │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +### 6.3 核心页面原型 + +#### 工作台首页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 ● │ 欢迎回来,张三 2026年1月8日 │ +│ │ │ +│ 👥 人员管理 ▶ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ │ 待学习课程 │ │ 待完成考试 │ │ 培训进度 │ │ +│ 📚 知识库 ▶ │ │ │ │ │ │ │ │ +│ │ │ 12 │ │ 3 │ │ 75% │ │ +│ ✏️ 考题管理 ▶ │ │ │ │ │ │ ████░░ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ 📄 试卷管理 │ │ +│ │ ┌──────────────────────────────────────────────────┐ │ +│ 📝 考试管理 │ │ 我的培训计划 │ │ +│ │ ├──────────────────────────────────────────────────┤ │ +│ 📅 培训计划 │ │ 📋 2026年Q1安全规范培训 进行中 60% │ │ +│ │ │ 📋 新员工入职培训 进行中 30% │ │ +│ │ │ 📋 高速救援操作规范 未开始 -- │ │ +│ │ └──────────────────────────────────────────────────┘ │ +│ │ │ +│ │ ┌──────────────────────────────────────────────────┐ │ +│ │ │ 待完成考试 │ │ +│ │ ├──────────────────────────────────────────────────┤ │ +│ │ │ 📝 安全规范考核 截止: 1月15日 [进入考试] │ │ +│ │ │ 📝 月度技能测试 截止: 1月20日 [进入考试] │ │ +│ │ └──────────────────────────────────────────────────┘ │ +│ │ │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +#### 知识库列表页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 │ 知识库 > 安全规范 │ +│ │ ───────────────────────────────────────────────── │ +│ 👥 人员管理 ▶ │ │ +│ │ [+ 新增知识] [所有状态 ▼] [🔍 搜索知识...] │ +│ 📚 知识库 ▼ │ │ +│ ┌ 知识分类 │ ┌────────────────────────────────────────────────┐ │ +│ └ 知识列表 ● │ │ 标题 类型 状态 更新时间 操作│ │ +│ │ ├────────────────────────────────────────────────┤ │ +│ ✏️ 考题管理 ▶ │ │ 高速救援SOP 📄文档 已发布 01-05 编辑│ │ +│ │ │ 安全操作视频 🎬视频 已发布 01-03 编辑│ │ +│ 📄 试卷管理 │ │ 应急处理流程 📄文档 草稿 01-02 编辑│ │ +│ │ │ 设备使用手册 📄文档 已下架 12-28 编辑│ │ +│ 📝 考试管理 │ └────────────────────────────────────────────────┘ │ +│ │ │ +│ 📅 培训计划 │ 共 24 条记录 < 1 2 3 4 5 > │ +│ │ │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +#### 在线考试页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 📝 2026年Q1安全规范考试 剩余时间: 45:23 │ +├────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ 第 3 题 / 共 20 题 [单选题] │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ │ │ +│ │ 高速公路救援作业时,警示标志应放置在故障车辆后方多少米处? │ │ +│ │ │ │ +│ │ ○ A. 50米 │ │ +│ │ ● B. 150米 │ │ +│ │ ○ C. 200米 │ │ +│ │ ○ D. 100米 │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ 答题卡: │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ ✓1 ✓2 ●3 ○4 ○5 ○6 ○7 ○8 ○9 ○10 │ │ +│ │ ○11 ○12 ○13 ○14 ○15 ○16 ○17 ○18 ○19 ○20 │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ [< 上一题] [下一题 >] [交卷] │ +│ │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +#### 试卷组卷页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 │ 创建试卷 > 第2步:选择题目 │ +│ │ ═══════════════════════════════════════════════════ │ +│ 👥 人员管理 ▶ │ ① 基本信息 ────── ② 选择题目 ────── ③ 预览确认 │ +│ │ ● │ +│ 📚 知识库 ▶ │ │ +│ │ ┌─────────────────────┐ ┌────────────────────────┐ │ +│ ✏️ 考题管理 ▶ │ │ 📂 题库 │ │ 📋 已选题目 (15) │ │ +│ │ │ ┌─────────────────┐ │ │ │ │ +│ 📄 试卷管理 ● │ │ │ [全选] 安全(48) │ │ │ 单选题 x10 30分 │ │ +│ │ │ │ ☑ 高速警示... │ │ │ 多选题 x3 15分 │ │ +│ 📝 考试管理 │ │ │ ☑ 夜间灯光... │ │ │ 判断题 x2 5分 │ │ +│ │ │ │ ☐ 拖车规范... │ │ │ ─────────────── │ │ +│ 📅 培训计划 │ │ └─────────────────┘ │ │ 总分: 50分 │ │ +│ │ │ │ │ │ │ +│ │ │ ── 或自动组卷 ── │ │ [自动组卷] │ │ +│ │ │ 单选: [10] 题 │ │ 单选: [10] 题 │ │ +│ │ │ 多选: [ 5] 题 │ │ 多选: [ 5] 题 │ │ +│ │ │ 判断: [ 5] 题 │ │ 判断: [ 5] 题 │ │ +│ │ │ [随机抽题] │ │ [确认随机抽取] │ │ +│ │ └─────────────────────┘ └────────────────────────┘ │ +│ │ │ +│ │ [上一步] [下一步:预览] │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +--- + +## 七、架构设计蓝图 + +### 7.1 系统架构总览 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 客户端层 │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ PC浏览器 │ │ 企业微信H5 │ │ 移动端浏览器 │ │ +│ │ (管理员/讲师) │ │ (学员) │ │ (学员) │ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ +└───────────┼─────────────────────┼─────────────────────┼─────────────────┘ + │ │ │ + └─────────────────────┼─────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 网关层 │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Spring Boot 应用 │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ JWT认证 │ │ 权限拦截 │ │ 日志记录 │ │ 异常处理 │ │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 业务层 │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ 认证模块 │ │ 组织模块 │ │ 知识库模块│ │ 考试模块 │ │ 培训模块 │ │ +│ │ │ │ │ │ │ │ │ │ │ │ +│ │ ·企微登录 │ │ ·组织架构 │ │ ·分类管理 │ │ ·题库管理 │ │ ·计划管理 │ │ +│ │ ·Token管理│ │ ·员工管理 │ │ ·知识CRUD │ │ ·试卷管理 │ │ ·任务分配 │ │ +│ │ ·权限校验 │ │ ·角色管理 │ │ ·文件上传 │ │ ·考试管理 │ │ ·进度跟踪 │ │ +│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 数据层 │ +│ ┌─────────────────────────┐ ┌─────────────────────────┐ │ +│ │ MySQL 8.0 │ │ 文件存储服务 │ │ +│ │ (MyBatis Plus ORM) │ │ (本地/OSS/MinIO) │ │ +│ └─────────────────────────┘ └─────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 7.2 核心流程图 + +#### 用户登录流程(企业微信OAuth) + +```mermaid +sequenceDiagram + participant U as 用户 + participant FE as 前端页面 + participant BE as 后端服务 + participant WX as 企业微信API + participant DB as 数据库 + + U->>FE: 点击"企业微信登录" + FE->>WX: 跳转企微授权页面 + WX->>U: 显示授权确认 + U->>WX: 确认授权 + WX->>FE: 回调返回code + FE->>BE: 携带code请求登录 + BE->>WX: 用code换取access_token + WX-->>BE: 返回access_token + BE->>WX: 获取用户信息(userid) + WX-->>BE: 返回用户信息 + BE->>DB: 查询用户是否存在 + alt 用户存在 + DB-->>BE: 返回用户信息 + BE->>BE: 生成JWT Token + BE-->>FE: 返回Token+用户信息 + FE->>FE: 存储Token,跳转工作台 + else 用户不存在 + BE-->>FE: 返回错误:用户未录入 + FE->>U: 提示联系管理员 + end +``` + +#### 在线考试流程 + +```mermaid +sequenceDiagram + participant S as 学员 + participant FE as 前端页面 + participant BE as 后端服务 + participant DB as 数据库 + + S->>FE: 进入考试列表 + FE->>BE: 获取我的考试列表 + BE->>DB: 查询分配给该学员的考试 + DB-->>BE: 返回考试列表 + BE-->>FE: 返回考试列表(含剩余次数) + + S->>FE: 点击"开始考试" + FE->>BE: 请求开始考试 + BE->>DB: 校验考试状态/次数/时间窗口 + alt 校验通过 + BE->>DB: 创建考试记录(开始时间) + BE->>DB: 获取试卷题目 + DB-->>BE: 返回题目列表 + BE-->>FE: 返回试卷内容+考试时长 + FE->>FE: 启动倒计时 + + loop 答题过程 + S->>FE: 选择/修改答案 + FE->>FE: 本地暂存答案 + FE->>BE: 定时自动保存(每30秒) + BE->>DB: 更新答题记录 + end + + alt 主动交卷 + S->>FE: 点击"交卷" + else 时间到 + FE->>FE: 倒计时结束 + end + + FE->>BE: 提交试卷 + BE->>BE: 自动判分(客观题) + BE->>DB: 保存成绩+答案详情 + BE-->>FE: 返回考试成绩 + FE->>S: 显示成绩+答案解析 + else 校验失败 + BE-->>FE: 返回错误(次数用尽/不在时间窗口) + FE->>S: 提示无法参加考试 + end +``` + +#### 培训计划执行流程 + +```mermaid +flowchart TB + subgraph 讲师操作 + A[创建培训计划] --> B[设置基本信息] + B --> C[关联知识内容] + C --> D[关联考试-可选] + D --> E[指定培训对象] + E --> F[发布培训计划] + end + + subgraph 学员学习 + F --> G[学员收到培训通知] + G --> H[进入培训计划] + H --> I[学习知识内容] + I --> J{知识学完?} + J -->|否| I + J -->|是| K{有关联考试?} + K -->|否| L[培训完成] + K -->|是| M[参加考试] + M --> N{考试通过?} + N -->|是| L + N -->|否| O{还有考试次数?} + O -->|是| M + O -->|否| P[培训未通过] + end + + subgraph 进度追踪 + I --> Q[更新学习进度] + M --> R[记录考试成绩] + L --> S[标记培训完成] + end +``` + +### 7.3 技术选型 + +| 层次 | 技术 | 版本 | 说明 | +|------|------|------|------| +| **构建** | Maven | 3.8+ | 依赖管理 | +| **框架** | Spring Boot | 3.1.2 | 主框架 | +| **ORM** | MyBatis Plus | 3.5.3+ | 简化CRUD | +| **数据库** | MySQL | 8.0 | 主数据库 | +| **认证** | java-jwt | 4.4+ | JWT Token生成验证 | +| **加密** | Spring Security | 6.x | BCrypt密码加密 | +| **文档** | Springdoc OpenAPI | 2.2+ | API文档 | +| **工具** | Lombok | 1.18+ | 简化代码 | +| **JSON** | Jackson | 内置 | JSON序列化 | +| **文件** | 本地存储 / MinIO | - | V1先用本地,V2可扩展 | +| **前端** | HTML + CSS + JS | - | 原生技术栈 | + +#### 关键依赖库 + +| 用途 | 库 | 说明 | +|------|-----|------| +| 企业微信SDK | weixin-java-cp | 企业微信Java SDK | +| 文件预览 | kkFileView / OnlyOffice | 文档在线预览(可选外部服务) | +| 视频播放 | video.js | 前端视频播放器 | +| Excel导入导出 | EasyExcel | 阿里开源,性能好 | +| 工具库 | Hutool | 常用工具集合 | + +### 7.4 风险评估 + +| 风险点 | 等级 | 描述 | 应对策略 | +|--------|------|------|----------| +| **企业微信对接** | 🟡 中 | 需要企业微信管理员配置应用 | 提前准备配置文档,预留账号密码登录作为备选 | +| **文件预览** | 🟡 中 | 不同格式文档预览兼容性 | V1优先支持PDF+图片+视频,复杂格式提示下载 | +| **大文件上传** | 🟡 中 | 视频文件可能较大 | 限制单文件大小,支持分片上传 | +| **考试并发** | 🟢 低 | 500人规模并发压力不大 | 合理设计索引,必要时加缓存 | +| **数据隔离** | 🟢 低 | 部门数据隔离逻辑 | 统一在Service层实现隔离过滤 | + +### 7.5 项目结构 + +``` +training-system/ +├── pom.xml +├── src/ +│ └── main/ +│ ├── java/com/sino/training/ +│ │ ├── TrainingApplication.java # 启动类 +│ │ │ +│ │ ├── common/ # 通用模块 +│ │ │ ├── config/ # 配置类 +│ │ │ ├── exception/ # 异常处理 +│ │ │ ├── result/ # 统一响应 +│ │ │ ├── interceptor/ # 拦截器 +│ │ │ └── utils/ # 工具类 +│ │ │ +│ │ ├── module/ # 业务模块 +│ │ │ ├── auth/ # 认证模块 +│ │ │ ├── system/ # 系统管理 +│ │ │ ├── knowledge/ # 知识库模块 +│ │ │ ├── exam/ # 考试模块 +│ │ │ └── training/ # 培训模块 +│ │ │ +│ │ └── integration/ # 外部集成 +│ │ └── wechat/ # 企业微信 +│ │ +│ └── resources/ +│ ├── application.yml # 主配置 +│ ├── application-dev.yml # 开发环境 +│ ├── application-prod.yml # 生产环境 +│ ├── mapper/ # MyBatis XML +│ ├── static/ # 静态资源 +│ └── templates/ # 页面模板 +│ +├── docs/ +│ └── PRD.md # 产品需求文档 +│ +└── sql/ + └── init.sql # 数据库初始化脚本 +``` + +--- + +## 八、附录 + +### 8.1 角色菜单权限对照表 + +| 菜单 | 管理员 | 讲师 | 学员 | +|------|--------|------|------| +| 工作台 | ✅ | ✅ | ✅ | +| 组织架构管理 | ✅ | ❌ | ❌ | +| 员工管理 | ✅ | 👁️ 本部门只读 | ❌ | +| 讲师管理 | ✅ | ❌ | ❌ | +| 知识库-分类管理 | ✅ | ✅ 本部门 | ❌ | +| 知识库-知识列表 | ✅ | ✅ 本部门 | 👁️ 本部门已发布 | +| 考题管理-题库分类 | ✅ | ✅ 本部门 | ❌ | +| 考题管理-题目列表 | ✅ | ✅ 本部门 | ❌ | +| 试卷管理 | ✅ | ✅ 本部门 | ❌ | +| 考试管理 | ✅ | ✅ 本部门 | 👁️ 我的考试 | +| 培训计划 | ✅ | ✅ 本部门 | 👁️ 我的培训 | +| 系统设置 | ✅ | ❌ | ❌ | + +### 8.2 状态枚举定义 + +```java +// 内容状态 +public enum ContentStatus { + DRAFT, // 草稿 + PUBLISHED, // 已发布 + OFFLINE // 已下架 +} + +// 用户角色 +public enum UserRole { + ADMIN, // 管理员 + LECTURER, // 讲师 + STUDENT // 学员 +} + +// 用户状态 +public enum UserStatus { + ENABLED, // 启用 + DISABLED // 禁用 +} + +// 题目类型 +public enum QuestionType { + SINGLE, // 单选题 + MULTIPLE, // 多选题 + JUDGE // 判断题 +} + +// 知识类型 +public enum KnowledgeType { + DOCUMENT, // 文档 + VIDEO // 视频 +} + +// 考试/培训目标类型 +public enum TargetType { + DEPARTMENT, // 部门 + GROUP, // 小组 + USER // 个人 +} + +// 考试状态 +public enum ExamStatus { + NOT_STARTED, // 未开始 + IN_PROGRESS, // 进行中 + ENDED // 已结束 +} + +// 培训计划状态 +public enum PlanStatus { + NOT_STARTED, // 未开始 + IN_PROGRESS, // 进行中 + ENDED // 已结束 +} +``` + +--- + +**文档状态:已确认,等待开发启动** diff --git a/training/codes/training-system/context/01-产品文档/student.md b/training/codes/training-system/context/01-产品文档/student.md new file mode 100644 index 0000000..4f3c9ed --- /dev/null +++ b/training/codes/training-system/context/01-产品文档/student.md @@ -0,0 +1,664 @@ +# 学员端功能需求文档 + +> 版本:V1.0.1 +> 创建日期:2026-01-15 +> 更新日期:2026-01-15 +> 状态:✅ 已批准(Supervisor 复审通过) + +--- + +## 版本更新记录 + +### V1.0.1 (2026-01-15) + +**根据Supervisor审查意见修订:** + +| 修订项 | 修订内容 | +|--------|----------| +| 知识学习入口 | 补充培训任务学习场景、返回逻辑、来源区分 | +| 断点续考机制 | 补充定时任务策略、超时判定规则、并发处理 | +| 排名计算规则 | 明确计算时机、统计范围、口径定义 | +| 学习时长防刷 | 新增活跃检测机制 | +| 数据表设计 | km_knowledge_progress 新增 department_id 字段 | +| 页面路径 | 调整为复用现有路径,按角色控制 | +| 解析显示策略 | 明确交卷后立即显示解析 | +| 非功能需求 | 补充可测试的性能指标 | + +--- + +## 一、概述 + +### 1.1 目标用户 +- **角色**:学员(STUDENT) +- **人数**:约450人 +- **入口**:PC浏览器(与管理端共用系统,按角色显示不同菜单) + +### 1.2 核心目标 +为学员提供便捷的在线学习平台,支持知识学习、在线考试、培训计划跟踪,帮助学员提升专业技能。 + +--- + +## 二、功能模块总览 + +| 模块 | 功能点 | 优先级 | +|------|--------|--------| +| 工作台 | 学习概览、待办事项 | P0 | +| 知识学习 | 知识浏览、学习进度、时长统计 | P0 | +| 在线考试 | 考试列表、在线答题、断点续考、成绩排名 | P0 | +| 我的培训 | 培训计划、进度跟踪、完成判定 | P0 | +| 个人中心 | 学习记录、考试记录、证书(预留) | P1 | + +--- + +## 三、详细功能需求 + +### 3.1 工作台(首页) + +#### 用户故事 +> 作为学员,我希望登录后看到学习概览和待办事项,以便快速了解学习状态和接下来要做的事。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 学习统计卡片 | 显示:待学习课程数、待完成考试数、培训进度百分比 | +| 我的培训计划 | 列表展示分配给我的培训计划(最多显示5条),显示进度 | +| 待完成考试 | 列表展示待完成的考试(最多显示5条),显示截止时间 | +| 快捷入口 | 点击可直接进入对应详情页 | + +#### 验收标准 +- [ ] 登录后默认进入工作台页面 +- [ ] 统计数据实时准确(待学习=未完成的必修知识数,待考试=未通过的必考考试数) +- [ ] 培训进度百分比 = 已完成必修项 / 总必修项 × 100% +- [ ] 点击培训计划可跳转到计划详情 +- [ ] 点击考试可跳转到考试页面 +- [ ] 只显示"进行中"状态的培训计划和处于"时间窗口内"的考试 + +--- + +### 3.2 知识学习 + +#### 用户故事 +> 作为学员,我希望浏览本部门的知识库,学习文档和视频,并能看到自己的学习进度。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 知识分类导航 | 左侧树形结构展示知识分类 | +| 知识列表 | 按分类筛选,显示知识标题、类型、学习状态 | +| 知识详情/预览 | 文档在线预览、视频在线播放 | +| 学习进度记录 | 记录学习完成状态、学习时长 | +| 强制学习机制 | 视频必须播放完成、文档必须阅读完成才记录为"已完成" | + +#### 3.2.1 知识列表页 + +**页面元素**: +- 分类筛选(树形) +- 知识卡片列表:标题、类型图标(文档/视频)、学习状态标签、时长/大小 +- 搜索框(按标题搜索) + +**学习状态**: +| 状态 | 说明 | 显示样式 | +|------|------|----------| +| 未学习 | 从未打开过 | 灰色标签 | +| 学习中 | 已开始但未完成 | 蓝色标签 + 进度% | +| 已完成 | 满足完成条件 | 绿色标签 ✓ | + +#### 3.2.2 知识详情页 + +**文档类型**: +- 在线预览(PDF直接显示,Word/Excel/PPT转换预览) +- 学习计时:进入页面开始计时,离开页面停止 +- 完成条件:停留时间 ≥ 预估阅读时间(可配置,默认按页数计算) + +**视频类型**: +- 在线播放(支持进度条、全屏) +- 播放进度记录:记录当前播放位置,下次打开从断点继续 +- 完成条件:播放进度 ≥ 90%(可配置) +- 禁止拖动快进(强制学习模式下) + +#### 3.2.3 学习入口场景(🔴 修订项1) + +**场景一:自由学习(从知识库进入)** +- 入口:左侧菜单"知识库" → 知识列表 → 知识详情 +- 学习完成后:停留在知识详情页,可点击"返回列表" +- 进度记录:标记来源为 `FREE`(自由学习) + +**场景二:培训任务学习(从培训计划进入)** +- 入口:培训详情页 → 点击知识项 → 知识详情 +- 页面顶部显示:返回培训计划入口(如:"← 返回《2026年Q1安全规范培训》") +- 学习完成后: + - 自动弹出提示:"学习完成!是否返回培训计划?" + - 用户可选择【返回培训】或【继续浏览】 +- 进度记录:标记来源为 `TRAINING`(培训任务),关联 `plan_id` + +**进度记录来源区分**: +| 来源 | 场景 | 影响 | +|------|------|------| +| FREE | 自由学习 | 仅记录个人学习进度 | +| TRAINING | 培训任务学习 | 同时更新培训计划进度 | + +> **说明**:无论从哪个入口学习,学习时长和完成状态都会记录。区分来源是为了后续统计分析(如:培训驱动的学习 vs 自主学习)。 + +#### 3.2.4 学习时长防刷机制(🟡 优化项1) + +**文档类型防刷策略**: +- 每60秒检测一次用户活跃状态 +- 活跃判定:60秒内有鼠标移动、点击、滚动任一事件 +- 非活跃时:暂停计时,页面显示"检测到您暂时离开,学习计时已暂停" +- 恢复活跃后:继续计时 + +**视频类型防刷策略**: +- 依赖视频播放事件,暂停时不计时长 +- 无需额外活跃检测 + +**多标签页处理**: +- 同一知识同时只能在一个标签页学习 +- 检测到多标签页打开同一知识时,后打开的标签页提示"该知识已在其他窗口学习中" + +#### 验收标准 +- [ ] 只显示本部门、已发布状态的知识 +- [ ] 分类树正确展示,点击分类筛选对应知识 +- [ ] 文档能正常在线预览(至少支持PDF) +- [ ] 视频能正常播放,支持暂停、音量调节 +- [ ] 视频播放进度自动保存,刷新页面后从断点继续 +- [ ] 强制学习模式下,视频不能快进跳过 +- [ ] 学习时长准确记录(精确到秒) +- [ ] 满足完成条件后自动标记为"已完成" +- [ ] 学习进度与培训计划进度联动 +- [ ] 🔴 从培训计划进入的知识学习,顶部显示返回入口 +- [ ] 🔴 学习完成后弹出返回培训计划提示(仅培训任务学习场景) +- [ ] 🟡 文档学习时,非活跃状态暂停计时 +- [ ] 🟡 同一知识不能在多个标签页同时学习 + +--- + +### 3.3 在线考试 + +#### 用户故事 +> 作为学员,我希望参加分配给我的考试,并能查看成绩和排名。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 考试列表 | 展示分配给我的所有考试 | +| 考试详情 | 显示考试信息、我的考试记录 | +| 在线答题 | 单选/多选/判断题作答 | +| 断点续考 | 中途退出后可继续答题 | +| 自动交卷 | 时间到自动提交 | +| 成绩查看 | 显示得分、答案解析 | +| 成绩排名 | 显示我在本次考试中的排名 | + +#### 3.3.1 考试列表页 + +**列表字段**: +| 字段 | 说明 | +|------|------| +| 考试名称 | 点击进入考试详情 | +| 考试时间 | 开始时间 ~ 结束时间 | +| 考试状态 | 未开始/进行中/已结束 | +| 我的状态 | 未参加/考试中/已完成 | +| 最高成绩 | 多次考试取最高分 | +| 剩余次数 | 最大次数 - 已考次数 | +| 操作 | 进入考试/查看成绩 | + +**筛选条件**: +- 考试状态:全部/进行中/已结束 +- 我的状态:全部/未完成/已完成 + +#### 3.3.2 考试详情页(进入考试前) + +**显示信息**: +- 考试名称、考试时间窗口 +- 考试规则:时长、总分、及格线、最大次数 +- 我的考试记录表格(第N次、得分、是否通过、时间) +- 【开始考试】/【继续考试】按钮 + +**按钮逻辑**: +| 场景 | 按钮状态 | +|------|----------| +| 不在时间窗口内 | 禁用,提示"考试未开始"或"考试已结束" | +| 次数已用完 | 禁用,提示"考试次数已用完" | +| 有进行中的考试记录 | 显示【继续考试】 | +| 可以开始新考试 | 显示【开始考试】 | + +#### 3.3.3 在线答题页 + +**页面布局**: +``` +┌─────────────────────────────────────────────────────────┐ +│ 考试名称 剩余时间: 45:23 │ +├─────────────────────────────────────────────────────────┤ +│ 第 3 题 / 共 20 题 [单选题] │ +│ ───────────────────────────────────────────────────── │ +│ 题干内容... │ +│ │ +│ ○ A. 选项A │ +│ ● B. 选项B(已选中) │ +│ ○ C. 选项C │ +│ ○ D. 选项D │ +├─────────────────────────────────────────────────────────┤ +│ 答题卡: [1✓] [2✓] [3●] [4○] [5○] ... │ +├─────────────────────────────────────────────────────────┤ +│ [上一题] [下一题] [交卷] │ +└─────────────────────────────────────────────────────────┘ +``` + +**功能说明**: +| 功能 | 说明 | +|------|------| +| 倒计时 | 显示剩余时间,最后5分钟变红色提醒 | +| 题目导航 | 点击答题卡数字可跳转到对应题目 | +| 答案自动保存 | 选择答案后自动保存到服务器(每30秒或切题时) | +| 上一题/下一题 | 切换题目 | +| 交卷 | 弹出确认框,确认后提交 | +| 自动交卷 | 时间结束自动提交当前答案 | + +**断点续考机制**: +- 考试开始后创建考试记录,状态为"进行中" +- 答案实时保存到服务器 +- 中途关闭浏览器/断网,考试记录保持"进行中" +- 重新打开考试,检测到进行中记录,恢复到上次状态 +- 倒计时从剩余时间继续(服务端计算:考试时长 - 已用时间) +- 超时未提交的考试,由定时任务自动交卷 + +#### 3.3.5 超时自动交卷机制(🔴 修订项2) + +**超时判定规则**: +``` +超时时间点 = 考试记录开始时间 + 考试时长(分钟) +``` + +**示例**: +- 学员A在 10:00 开始考试,考试时长60分钟 +- 超时时间点 = 10:00 + 60分钟 = 11:00 +- 无论考试时间窗口是否结束,11:00后该考试记录即视为超时 + +**定时任务策略**: +| 配置项 | 值 | 说明 | +|--------|-----|------| +| 执行频率 | 每1分钟 | Cron: `0 */1 * * * ?` | +| 扫描范围 | 状态为IN_PROGRESS的考试记录 | 只处理进行中的记录 | +| 超时判定 | 当前时间 > 开始时间 + 考试时长 | 精确到秒 | +| 处理动作 | 自动提交当前已保存的答案,计算成绩 | 标记为系统自动交卷 | + +**并发提交处理(乐观锁)**: +``` +场景:学员在超时前1秒点击交卷,同时定时任务也在处理该记录 +``` + +**处理策略**: +1. 考试记录表增加 `version` 字段(乐观锁) +2. 提交时检查 `status = IN_PROGRESS AND version = 当前版本` +3. 更新时 `version = version + 1` +4. 若更新失败(version已变),说明已被其他请求处理,直接返回已提交的结果 + +**状态流转**: +``` +IN_PROGRESS ──用户主动交卷──▶ SUBMITTED(来源:USER) + │ + └──────定时任务超时交卷──▶ SUBMITTED(来源:SYSTEM_TIMEOUT) +``` + +**交卷来源标记**: +| 来源 | 说明 | +|------|------| +| USER | 用户主动交卷 | +| SYSTEM_TIMEOUT | 系统超时自动交卷 | + +> **注意**:即使考试时间窗口已结束,只要考试记录未超时,学员仍可继续答题(但无法开始新的考试) + +#### 3.3.6 考试结果页 + +**显示内容**: +| 项目 | 说明 | +|------|------| +| 得分 | 大字显示,及格绿色/不及格红色 | +| 是否通过 | 通过✓ / 未通过✗ | +| 排名 | "您的成绩排名第 X 名(共 Y 人参加)" | +| 答题详情 | 每道题的正确答案、我的答案、解析 | +| 操作按钮 | 【再考一次】(有次数)/ 【返回列表】 | + +**答案解析显示策略(🟡 优化项4)**: +- 交卷后立即显示所有题目的答案解析 +- 无论考试是否通过,都显示解析 +- 目的:培训学习为主,帮助学员理解错误原因 + +#### 3.3.7 排名计算规则(🔴 修订项3) + +**计算时机**: +- 实时计算:每次交卷时计算排名 +- 非定时计算:不采用批量定时计算方式 + +**统计范围**: +- 范围:当前考试的所有参与者(被分配且已交卷的学员) +- 跨部门:若考试分配给多个部门,排名为所有参与者的总排名 + +**排名规则**: +| 优先级 | 规则 | 说明 | +|--------|------|------| +| 1 | 最高成绩降序 | 成绩高的排名靠前 | +| 2 | 达到最高成绩的时间升序 | 成绩相同时,先达到该成绩的排名靠前 | + +**"共Y人参加"统计口径**: +| 统计项 | 是否计入 | +|--------|----------| +| 已交卷学员 | ✅ 计入 | +| 正在考试中(未交卷) | ❌ 不计入 | +| 被分配但未参加 | ❌ 不计入 | + +**排名示例**: +``` +考试:安全规范考核 +分配对象:部门A(30人)、部门B(20人) + +参与情况: +- 部门A:25人已交卷,5人未参加 +- 部门B:18人已交卷,2人正在考试中 + +排名统计:共 43 人参加(25 + 18 = 43) + +排名结果: +第1名:张三(95分,首次达到时间 10:30) +第2名:李四(95分,首次达到时间 10:45) +第3名:王五(90分) +... +``` + +**实现建议**: +- 交卷时实时计算排名并返回 +- 排名计算SQL示例思路: + ```sql + SELECT user_id, MAX(score) as best_score, MIN(submit_time) as first_best_time + FROM ex_exam_record + WHERE exam_id = ? AND status = 'SUBMITTED' + GROUP BY user_id + ORDER BY best_score DESC, first_best_time ASC + ``` + +#### 验收标准 +- [ ] 只显示分配给当前学员的考试 +- [ ] 考试状态、我的状态准确显示 +- [ ] 不在时间窗口内无法进入考试 +- [ ] 次数用完无法再次考试 +- [ ] 答题过程中答案实时保存 +- [ ] 刷新页面/重新进入可继续答题(断点续考) +- [ ] 倒计时准确,超时自动交卷 +- [ ] 交卷后立即显示成绩和排名 +- [ ] 答案解析正确显示 +- [ ] 多次考试取最高分显示 +- [ ] 🔴 定时任务每分钟执行,自动处理超时考试记录 +- [ ] 🔴 并发交卷场景下数据一致(乐观锁机制) +- [ ] 🔴 排名实时计算,仅统计已交卷学员 +- [ ] 🔴 排名规则:最高分优先,同分按首次达到时间排序 + +--- + +### 3.4 我的培训 + +#### 用户故事 +> 作为学员,我希望查看分配给我的培训计划,跟踪学习进度,完成培训任务。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 培训列表 | 展示分配给我的培训计划 | +| 培训详情 | 显示计划内容、学习进度 | +| 进度跟踪 | 实时显示知识学习、考试完成情况 | +| 完成判定 | 根据必修项判定培训是否完成 | +| 证书预留 | 培训完成后预留证书/徽章展示位置 | + +#### 3.4.1 培训列表页 + +**列表字段**: +| 字段 | 说明 | +|------|------| +| 培训名称 | 点击进入详情 | +| 培训周期 | 开始日期 ~ 结束日期 | +| 状态 | 未开始/进行中/已结束 | +| 我的进度 | 进度条 + 百分比 | +| 完成状态 | 未完成/已完成 | + +#### 3.4.2 培训详情页 + +**页面结构**: +``` +┌─────────────────────────────────────────────────────────┐ +│ 2026年Q1安全规范培训 │ +│ ───────────────────────────────────────────────────── │ +│ 培训周期:2026-01-01 ~ 2026-03-31 │ +│ 培训目标:掌握高速公路救援安全规范... │ +│ 我的进度:████████░░░░ 75% │ +├─────────────────────────────────────────────────────────┤ +│ 📚 学习内容 (3/5) │ +│ ├─ ✅ 高速救援SOP手册 [必修] [已完成] │ +│ ├─ ✅ 安全操作视频 [必修] [已完成] │ +│ ├─ 🔵 应急处理流程 [必修] [学习中 60%] │ +│ ├─ ⚪ 设备使用指南 [选修] [未学习] │ +│ └─ ✅ 案例分析 [选修] [已完成] │ +├─────────────────────────────────────────────────────────┤ +│ 📝 考试任务 (1/2) │ +│ ├─ ✅ 安全规范考核 [必考] [已通过 85分] │ +│ └─ ⚪ 操作技能测试 [必考] [未参加] │ +├─────────────────────────────────────────────────────────┤ +│ 🏆 完成奖励 │ +│ └─ 完成培训后可获得【安全规范认证】徽章(敬请期待) │ +└─────────────────────────────────────────────────────────┘ +``` + +**进度计算规则**: + +``` +培训进度 = (已完成必修知识数 + 已通过必考考试数) / (必修知识总数 + 必考考试总数) × 100% +``` + +**完成判定规则**: +| 条件 | 说明 | +|------|------| +| 所有必修知识已完成 | ✓ | +| 所有必考考试已通过 | ✓ | +| 选修知识 | 不影响完成判定 | +| 选考考试 | 不影响完成判定 | + +#### 验收标准 +- [ ] 只显示分配给当前学员的培训计划 +- [ ] 进度百分比计算准确(只计算必修项) +- [ ] 学习内容、考试任务分开展示 +- [ ] 显示每项的必修/选修、必考/选考标签 +- [ ] 点击知识内容可跳转到知识详情页 +- [ ] 点击考试任务可跳转到考试页面 +- [ ] 所有必修项完成后,培训状态变为"已完成" +- [ ] 证书/徽章区域预留(V1显示"敬请期待") + +--- + +### 3.5 个人中心 + +#### 用户故事 +> 作为学员,我希望查看自己的学习记录、考试历史和获得的证书。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 个人信息 | 显示姓名、部门、角色 | +| 学习统计 | 总学习时长、完成课程数 | +| 学习记录 | 知识学习历史列表 | +| 考试记录 | 考试历史列表 | +| 我的证书 | 证书/徽章展示(预留) | + +#### 验收标准 +- [ ] 正确显示当前用户信息 +- [ ] 学习时长统计准确(累计所有知识学习时长) +- [ ] 学习记录按时间倒序展示 +- [ ] 考试记录显示每次考试的详情 +- [ ] 证书模块预留(V1显示"暂无证书") + +--- + +## 四、数据契约补充 + +### 4.1 新增/修改实体 + +#### 知识学习进度表 (km_knowledge_progress) - 新增 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| user_id | Long | 学员ID | +| knowledge_id | Long | 知识ID | +| department_id | Long | **🟡 新增** 所属部门(冗余字段,便于按部门统计) | +| status | Enum | 状态:NOT_STARTED/IN_PROGRESS/COMPLETED | +| progress | Integer | 进度百分比(0-100) | +| duration | Long | 学习时长(秒) | +| video_position | Long | 视频播放位置(秒),仅视频类型 | +| source | Enum | **🔴 新增** 学习来源:FREE/TRAINING | +| plan_id | Long | **🔴 新增** 关联培训计划ID(来源为TRAINING时有值) | +| start_time | DateTime | 首次学习时间 | +| complete_time | DateTime | 完成时间 | +| update_time | DateTime | 最后更新时间 | + +#### 考试记录表 (ex_exam_record) - 补充字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| status | Enum | **新增** 状态:IN_PROGRESS/SUBMITTED | +| last_save_time | DateTime | **新增** 最后保存时间(断点续考用) | +| version | Integer | **🔴 新增** 乐观锁版本号 | +| submit_source | Enum | **🔴 新增** 交卷来源:USER/SYSTEM_TIMEOUT | + +### 4.2 新增枚举 + +```java +// 学习状态 +public enum LearningStatus { + NOT_STARTED, // 未学习 + IN_PROGRESS, // 学习中 + COMPLETED // 已完成 +} + +// 🔴 学习来源 +public enum LearningSource { + FREE, // 自由学习(从知识库直接进入) + TRAINING // 培训任务学习(从培训计划进入) +} + +// 考试记录状态 +public enum ExamRecordStatus { + IN_PROGRESS, // 考试中(用于断点续考) + SUBMITTED // 已提交 +} + +// 🔴 交卷来源 +public enum SubmitSource { + USER, // 用户主动交卷 + SYSTEM_TIMEOUT // 系统超时自动交卷 +} +``` + +--- + +## 五、接口预留(V2/V3功能) + +### 5.1 证书/徽章接口(预留) + +``` +POST /api/certificate/generate # 生成证书 +GET /api/certificate/my # 我的证书列表 +GET /api/certificate/{id} # 证书详情 +GET /api/badge/my # 我的徽章列表 +``` + +### 5.2 错题本接口(V2预留) + +``` +GET /api/wrong-questions # 错题列表 +POST /api/wrong-questions/review # 标记已复习 +``` + +--- + +## 六、非功能需求 + +| 项目 | 要求 | 测试指标(🟡 优化项5) | +|------|------|------------------------| +| 页面加载 | 页面加载 < 2秒 | 95%请求 < 2秒 | +| 考试提交 | 考试提交 < 1秒 | 99%请求 < 1秒 | +| 并发支持 | 支持100人同时在线考试 | 100并发答题,响应时间 < 500ms,成功率 > 99.9% | +| 答案保存 | 答案自动保存 | 保存成功率 > 99.99%,保存延迟 < 300ms | +| 数据安全 | 考试答案定时自动保存,防止数据丢失 | 断网重连后数据无丢失 | +| 兼容性 | 支持主流浏览器 | Chrome 90+、Edge 90+、Firefox 90+ | + +--- + +## 七、不做什么(Out of Scope) + +| 功能 | 原因 | +|------|------| +| 移动端适配 | V1仅支持PC浏览器 | +| 防作弊功能 | 产品确认不需要 | +| 学习日历 | 产品确认不需要 | +| 错题本 | V2规划 | +| 社交功能(评论、点赞) | V3规划 | + +--- + +## 八、页面清单(🟡 优化项3) + +> **说明**:采用与管理端复用同一套系统的方案,按角色控制菜单和功能显示,不使用独立的 `/student/` 路径前缀。 + +| 页面 | 路径 | 角色可见 | 说明 | +|------|------|----------|------| +| 工作台 | /index.html | 全角色 | 根据角色显示不同内容 | +| 知识列表 | /knowledge/list.html | 全角色 | 学员只读,讲师可管理 | +| 知识详情 | /knowledge/view.html?id={id} | 全角色 | 学员增加学习进度记录 | +| 考试列表 | /exam/my-exams.html | 学员 | 我的考试列表 | +| 考试详情 | /exam/detail.html?id={id} | 学员 | 考试信息页 | +| 在线答题 | /exam/answer.html?id={id} | 学员 | 答题页面 | +| 考试结果 | /exam/result.html?recordId={id} | 学员 | 成绩结果页 | +| 培训列表 | /training/my-training.html | 学员 | 我的培训列表 | +| 培训详情 | /training/detail.html?id={id} | 学员 | 培训计划详情 | +| 个人中心 | /profile/index.html | 全角色 | 个人信息页 | +| 学习记录 | /profile/learning.html | 学员 | 学习记录列表 | +| 考试记录 | /profile/exam-history.html | 学员 | 考试记录列表 | +| 我的证书 | /profile/certificate.html | 学员 | 证书展示(预留) | + +**菜单权限控制**: +| 菜单项 | 管理员 | 讲师 | 学员 | +|--------|--------|------|------| +| 工作台 | ✅ | ✅ | ✅ | +| 知识库 | ✅ 管理 | ✅ 管理 | ✅ 只读学习 | +| 考题管理 | ✅ | ✅ | ❌ | +| 试卷管理 | ✅ | ✅ | ❌ | +| 考试管理 | ✅ | ✅ | ❌ | +| 我的考试 | ❌ | ❌ | ✅ | +| 培训计划 | ✅ | ✅ | ❌ | +| 我的培训 | ❌ | ❌ | ✅ | +| 个人中心 | ✅ | ✅ | ✅ | +| 系统设置 | ✅ | ❌ | ❌ | + +--- + +## 九、修订项对照表 + +| 修订项 | 类型 | 章节位置 | 状态 | +|--------|------|----------|------| +| 知识学习入口场景 | 🔴 必须修正 | 3.2.3 | ✅ 已补充 | +| 断点续考超时机制 | 🔴 必须修正 | 3.3.5 | ✅ 已补充 | +| 排名计算规则 | 🔴 必须修正 | 3.3.7 | ✅ 已补充 | +| 学习时长防刷机制 | 🟡 建议优化 | 3.2.4 | ✅ 已采纳 | +| 数据表department_id | 🟡 建议优化 | 4.1 | ✅ 已采纳 | +| 页面路径设计 | 🟡 建议优化 | 8 | ✅ 已调整 | +| 答案解析显示策略 | 🟡 建议优化 | 3.3.6 | ✅ 已明确 | +| 非功能需求测试指标 | 🟡 建议优化 | 6 | ✅ 已补充 | + +--- + +**文档状态:✅ 已批准** + +> **Supervisor 复审结论**:通过 +> **复审日期**:2026-01-15 +> **下一阶段**:技术评审 → 开发 diff --git a/training/codes/training-system/context/02-产品架构/01-产品整体架构.md b/training/codes/training-system/context/02-产品架构/01-产品整体架构.md new file mode 100644 index 0000000..b853e47 --- /dev/null +++ b/training/codes/training-system/context/02-产品架构/01-产品整体架构.md @@ -0,0 +1,379 @@ +# 产品整体架构文档 — 道路救援企业培训系统 + +> 版本:V1.0 +> 生成日期:2026-04-07 +> 证据状态:✅ 已验证 +> 证据来源:[SRC-FEAT-01] [SRC-SQL-01] [SRC-API-01] [SRC-CODE-01] + +--- + +## 1. 产品定位 + +### 1.1 产品目标 + +为道路救援企业提供**一站式内部培训管理平台**,通过数字化方式替代口口相传的知识传承模式,实现知识沉淀、在线考核、培训过程管控三位一体。[SRC-FEAT-01] + +### 1.2 核心价值 + +| 价值维度 | 解决的痛点 | 实现方式 | +|---------|---------|---------| +| 知识沉淀 | 知识依赖口口相传,无法沉淀 | 结构化知识库(文档+视频) | +| 高效考核 | 出题组卷效率低,纸质考试成本高 | 在线题库 + 自动/手动组卷 + 在线考试 | +| 培训管控 | 培训效果难追踪,进度不透明 | 培训计划+进度跟踪+成绩统计 | +| 标准化输出 | 服务标准参差不齐 | 统一平台 + 及格线控制 + 必考机制 | + +### 1.3 目标用户 + +| 角色 | 人数 | 核心诉求 | +|------|------|---------| +| 管理员 | 3-5人 | 系统配置、人员管理、全平台数据可见 | +| 讲师(技师) | 30-50人 | 本部门知识上传、出题、发布考试、管理培训计划 | +| 学员 | ~450人 | 学习资料、参加考试、查看进度 | + +[SRC-FEAT-01:二、用户画像] + +### 1.4 使用场景 + +- **管理员场景**:系统初始化时维护组织架构;日常监控各部门培训完成情况。 +- **讲师场景**:救援规范更新后,上传新手册并更新题库,发布新考试,关联到培训计划。 +- **学员场景**:PC端浏览知识库预习、参加指定考试、查看自己的培训进度。 +- **企业微信场景**:学员通过企业微信 H5 入口完成移动端学习和考试。 + +--- + +## 2. 系统整体架构 + +### 2.1 架构分层 + +| 层次 | 组件 | 职责 | +|-----|------|------| +| **表现层** | PC浏览器(管理员/讲师)、企业微信H5(学员)、移动端浏览器 | 用户交互,HTML+CSS+JS 原生技术栈 | +| **网关层** | Spring Boot 应用(JWT认证、权限拦截、日志、异常处理) | 请求入口,统一认证鉴权,全局异常处理 | +| **业务层** | 5个业务模块(auth / system / knowledge / exam / training) | 核心业务逻辑,按模块边界划分,禁止跨模块直接调用数据层 | +| **数据层** | MySQL 8.0(MyBatis Plus ORM)+ 文件存储(本地/MinIO/OSS) | 持久化业务数据和媒体文件 | + +[SRC-FEAT-01:七、架构设计蓝图] + +### 2.2 技术架构概览 + +| 技术选型 | 版本 | 用途 | +|---------|------|------| +| Spring Boot | 3.1.2 | 主框架 | +| MyBatis Plus | 3.5.3+ | ORM,简化CRUD | +| MySQL | 8.0 | 主数据库 | +| java-jwt | 4.4+ | JWT Token 签发与验证 | +| Spring Security | 6.x | BCrypt 密码加密 | +| weixin-java-cp | - | 企业微信 SDK(OAuth 登录) | +| Springdoc OpenAPI | 2.2+ | API 文档 | +| Spring @Scheduled | - | 考试超时自动交卷定时任务 | +| 文件存储 | - | V1 本地存储,V2 可扩展 MinIO/OSS | + +[SRC-FEAT-01:7.3 技术选型] + +### 2.3 系统依赖关系 + +``` +外部系统依赖: + 企业微信API ←→ auth 模块(OAuth 换 userid) + 文件存储服务 ←→ knowledge 模块(上传/下载文件) + 文档预览服务(可选) ←→ knowledge 模块(在线预览) + +内部模块依赖方向(单向,禁止逆向调用): + training → knowledge(引用知识内容、学习进度) + training → exam(引用考试通过状态) + knowledge / exam / training → system(查询用户/部门信息) + 所有模块 → auth(权限校验拦截器) +``` + +--- + +## 3. 业务架构图 + +```mermaid +graph TD + subgraph 客户端 + PC[PC浏览器
管理员/讲师] + WX[企业微信H5
学员] + MB[移动端浏览器
学员] + end + + subgraph 后端服务 + subgraph 网关层 + GW[JWT认证 / 权限拦截 / 异常处理] + end + + subgraph 业务域 + AUTH[认证模块
auth] + SYS[系统管理
system] + KM[知识库模块
knowledge] + EX[考试模块
exam] + TR[培训模块
training] + end + end + + subgraph 数据层 + DB[(MySQL 8.0)] + FS[文件存储
本地/MinIO] + end + + subgraph 外部服务 + WXAPI[企业微信API] + PREVIEW[文档预览服务
可选] + end + + PC --> GW + WX --> GW + MB --> GW + GW --> AUTH + GW --> SYS + GW --> KM + GW --> EX + GW --> TR + + AUTH --> WXAPI + KM --> FS + KM --> PREVIEW + + TR --> KM + TR --> EX + KM --> SYS + EX --> SYS + TR --> SYS + + AUTH --> DB + SYS --> DB + KM --> DB + EX --> DB + TR --> DB +``` + +--- + +## 4. 核心业务流程说明 + +### 4.1 用户主流程(学员) + +```mermaid +sequenceDiagram + participant 学员 + participant 前端 + participant 后端 + participant 企业微信 + + 学员->>前端: 点击企业微信登录 + 前端->>企业微信: 跳转 OAuth 授权 + 企业微信-->>前端: 返回 code + 前端->>后端: code 换 Token + 后端->>企业微信: code → userid + 后端->>后端: 查 sys_user 匹配 + alt 用户已录入 + 后端-->>前端: JWT Token + 用户信息 + 前端->>学员: 进入工作台 + else 用户未录入 + 后端-->>前端: 错误:联系管理员 + end + + 学员->>前端: 查看培训计划 + 前端->>后端: GET /api/student/training + 后端-->>前端: 计划列表 + 进度 + + 学员->>前端: 参加考试 + 前端->>后端: POST /api/student/exam/{id}/start + 后端->>后端: 校验时间窗口 + 剩余次数 + 后端-->>前端: 试题 + 考试时长 + 学员->>前端: 答题 + 交卷 + 前端->>后端: POST /api/student/exam/{id}/submit + 后端->>后端: 自动判分 + 后端-->>前端: 成绩 + 排名 + 答案解析 +``` + +### 4.2 管理流程(讲师) + +```mermaid +flowchart LR + A[上传知识
DRAFT] --> B[发布知识
PUBLISHED] + B --> C{有培训计划?} + C -->|是| D[关联到培训计划] + C -->|否| E[学员自由学习] + + F[创建题目
DRAFT] --> G[发布题目
PUBLISHED] + G --> H[组卷
手动/自动] + H --> I[发布试卷
PUBLISHED] + I --> J[创建考试
设置时间/次数] + J --> K[指定考试对象
部门/小组/个人] + D --> L[创建培训计划] + J --> L + L --> M[分配学员
部门/小组/个人] + M --> N[发布培训计划] +``` + +### 4.3 数据流转流程 + +```mermaid +flowchart TB + subgraph 内容生产 + KM_CREATE[知识创建
km_knowledge DRAFT] + KM_PUBLISH[发布知识
PUBLISHED] + QS_CREATE[题目创建
ex_question DRAFT] + QS_PUBLISH[发布题目
PUBLISHED] + PP_CREATE[组卷
ex_paper + ex_paper_question] + EX_CREATE[创建考试
ex_exam + ex_exam_target] + TR_CREATE[培训计划
tr_plan + tr_plan_knowledge
+ tr_plan_exam + tr_plan_target] + end + + subgraph 学员消费 + LEARN[学习知识
→ km_knowledge_progress UPSERT] + EXAM[参加考试
→ ex_exam_record 创建/更新] + PROGRESS[进度计算
必修知识完成数 + 必考通过数] + end + + KM_CREATE --> KM_PUBLISH --> LEARN + QS_CREATE --> QS_PUBLISH --> PP_CREATE --> EX_CREATE --> EXAM + TR_CREATE --> LEARN + TR_CREATE --> EXAM + LEARN --> PROGRESS + EXAM --> PROGRESS +``` + +--- + +## 5. 数据架构 + +### 5.1 核心数据实体(共19张表) + +[SRC-SQL-01] [SRC-SQL-02] + +| 数据域 | 表名 | 核心字段 | 说明 | +|-------|------|---------|------| +| **系统** | sys_center | id, name | 中心(最高层级) | +| **系统** | sys_department | id, name, center_id | 部门(隶属中心) | +| **系统** | sys_group | id, name, department_id | 小组(隶属部门) | +| **系统** | sys_user | id, wx_userid, role(0/1/2), department_id, group_id, status | 用户(ADMIN/LECTURER/STUDENT) | +| **知识库** | km_category | id, name, parent_id, department_id | 知识分类(多级树) | +| **知识库** | km_knowledge | id, title, type(0文档/1视频), file_url, department_id, status(0/1/2) | 知识内容 | +| **知识库** | km_knowledge_progress | id, user_id, knowledge_id, status, progress, duration, video_position, source, plan_id | 学习进度 | +| **考试** | ex_question_category | id, name, parent_id, department_id | 题目分类 | +| **考试** | ex_question | id, type(0/1/2), content, options(JSON), answer, analysis, status | 题目 | +| **考试** | ex_paper | id, title, total_score, duration, pass_score, status | 试卷 | +| **考试** | ex_paper_question | paper_id, question_id, score, sort_order | 试卷-题目关联 | +| **考试** | ex_exam | id, title, paper_id, start_time, end_time, max_attempts, status(0/1/2) | 考试 | +| **考试** | ex_exam_target | exam_id, target_type(0部门/1小组/2个人), target_id | 考试对象 | +| **考试** | ex_exam_record | id, exam_id, user_id, attempt_no, score, passed, answers(JSON), status, version | 考试记录(含乐观锁) | +| **培训** | tr_plan | id, title, start_date, end_date, department_id, status(0/1/2) | 培训计划 | +| **培训** | tr_plan_knowledge | plan_id, knowledge_id, required, sort_order | 计划-知识关联 | +| **培训** | tr_plan_exam | plan_id, exam_id, required, sort_order | 计划-考试关联(V1.0.1) | +| **培训** | tr_plan_target | plan_id, target_type, target_id | 计划-对象 | +| **培训** | tr_plan_progress | plan_id, user_id, knowledge_id, completed, complete_time | 旧版进度(已被 km_knowledge_progress 替代)| + +### 5.2 实体关系图 + +```mermaid +erDiagram + sys_center ||--o{ sys_department : "中心下辖多个部门" + sys_department ||--o{ sys_group : "部门下辖多个小组" + sys_department ||--o{ sys_user : "用户属于部门" + sys_group ||--o{ sys_user : "用户可归属小组" + + sys_department ||--o{ km_category : "按部门隔离" + km_category ||--o{ km_knowledge : "知识归属分类" + sys_user ||--o{ km_knowledge_progress : "学员学习进度" + km_knowledge ||--o{ km_knowledge_progress : "知识对应进度" + + sys_department ||--o{ ex_question_category : "按部门隔离" + ex_question_category ||--o{ ex_question : "题目归属分类" + ex_paper ||--o{ ex_paper_question : "试卷包含多道题" + ex_question ||--o{ ex_paper_question : "题目被多张试卷引用" + ex_paper ||--|| ex_exam : "考试使用某试卷" + ex_exam ||--o{ ex_exam_target : "考试指定对象" + ex_exam ||--o{ ex_exam_record : "考试产生记录" + sys_user ||--o{ ex_exam_record : "学员的考试记录" + + tr_plan ||--o{ tr_plan_knowledge : "计划包含多个知识" + tr_plan ||--o{ tr_plan_exam : "计划关联多场考试" + tr_plan ||--o{ tr_plan_target : "计划分配给对象" + km_knowledge ||--o{ tr_plan_knowledge : "知识被计划引用" + ex_exam ||--o{ tr_plan_exam : "考试被计划引用" +``` + +### 5.3 数据生命周期 + +| 数据类型 | 创建 | 有效态 | 失效态 | 删除方式 | +|---------|------|-------|-------|---------| +| 知识/题目/试卷 | DRAFT | PUBLISHED | OFFLINE | 逻辑删除(deleted=1) | +| 考试 | NOT_STARTED | IN_PROGRESS | ENDED | 逻辑删除 | +| 培训计划 | NOT_STARTED | IN_PROGRESS | ENDED | 逻辑删除 | +| 考试记录 | IN_PROGRESS | SUBMITTED | 永久保留(取最高分) | 不删除 | +| 学习进度 | NOT_STARTED | IN_PROGRESS | COMPLETED | UPSERT更新 | + +--- + +## 6. 权限与角色体系 + +### 6.1 角色定义 + +[SRC-FEAT-01:4.1 权限规则] [SRC-SQL-01:sys_user.role] + +| 角色 | 枚举值(DB) | 数据范围 | +|------|------------|---------| +| 管理员 ADMIN | role=0 | 全平台(无部门隔离) | +| 讲师 LECTURER | role=1 | 本部门(department_id匹配) | +| 学员 STUDENT | role=2 | 本部门(只读;自己的进度/成绩) | + +### 6.2 权限分层(按功能菜单) + +| 菜单/功能 | ADMIN | LECTURER | STUDENT | +|---------|-------|---------|---------| +| 组织架构管理 | ✅ 全部 | ❌ | ❌ | +| 员工管理 | ✅ 全部 | 👁️ 本部门只读 | ❌ | +| 知识库-分类管理 | ✅ 全部 | ✅ 本部门 | ❌ | +| 知识库-知识列表 | ✅ 全部 | ✅ 本部门(含草稿) | 👁️ 本部门已发布 | +| 考题管理-题库分类 | ✅ | ✅ 本部门 | ❌ | +| 考题管理-题目列表 | ✅ | ✅ 本部门 | ❌ | +| 试卷管理 | ✅ | ✅ 本部门 | ❌ | +| 考试管理 | ✅ | ✅ 本部门发布 | 👁️ 我的考试 | +| 培训计划 | ✅ | ✅ 本部门创建/管理 | 👁️ 我的培训 | +| 系统设置 | ✅ | ❌ | ❌ | +| 工作台 | ✅ | ✅ | ✅(学员视图) | + +[SRC-FEAT-01:8.1 角色菜单权限对照表] + +### 6.3 控制逻辑 + +**认证层**:JWT Token 携带 userId + role,每次请求由 AuthInterceptor 解析验证。[SRC-CODE-01:common/interceptor/AuthInterceptor.java] + +**授权层**: +- 接口级:`@PreAuthorize` / SecurityConfig 按角色匹配路径(`/api/student/**` 仅 STUDENT) +- 数据级:Service 层注入 `department_id` 过滤,讲师/学员查询一律追加 `WHERE department_id = ?` + +**隔离规则**: +- 4类数据(知识库、题库、试卷/考试、培训计划)均按 `department_id` 隔离 +- 管理员不注入部门过滤,讲师注入本部门,学员额外限制只查 PUBLISHED 状态 + +--- + +## 7. 系统扩展点分析 + +### 7.1 可扩展模块 + +| 扩展点 | 当前状态 | V2+ 扩展方向 | +|-------|---------|------------| +| 文件存储 | 本地文件系统 | 抽象 `FileStorageService` 接口 → MinIO/OSS 实现切换 | +| 认证方式 | 企业微信 OAuth(账号密码作备选) | 可接入 SSO/LDAP | +| 定时任务 | Spring @Scheduled(单机) | 可替换为 XXL-Job/SchedulerX 支持集群 | +| 统计报表 | 无(V2规划) | 新增 `statistics` 模块,不影响现有模块 | +| 通知 | 无(V2规划) | 可插入企业微信消息推送 | + +### 7.2 可插拔能力 + +- **文档预览**:kkFileView / OnlyOffice 作为可选外部服务,knowledge 模块通过 URL 调用,可随时替换 +- **视频播放器**:video.js 为前端组件,与后端解耦,可替换 +- **批量导入**:EasyExcel 依赖已选型,可随时为题目/用户管理添加 Excel 导入功能 + +### 7.3 易变业务点 + +| 易变点 | 变化方向 | 影响范围 | +|-------|---------|---------| +| 培训完成判定逻辑(当前:必修知识+必考通过) | V2 可能增加签到、线下课时 | `TrainingProgressService.calculateProgress()` | +| 知识学习完成判定(视频90%/文档时长) | 可能改为100%或按章节 | `KnowledgeLearningService.isCompleted()` | +| 考试排名计算口径(当前:最高分排名 DENSE_RANK) | 可能改为平均分/最后一次 | SQL窗口函数,需同步修改 | +| 组织架构层级(当前:中心→部门→小组三级) | 可能增减层级 | system 模块全面影响 | diff --git a/training/codes/training-system/context/02-产品架构/03-系统能力模型.md b/training/codes/training-system/context/02-产品架构/03-系统能力模型.md new file mode 100644 index 0000000..552c155 --- /dev/null +++ b/training/codes/training-system/context/02-产品架构/03-系统能力模型.md @@ -0,0 +1,105 @@ +# 系统能力模型总结 — 道路救援企业培训系统 + +> 版本:V1.0 | 生成日期:2026-04-07 +> 证据来源:[SRC-FEAT-01] [SRC-API-01] [SRC-CODE-01] + +--- + +## 1. 当前系统具备的核心能力 + +| # | 能力名称 | 一句话描述 | +|---|---------|----------| +| C1 | **基于角色的访问控制(RBAC)** | 三种角色(管理员/讲师/学员)+ 部门级数据隔离,统一鉴权入口 | +| C2 | **组织与人员管理** | 三级组织结构(中心→部门→小组)+ 用户生命周期管理 | +| C3 | **知识资产管理** | 支持文档/视频两种类型的知识内容创建、状态发布和多级分类 | +| C4 | **学习行为跟踪** | 防刷机制下的真实时长累计和进度记录(视频90%/文档时长判定) | +| C5 | **在线考核** | 题库管理 + 手动/自动组卷 + 在线答题 + 客观题自动判分 | +| C6 | **考试行为管控** | 时间窗口约束 + 次数限制 + 超时自动交卷 + 并发乐观锁保护 | +| C7 | **培训任务编排** | 将知识+考试组织成有时间周期的培训计划,支持必修/选修/必考/选考四维属性 | +| C8 | **培训进度可视化** | 实时计算学员对某培训计划的完成百分比(必修完成+必考通过) | +| C9 | **成绩与排名** | 交卷后立即返回成绩+答案解析+实时排名(窗口函数,同分按时间早优先) | +| C10 | **企业微信身份集成** | OAuth 登录打通企业微信用户体系,无需单独注册 | + +--- + +## 2. 能力依赖关系图 + +```mermaid +graph TD + C10[C10 企业微信身份集成] --> C1 + C1[C1 RBAC + 部门隔离] --> C2 + C1 --> C3 + C1 --> C5 + C1 --> C7 + + C2[C2 组织人员管理] --> C7 + C3[C3 知识资产管理] --> C4 + C3 --> C7 + + C4[C4 学习行为跟踪] --> C8 + C5[C5 在线考核] --> C6 + C5 --> C9 + C6[C6 考试行为管控] --> C9 + C9[C9 成绩与排名] --> C8 + + C7[C7 培训任务编排] --> C4 + C7 --> C6 + C7 --> C8[C8 培训进度可视化] +``` + +--- + +## 3. 可复用能力清单 + +| 能力 | 复用场景 | 复用方式 | +|-----|---------|---------| +| C1 RBAC + 部门隔离 | 企业内任何需要角色权限 + 数据隔离的系统 | 提取 AuthInterceptor + UserContext + department_id 注入框架 | +| C4 学习行为跟踪 | 其他类型内容(音频/直播回放/课件)的学习进度记录 | 抽象 `LearningTarget` 接口,替换完成判定策略(Strategy 模式) | +| C5 在线考核 | 任意客观题在线测评场景(安全培训/合规考试等)| 题库+试卷+客观题判分为独立子系统,可独立部署 | +| C6 考试行为管控 | 任何有"限时+限次"约束的在线测试 | ExamTakingService 中时间窗口/次数/乐观锁的组合控制逻辑 | +| C9 成绩排名 | 任何需要实时排名的比赛/评测场景 | SQL 窗口函数 DENSE_RANK 模式,按需修改排序条件 | + +--- + +## 4. 平台级能力(多业务依赖的底层能力) + +**C1 — RBAC + 部门数据隔离** +- 所有业务模块(知识/考试/培训)均依赖此能力实现权限控制和数据隔离 +- 是系统安全性和多租户(多部门)能力的基础 +- 扩展方向:目前硬编码三个角色,V2 可改为动态角色配置 + +**C2 — 组织人员管理** +- 每个业务操作都需要 `department_id`、`user_id`、`role` 三个属性 +- 是知识/考试/培训三大域的数据隔离依据来源 +- 扩展方向:当前三级固定结构,可抽象为 n 级通用树 + +**C10 — 企业微信身份集成** +- 是唯一的用户来源(V1),后续接其他身份源也需通过此层 +- 扩展方向:抽象为 `IdentityProvider` 接口,支持插入 SSO/LDAP + +--- + +## 5. 业务定制能力(特定场景才需要的能力) + +| 能力 | 定制场景 | 为何不是平台级 | +|-----|---------|-------------| +| C3 知识资产管理(文档/视频类型) | 救援行业的操作手册、安全规范、视频讲解 | 内容类型固定(文档/视频),换行业可能需要不同类型 | +| C4 学习行为防刷(视频90%/文档时长) | 强制"真实学习"的企业培训场景 | 完成判定规则是业务决策,C端平台通常不这么严格 | +| C6 超时自动交卷 + 乐观锁 | 有严格时长限制的考试 | 休闲测试/知识问答类应用不需要这个复杂度 | +| C7 培训任务编排(必修/选修/必考/选考四维)| 企业有组织要求的培训管理 | 自学平台不需要"强制必修"的约束 | +| C9 成绩排名(DENSE_RANK + 同分按时间) | 部门内部考核的绩效排名 | 无竞争性考核场景(如自我测评)不需要排名 | + +--- + +## 6. V2 能力规划缺口 + +当前 V1.0.1 尚未具备以下能力,是 V2 的扩展方向: + +| 缺失能力 | 业务价值 | 实现复杂度 | +|---------|---------|---------| +| 统计报表中心(完成率/通过率/部门排名) | 管理层查看全局培训效果 | 中(新增 statistics 模块,读现有数据) | +| 线下培训签到(扫码签到/签退) | 线上线下混合培训场景 | 中(需硬件支持) | +| 错题本(自动归集错误题目) | 学员针对性复习 | 低(从 exam_record.answers 中提取 correct=false) | +| 知识全文搜索 | 快速查找内容 | 高(需引入 Elasticsearch 或 MySQL FULLTEXT) | +| 奖励体系(积分/勋章/排行榜) | 提升学习积极性 | 中(新增独立模块) | +| 证书管理 | 培训完成凭证 | 低(基于培训计划完成状态生成 PDF) | diff --git a/training/codes/training-system/context/03-功能模块/01-认证模块.md b/training/codes/training-system/context/03-功能模块/01-认证模块.md new file mode 100644 index 0000000..3ca81b4 --- /dev/null +++ b/training/codes/training-system/context/03-功能模块/01-认证模块.md @@ -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 有效 | diff --git a/training/codes/training-system/context/03-功能模块/02-系统管理模块.md b/training/codes/training-system/context/03-功能模块/02-系统管理模块.md new file mode 100644 index 0000000..a45ea41 --- /dev/null +++ b/training/codes/training-system/context/03-功能模块/02-系统管理模块.md @@ -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 | - | +| 员工录入 | 管理员手动录入员工信息,绑定企业微信 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 | 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 为冗余字段不随之更新 | diff --git a/training/codes/training-system/context/03-功能模块/03-知识库模块.md b/training/codes/training-system/context/03-功能模块/03-知识库模块.md new file mode 100644 index 0000000..d9a7d16 --- /dev/null +++ b/training/codes/training-system/context/03-功能模块/03-知识库模块.md @@ -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(树) | - | +| 知识内容 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 | - | +| 知识列表查询(学员端) | 只查 PUBLISHED 状态,携带个人学习状态 | department_id, KnowledgeQueryDTO | PageResult | auth | +| 知识在线预览 | 文档在线预览(PDF/Word等)、视频在线播放 | knowledgeId | 文件 URL / 预览链接 | 文件存储/文档预览服务 | +| 开始学习 | 创建或获取学习进度记录,记录首次学习时间 | knowledgeId, [source, planId] | - | auth | +| 更新学习进度 | 定时上报学习进度(每60秒),累加时长,判断完成 | ProgressUpdateDTO(duration, progress, videoPosition, source, planId) | - | training(若来源=TRAINING) | +| 完成学习 | 前端显式标记完成(文档)| knowledgeId | - | - | +| 查询学习进度 | 查询学员对某知识/某批知识的学习状态 | userId/knowledgeIds | List | - | +| 引用保护检查 | 检查知识是否被培训计划引用,下架前警告 | 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 请先移除分类下的知识 | diff --git a/training/codes/training-system/context/03-功能模块/04-考试模块.md b/training/codes/training-system/context/03-功能模块/04-考试模块.md new file mode 100644 index 0000000..fe174f5 --- /dev/null +++ b/training/codes/training-system/context/03-功能模块/04-考试模块.md @@ -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 | 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 记录恢复,重新进入走断点续考流程 | diff --git a/training/codes/training-system/context/03-功能模块/05-培训计划模块.md b/training/codes/training-system/context/03-功能模块/05-培训计划模块.md new file mode 100644 index 0000000..23ed921 --- /dev/null +++ b/training/codes/training-system/context/03-功能模块/05-培训计划模块.md @@ -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 | - | +| 培训计划详情(讲师端) | 查看计划详情,含关联知识/考试/对象/进度汇总 | planId | TrainingPlanDetailVO | - | +| 更新/删除培训计划 | 修改计划信息(未开始时可修改,进行中时受限)| planId, TrainingPlanQueryDTO | - | - | +| 学员-我的培训列表 | 查询分配给当前学员的培训计划(进行中优先) | - | PageResult | 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] | diff --git a/training/codes/training-system/docs/INDEX.md b/training/codes/training-system/docs/INDEX.md new file mode 100644 index 0000000..800b861 --- /dev/null +++ b/training/codes/training-system/docs/INDEX.md @@ -0,0 +1,191 @@ +# 道路救援企业培训系统 - 文档索引 + +> 最后更新:2026-01-28 +> 本文件是 AI 助手的入口文档,用于快速定位项目文档 + +--- + +## 一、文档结构 + +``` +docs/ +├── INDEX.md # 👈 当前文件(文档索引) +├── LLR.md # 低层需求文档 +├── features/ # 功能需求文档 +│ ├── PRD.md # 产品需求文档(主文档) +│ └── student.md # 学员端需求 ✅ 已完成 +├── architecture/ # 技术设计文档 +│ ├── student-technical-design.md # 学员端技术设计 ✅ 已完成 +│ └── fix-knowledge-file-preview.md # 知识库文件预览修复方案 +├── sql/ # 数据库脚本 +│ ├── init.sql # 数据库初始化脚本(基础表) +│ └── student-module.sql # 学员端模块增量脚本 ✅ 已完成 +├── Prompt/ # AI提示词模板 +│ ├── Codeing_Prompt.md # 编码提示词 +│ ├── Tester_Prompt.md # 测试提示词 +│ └── PM_Prompt.md # 产品经理提示词 +├── test-reports/ # 测试报告 +│ ├── TestPlan.md # 测试计划 +│ ├── TestPlan_ExamModule.md # 考试模块测试计划 +│ ├── TestReport_Summary.md # 测试报告汇总 +│ ├── TestReport_1.3_UserManagement.md # 用户管理测试报告 +│ ├── TestReport_2.0_Knowledge.md # 知识库测试报告 +│ ├── TestReport_3.0_Question.md # 题库测试报告 +│ ├── TestReport_4.0_Paper.md # 试卷测试报告 +│ ├── TestReport_5.0_Exam.md # 考试测试报告 +│ ├── TestReport_6.0_TrainingPlan.md # 培训计划测试报告 +│ ├── TestReport_TrainingModule.md # 培训模块测试报告 +│ ├── TestReport_ExamModule_Regression.md # 考试模块回归测试报告 +│ ├── RegressionReport_1.3_UserManagement.md # 用户管理回归报告 +│ ├── RegressionReport_1.3_UserManagement_V2.md # 用户管理回归报告V2 +│ ├── BugAnalysis_2.0_Knowledge.md # 知识库Bug分析 +│ ├── QA_Report_Knowledge_FileView.md # 知识库文件预览QA报告 +│ ├── UI-Review-Report.md # UI审查报告 +│ ├── Integration-Test-Report.md # 集成测试报告 +│ ├── 测试报告-登录模块.md # 登录模块测试报告 +│ └── 测试报告-题库分类模块.md # 题库分类模块测试报告 +├── review-reports/ # 评审报告 +│ └── TestPlan_TrainingModule.md # 培训模块测试计划评审 +└── test-screenshots/ # 测试截图 + ├── 01-student-dashboard.png # 学员工作台截图 + ├── 02-knowledge-list.png # 知识库列表截图 + ├── 04-my-exams-list.png # 我的考试列表截图 + ├── 05-exam-taking-error.png # 考试错误截图 + └── 06-my-training.png # 我的培训截图 + +``` + +--- + +## 二、文档职责划分 + +| 文档 | 职责 | 包含内容 | +|------|------|----------| +| **features/PRD.md** | 产品全局视角 | 核心目标、用户画像、版本规划、业务规则、数据契约、系统架构、技术选型 | +| **features/student.md** | 学员端详细需求 | 学员功能清单、页面原型、验收标准、学员专属数据表 | +| **LLR.md** | 低层需求 | 详细的技术实现需求 | +| **architecture/** | 技术设计文档 | 技术架构、模块设计、修复方案 | +| **test-reports/** | 测试文档 | 测试计划、测试报告、回归报告、Bug分析 | +| **Prompt/** | AI提示词 | 编码、测试、产品经理提示词模板 | + +--- + +## 三、文档版本状态 + +| 文档 | 版本 | 状态 | 更新日期 | +|------|------|------|----------| +| features/PRD.md | V1.0.1 | ✅ 已确认 | 2026-01-13 | +| features/student.md | V1.0.1 | ✅ 已批准 | 2026-01-15 | +| architecture/student-technical-design.md | V1.0.0 | ✅ 已批准 | 2026-01-15 | +| architecture/fix-knowledge-file-preview.md | V1.0.0 | ✅ 已完成 | 2026-01-28 | +| sql/student-module.sql | V1.0.0 | ✅ 已完成 | 2026-01-15 | +| test-reports/TestReport_Summary.md | - | ✅ 已完成 | 2026-01-28 | + +--- + +## 四、快速导航 + +### 按角色查找 +- **学员相关** → [features/student.md](features/student.md) + +### 按主题查找 +| 主题 | 文档位置 | +|------|----------| +| 系统架构 | features/PRD.md > 七、架构设计蓝图 | +| 数据库设计 | features/PRD.md > 五、数据契约 | +| 权限规则 | features/PRD.md > 4.1 权限规则 | +| 内容状态流转 | features/PRD.md > 4.2 内容状态流转 | +| 考试规则 | features/PRD.md > 4.3 考试规则 | +| 数据隔离规则 | features/PRD.md > 4.4 数据隔离规则 | +| 学员工作台 | features/student.md > 3.1 | +| 知识学习 | features/student.md > 3.2 | +| 在线考试 | features/student.md > 3.3 | +| 培训计划 | features/student.md > 3.4 | +| 学员端API设计 | architecture/student-technical-design.md > 四 | +| 学员端数据库设计 | architecture/student-technical-design.md > 三 | +| 学员端模块划分 | architecture/student-technical-design.md > 二 | +| 知识库文件预览修复 | architecture/fix-knowledge-file-preview.md | +| 学员端DDL脚本 | sql/student-module.sql | +| 基础数据库初始化 | sql/init.sql | + +### 按测试报告查找 +| 模块 | 测试报告 | +|------|----------| +| 测试汇总 | test-reports/TestReport_Summary.md | +| 用户管理 | test-reports/TestReport_1.3_UserManagement.md | +| 知识库 | test-reports/TestReport_2.0_Knowledge.md | +| 题库 | test-reports/TestReport_3.0_Question.md | +| 试卷 | test-reports/TestReport_4.0_Paper.md | +| 考试 | test-reports/TestReport_5.0_Exam.md | +| 培训计划 | test-reports/TestReport_6.0_TrainingPlan.md | +| 集成测试 | test-reports/Integration-Test-Report.md | +| UI审查 | test-reports/UI-Review-Report.md | + +--- + +## 五、AI 使用指南 + +### 何时读哪个文档 + +| 场景 | 应读文档 | +|------|----------| +| 了解项目全貌 | features/PRD.md | +| 开发学员端功能 | features/student.md | +| 查询数据库表结构 | features/PRD.md > 五、数据契约 | +| 查询业务规则 | features/PRD.md > 四、关键业务逻辑 | +| 查询技术选型 | features/PRD.md > 7.3 技术选型 | +| 执行数据库脚本 | sql/init.sql → sql/student-module.sql | +| 查看测试结果 | test-reports/TestReport_Summary.md | +| 查看AI提示词模板 | Prompt/*.md | + +### 读取顺序建议 + +1. **首次了解项目**:INDEX.md → features/PRD.md(全文) +2. **开发某角色功能**:INDEX.md → 对应 features/*.md +3. **查询特定规则**:INDEX.md > 四、快速导航 → 定位具体章节 +4. **查看测试情况**:test-reports/TestReport_Summary.md → 具体模块报告 + +--- + +## 六、公共规则速查 + +> 以下规则定义在 PRD.md,各角色需求文档不再重复 + +### 6.1 角色权限 + +| 角色 | 权限范围 | +|------|----------| +| ADMIN | 全平台管理权限 | +| LECTURER | 本部门知识库、题库、试卷、考试、培训计划管理 | +| STUDENT | 本部门知识学习、参加考试、查看个人数据 | + +### 6.2 内容状态 + +| 状态 | 说明 | 学员可见 | +|------|------|----------| +| DRAFT | 草稿 | ❌ | +| PUBLISHED | 已发布 | ✅ | +| OFFLINE | 已下架 | ❌ | + +### 6.3 数据隔离 + +- 知识库、题库、试卷、考试、培训计划均按**部门隔离** +- 管理员可见全部,讲师/学员只能操作/查看本部门 + +--- + +## 七、变更日志 + +| 日期 | 变更内容 | 操作人 | +|------|----------|--------| +| 2026-01-28 | 更新INDEX.md,反映实际目录结构 | AI | +| 2026-01-28 | 添加知识库文件预览修复方案 | Developer | +| 2026-01-28 | 完成集成测试报告 | QA | +| 2026-01-28 | 完成UI审查报告 | QA | +| 2026-01-15 | 学员端DDL脚本整理至sql目录 | Architect | +| 2026-01-15 | 学员端技术设计文档通过审核 | Supervisor | +| 2026-01-15 | 学员端技术设计文档完成 | Architect | +| 2026-01-15 | 创建文档索引 INDEX.md | PM | +| 2026-01-15 | 学员端需求文档通过评审 | Supervisor | +| 2026-01-13 | PRD V1.0.1 发布(多考试支持) | PM | +| 2026-01-08 | PRD V1.0.0 初版发布 | PM | diff --git a/training/codes/training-system/docs/LLR.md b/training/codes/training-system/docs/LLR.md new file mode 100644 index 0000000..8227c4f --- /dev/null +++ b/training/codes/training-system/docs/LLR.md @@ -0,0 +1,89 @@ +# LLR (Lessons Learned Record) + +## 2026-01-09: 组织架构列表加载失败 + +### 问题现象 +组织架构页面列表一直显示 loading,无法加载数据。 + +### 根本原因 +`common.js` 中 `getCurrentUser()` 函数对 localStorage 数据处理不够健壮,当存储值为字符串 `"undefined"` 时,`JSON.parse("undefined")` 抛出异常,导致页面脚本中断。 + +### 经验教训 + +1. **防御性编程**:从 localStorage 读取数据时,应考虑数据可能被污染或格式异常的情况,需增加有效性校验 +2. **错误定位**:前端加载问题应先查看浏览器控制台错误信息,而非只检查网络请求 +3. **文件去重**:项目中 `static` 和 `templates` 目录下存在相同文件(如 `org.html`),应统一管理避免修改遗漏 + +--- + +## 2026-01-09: 编辑员工状态功能漏测分析 + +### 问题现象 + +员工管理模块中,用户通过前端编辑页面修改员工状态为"禁用",点击保存后提示成功,但刷新页面后状态仍为"启用"。 + +### 根本原因 + +**后端Bug**:`UserDTO` 没有定义 `status` 字段,`UserServiceImpl.updateUser()` 方法也未处理状态更新,导致前端传递的 status 字段被静默忽略。 + +### 漏测原因分析 + +#### 1. 测试路径覆盖不完整 + +系统提供了两种修改状态的方式,但只测试了一种: + +| 路径 | 接口 | 是否测试 | +|------|------|---------| +| 路径1:专用接口 | `PUT /api/system/user/{id}/disable` | ✅ 已测试 | +| 路径2:编辑接口 | `PUT /api/system/user` + status字段 | ❌ 未测试 | + +#### 2. 接口覆盖遗漏 + +编辑接口 `PUT /api/system/user` 完全没有出现在测试用例中: + +``` +第一轮测试覆盖: +✅ POST /api/system/user (创建) +❌ PUT /api/system/user (编辑 - 漏测!) +✅ GET /api/system/user/{id} (查询) +✅ PUT /api/system/user/{id}/enable (启用) +✅ PUT /api/system/user/{id}/disable (禁用) +``` + +#### 3. 未进行端到端测试 + +只做了接口级测试,没有模拟前端真实操作流程。如果从前端页面操作验证,可以直接发现问题。 + +#### 4. 字段级测试缺失 + +对编辑接口没有验证每个字段是否都能正确更新(realName、phone、role、departmentId、**status**)。 + +#### 5. 前后端契约未验证 + +没有验证前端传递的字段后端是否都能正确接收和处理。 + +#### 6. 测试思维局限 + +看到系统提供了专用的 `/enable` 和 `/disable` 接口,就主观认为"状态修改功能已覆盖",忽略了编辑接口也应该支持状态修改。 + +### 经验教训 + +1. **测试要覆盖所有接口**:不能遗漏任何CRUD接口 +2. **测试要覆盖所有路径**:同一功能的不同实现路径,每条都要测试 +3. **测试要覆盖所有字段**:对编辑接口,需建立字段覆盖矩阵 +4. **测试要端到端验证**:重要功能必须从前端页面操作验证 +5. **测试要二次确认**:不信任接口返回值,必须查询确认数据确实变化 +6. **验证前后端契约**:确保前端发送的字段后端都能正确处理 + +### 改进检查清单 + +设计和执行测试时,问自己: + +- [ ] 这个功能有几种实现路径?都测了吗? +- [ ] 这个接口的所有字段都验证了吗? +- [ ] 前端实际是怎么调用的?和我测试的方式一样吗? +- [ ] 我验证了数据确实变化了吗?还是只看了返回值? + +### 一句话总结 + +**"只测了能用的接口,没测用户实际用的接口"** diff --git a/training/codes/training-system/docs/architecture/fix-knowledge-file-preview.md b/training/codes/training-system/docs/architecture/fix-knowledge-file-preview.md new file mode 100644 index 0000000..f494f74 --- /dev/null +++ b/training/codes/training-system/docs/architecture/fix-knowledge-file-preview.md @@ -0,0 +1,588 @@ +# 知识库文件预览功能修复方案 + +> **文档版本**:V1.0 +> **编写日期**:2026-01-20 +> **编写人员**:系统架构师 +> **关联QA报告**:docs/test-reports/QA_Report_Knowledge_FileView.md + +--- + +## 一、问题概述 + +### 1.1 BUG清单 + +| BUG编号 | 严重程度 | 问题描述 | 修复优先级 | +|---------|---------|---------|-----------| +| BUG-KV-001 | 高 | Office文档(Word/Excel/PPT)无法在线预览 | P0 | +| BUG-KV-002 | 中 | 静态资源路径配置不一致 | P0 | +| BUG-KV-003 | 低 | 文件扩展名判断不完整 | P2 | +| BUG-KV-004 | 低 | 视频断点续播边界处理缺失 | P3 | + +### 1.2 修复目标 + +1. 实现Word、Excel、PPT文档在线预览 +2. 统一文件存储路径配置,确保文件可正常访问 +3. 优化前端文件类型判断逻辑 +4. 增强视频播放的健壮性 + +--- + +## 二、技术选型分析 + +### 2.1 文档预览方案对比 + +| 方案 | 优点 | 缺点 | 是否需要外部服务 | 推荐度 | +|------|------|------|-----------------|--------| +| **kkFileView** | 开源免费、支持50+格式、可私有部署 | 需要单独部署服务 | 是(Docker) | ⭐⭐⭐⭐⭐ | +| **微软Office Online** | 无需部署、效果最佳 | 文件必须公网可访问 | 否(依赖微软) | ⭐⭐⭐ | +| **LibreOffice转PDF** | 完全私有化 | 需要服务器安装LibreOffice | 是(系统依赖) | ⭐⭐⭐ | +| **PDF.js + 服务端转换** | 纯前端渲染PDF | 需要后端转换Office到PDF | 部分 | ⭐⭐⭐⭐ | + +### 2.2 推荐方案:kkFileView + +**选择理由**: +1. **符合技术栈约束**:Java项目,不引入Python等其他语言 +2. **部署简单**:Docker一键部署,与现有Spring Boot架构解耦 +3. **格式全面**:支持Word/Excel/PPT/PDF/图片/视频/压缩包等50+格式 +4. **企业内网友好**:可完全私有化部署,无需公网访问 +5. **开源免费**:Apache 2.0协议 + +--- + +## 三、整体架构设计 + +### 3.1 修复后架构图 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 前端页面 (view.html) │ +│ ┌──────────────┐ ┌──────────────────────────┐ ┌──────────────┐ │ +│ │ PDF │ │ Word/Excel/PPT │ │ 视频 │ │ +│ │ (iframe) │ │ (iframe → kkFileView) │ │ (video) │ │ +│ └──────────────┘ └──────────────────────────┘ └──────────────┘ │ +└────────────────────────────────┬────────────────────────────────────────────┘ + │ + ┌────────────────────────┼────────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌─────────────────────────┐ ┌─────────────────────┐ +│ Spring Boot │ │ kkFileView │ │ 静态资源映射 │ +│ 后端API │ │ (Docker: 端口8012) │ │ /uploads/** │ +│ │ │ │ │ │ +│ /api/file/* │ │ /onlinePreview?url= │ │ file:${upload.path}│ +└───────────────┘ └─────────────────────────┘ └─────────────────────┘ + │ │ │ + └────────────────────────┼────────────────────────────┘ + │ + ▼ + ┌─────────────────────────┐ + │ 文件存储目录 │ + │ ${training.upload.path}│ + │ (默认: ./uploads/) │ + └─────────────────────────┘ +``` + +### 3.2 模块职责划分 + +| 模块 | 职责 | 修改范围 | +|------|------|---------| +| **WebMvcConfig** | 静态资源路径映射 | 修改资源位置配置 | +| **application.yml** | 文件存储路径配置 | 新增kkFileView地址配置 | +| **view.html** | 前端预览逻辑 | 修改Office文档预览方式 | +| **kkFileView** | 文档转换预览 | 新增Docker服务 | + +--- + +## 四、详细修复方案 + +### 4.1 BUG-KV-002:静态资源路径配置修复(P0) + +#### 问题根因 + +``` +当前配置不一致: +- application.yml: training.upload.path = D:/upload/ +- WebMvcConfig: file:./uploads/ (相对路径) +``` + +#### 修复方案 + +**方案A(推荐):统一使用配置文件中的路径** + +修改 `WebMvcConfig.java`,从配置文件读取路径: + +```java +// 文件:src/main/java/com/sino/training/common/config/WebMvcConfig.java + +@Configuration +@RequiredArgsConstructor +public class WebMvcConfig implements WebMvcConfigurer { + + @Value("${training.upload.path}") + private String uploadPath; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 上传文件访问路径 - 使用配置文件中的路径 + String resourceLocation = "file:" + uploadPath; + if (!resourceLocation.endsWith("/")) { + resourceLocation += "/"; + } + registry.addResourceHandler("/uploads/**") + .addResourceLocations(resourceLocation); + + // 静态资源 + registry.addResourceHandler("/static/**") + .addResourceLocations("classpath:/static/"); + } +} +``` + +**方案B:统一使用相对路径** + +修改 `application.yml`: + +```yaml +training: + upload: + path: ./uploads/ # 改为相对路径,与WebMvcConfig一致 +``` + +**推荐方案A**,更灵活,支持不同环境配置不同路径。 + +--- + +### 4.2 BUG-KV-001:Office文档在线预览(P0) + +#### 4.2.1 kkFileView服务部署 + +**Docker部署命令**: + +```bash +docker run -d \ + --name kkfileview \ + -p 8012:8012 \ + -v /opt/kkfileview/files:/opt/kkFileView-4.0.0/file \ + keking/kkfileview:4.3.0 +``` + +**docker-compose.yml(推荐)**: + +```yaml +# 文件:docker/docker-compose.yml +version: '3.8' +services: + kkfileview: + image: keking/kkfileview:4.3.0 + container_name: kkfileview + ports: + - "8012:8012" + volumes: + - ./kkfileview/files:/opt/kkFileView-4.0.0/file + restart: unless-stopped + environment: + - TZ=Asia/Shanghai +``` + +#### 4.2.2 后端配置新增 + +**application.yml 新增配置**: + +```yaml +training: + upload: + path: ./uploads/ + allowed-types: pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif,mp4,avi,mov + max-size: 104857600 + # 新增:文档预览服务配置 + preview: + enabled: true + # kkFileView服务地址(内网地址) + server-url: http://localhost:8012 + # 文件访问基础URL(用于kkFileView回调获取文件) + file-base-url: http://localhost:8080/uploads +``` + +#### 4.2.3 新增预览配置类 + +```java +// 文件:src/main/java/com/sino/training/common/config/PreviewConfig.java + +package com.sino.training.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 文档预览配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "training.preview") +public class PreviewConfig { + + /** + * 是否启用文档预览 + */ + private boolean enabled = true; + + /** + * kkFileView服务地址 + */ + private String serverUrl = "http://localhost:8012"; + + /** + * 文件访问基础URL + */ + private String fileBaseUrl = "http://localhost:8080/uploads"; +} +``` + +#### 4.2.4 新增预览URL生成接口 + +```java +// 文件:src/main/java/com/sino/training/common/controller/FileController.java +// 在现有FileController中新增方法 + +@Operation(summary = "获取文件预览URL") +@GetMapping("/preview-url") +public Result getPreviewUrl( + @Parameter(description = "文件相对路径") @RequestParam String fileUrl) { + return Result.success(fileService.getPreviewUrl(fileUrl)); +} +``` + +```java +// 文件:src/main/java/com/sino/training/common/service/FileService.java +// 接口新增方法 + +/** + * 获取文件预览URL + * + * @param fileUrl 文件相对URL(如 /uploads/xxx.docx) + * @return 预览URL(kkFileView格式) + */ +String getPreviewUrl(String fileUrl); +``` + +```java +// 文件:src/main/java/com/sino/training/common/service/impl/LocalFileServiceImpl.java +// 实现类新增方法 + +@Autowired +private PreviewConfig previewConfig; + +@Override +public String getPreviewUrl(String fileUrl) { + if (!previewConfig.isEnabled()) { + return null; + } + + // 获取文件扩展名 + String ext = FileUtil.getSuffix(fileUrl).toLowerCase(); + + // PDF和图片直接返回原URL,浏览器可直接渲染 + if (Arrays.asList("pdf", "jpg", "jpeg", "png", "gif", "webp").contains(ext)) { + return fileUrl; + } + + // Office文档使用kkFileView预览 + if (Arrays.asList("doc", "docx", "xls", "xlsx", "ppt", "pptx").contains(ext)) { + // 构建完整文件访问URL + String fullFileUrl = previewConfig.getFileBaseUrl() + fileUrl.replace("/uploads", ""); + // Base64编码 + String encodedUrl = Base64.getEncoder().encodeToString(fullFileUrl.getBytes(StandardCharsets.UTF_8)); + // 返回kkFileView预览URL + return previewConfig.getServerUrl() + "/onlinePreview?url=" + encodedUrl; + } + + // 其他格式返回原URL(下载) + return fileUrl; +} +``` + +#### 4.2.5 前端view.html修改 + +```javascript +// 文件:src/main/resources/static/knowledge/view.html +// 修改 renderKnowledge 函数中的文档预览逻辑 + +function renderKnowledge(data) { + // ... 其他代码保持不变 ... + + let contentHtml = ''; + if (data.type === 'VIDEO') { + // 视频逻辑保持不变 + contentHtml = `
+ +
`; + } else if (data.type === 'DOCUMENT') { + // 使用后端返回的fileType字段,更准确 + const fileType = (data.fileType || '').toLowerCase(); + const fileUrl = data.fileUrl || ''; + + if (fileType === 'pdf' || fileUrl.toLowerCase().endsWith('.pdf')) { + // PDF - 浏览器原生支持 + contentHtml = `
+ +
`; + } else if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(fileType)) { + // 图片 - 直接显示 + contentHtml = `
+ ${data.title || ''} +
`; + } else if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(fileType)) { + // Office文档 - 使用kkFileView预览 + contentHtml = `
+
+
+

正在加载文档预览...

+
+
`; + // 异步获取预览URL + loadOfficePreview(fileUrl); + } else { + // 其他格式 - 提供下载 + contentHtml = `
+
+ +

${data.fileName || '文档'}

+

该格式暂不支持在线预览

+ + 下载文档 + +
+
`; + } + } + // ... 其他代码保持不变 ... +} + +// 新增:加载Office文档预览 +async function loadOfficePreview(fileUrl) { + try { + const result = await TrainingSystem.get('/file/preview-url', { fileUrl: fileUrl }); + if (result && result.code === 200 && result.data) { + const previewUrl = result.data; + document.getElementById('officePreviewContainer').innerHTML = ` + + `; + } else { + showPreviewError(fileUrl); + } + } catch (e) { + console.error('获取预览URL失败:', e); + showPreviewError(fileUrl); + } +} + +// 新增:显示预览错误 +function showPreviewError(fileUrl) { + document.getElementById('officePreviewContainer').innerHTML = ` +
+ +

文档预览加载失败

+

请检查预览服务是否正常运行

+ + 下载文档 + +
+ `; +} +``` + +--- + +### 4.3 BUG-KV-003:文件扩展名判断优化(P2) + +#### 修复方案 + +前端改用后端返回的 `fileType` 字段,而非从URL解析: + +```javascript +// 修改前(有缺陷) +const ext = (data.fileUrl || '').split('.').pop().toLowerCase(); + +// 修改后(推荐) +const fileType = (data.fileType || '').toLowerCase(); +// 或者更健壮的解析方式 +function getFileExtension(url) { + if (!url) return ''; + // 移除查询参数 + const urlWithoutQuery = url.split('?')[0]; + const parts = urlWithoutQuery.split('.'); + return parts.length > 1 ? parts.pop().toLowerCase() : ''; +} +``` + +--- + +### 4.4 BUG-KV-004:视频断点续播优化(P3) + +#### 修复方案 + +```javascript +// 文件:view.html +// 修改视频断点续播逻辑 + +if (data.type === 'VIDEO' && data.videoPosition > 0) { + const video = document.getElementById('videoPlayer'); + if (video) { + video.addEventListener('loadedmetadata', function() { + try { + // 确保不超过视频总时长,且留有余量 + const safePosition = Math.min(data.videoPosition, video.duration - 1); + if (safePosition > 0 && isFinite(safePosition)) { + video.currentTime = safePosition; + } + } catch (e) { + console.warn('设置视频播放位置失败:', e); + // 失败时从头播放 + video.currentTime = 0; + } + }, { once: true }); + + // 添加错误处理 + video.addEventListener('error', function(e) { + console.error('视频加载错误:', e); + }); + } +} +``` + +--- + +## 五、配置文件完整示例 + +### 5.1 application.yml(修改后) + +```yaml +# 自定义配置 +training: + jwt: + secret: training-system-jwt-secret-key-2026 + expire: 86400000 + header: Authorization + prefix: Bearer + # 文件上传配置 + upload: + path: ./uploads/ + allowed-types: pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif,mp4,avi,mov + max-size: 104857600 + # 文档预览配置(新增) + preview: + enabled: true + server-url: http://localhost:8012 + file-base-url: http://localhost:8080/uploads +``` + +### 5.2 application-prod.yml(生产环境) + +```yaml +training: + upload: + path: /data/training-system/uploads/ + preview: + enabled: true + server-url: http://kkfileview:8012 + file-base-url: http://your-domain.com/uploads +``` + +--- + +## 六、部署步骤 + +### 6.1 开发环境 + +```bash +# 1. 启动kkFileView +docker run -d --name kkfileview -p 8012:8012 keking/kkfileview:4.3.0 + +# 2. 创建上传目录 +mkdir -p ./uploads + +# 3. 启动Spring Boot应用 +mvn spring-boot:run +``` + +### 6.2 生产环境 + +```bash +# 1. 使用docker-compose部署 +cd docker +docker-compose up -d + +# 2. 确保文件目录权限 +mkdir -p /data/training-system/uploads +chown -R 1000:1000 /data/training-system/uploads + +# 3. 部署应用(根据实际部署方式) +``` + +--- + +## 七、修改文件清单 + +| 文件路径 | 修改类型 | 修改内容 | +|---------|---------|---------| +| `src/main/java/com/sino/training/common/config/WebMvcConfig.java` | 修改 | 资源路径从配置读取 | +| `src/main/java/com/sino/training/common/config/PreviewConfig.java` | 新增 | 预览配置类 | +| `src/main/java/com/sino/training/common/service/FileService.java` | 修改 | 新增getPreviewUrl方法 | +| `src/main/java/com/sino/training/common/service/impl/LocalFileServiceImpl.java` | 修改 | 实现getPreviewUrl方法 | +| `src/main/java/com/sino/training/common/controller/FileController.java` | 修改 | 新增预览URL接口 | +| `src/main/resources/application.yml` | 修改 | 新增preview配置 | +| `src/main/resources/static/knowledge/view.html` | 修改 | Office预览逻辑 | +| `docker/docker-compose.yml` | 新增 | kkFileView部署配置 | + +--- + +## 八、测试验证 + +### 8.1 功能测试用例 + +| 测试项 | 操作步骤 | 预期结果 | +|-------|---------|---------| +| PDF预览 | 上传PDF文件 → 查看 | iframe直接显示 | +| Word预览 | 上传docx文件 → 查看 | kkFileView渲染显示 | +| Excel预览 | 上传xlsx文件 → 查看 | kkFileView渲染显示 | +| PPT预览 | 上传pptx文件 → 查看 | kkFileView渲染显示 | +| 预览服务不可用 | 停止kkFileView → 查看Office文档 | 显示错误提示+下载按钮 | +| 视频断点续播 | 播放视频到50% → 刷新页面 | 从50%位置继续播放 | + +### 8.2 回归测试 + +- [ ] 文件上传功能正常 +- [ ] 文件下载功能正常 +- [ ] PDF预览功能正常 +- [ ] 图片预览功能正常 +- [ ] 视频播放功能正常 +- [ ] 学习时长统计正常 + +--- + +## 九、风险与回退 + +### 9.1 风险评估 + +| 风险 | 可能性 | 影响 | 缓解措施 | +|------|-------|------|---------| +| kkFileView服务不稳定 | 低 | 中 | 配置健康检查、自动重启 | +| 大文件预览超时 | 中 | 低 | 设置合理超时时间、提示用户下载 | +| 内存占用增加 | 中 | 低 | 限制并发预览数量 | + +### 9.2 回退方案 + +如果kkFileView方案出现问题,可快速回退: + +1. 将 `training.preview.enabled` 设置为 `false` +2. 前端自动降级为"仅下载"模式 +3. 无需修改代码,仅改配置即可 + +--- + +**文档状态**:待审批 +**预计工时**:3人天 +**审批人**:___________ diff --git a/training/codes/training-system/docs/architecture/student-technical-design.md b/training/codes/training-system/docs/architecture/student-technical-design.md new file mode 100644 index 0000000..86d7cac --- /dev/null +++ b/training/codes/training-system/docs/architecture/student-technical-design.md @@ -0,0 +1,803 @@ +# + +> 版本:V1.0.0 +> 创建日期:2026-01-15 +> 作者:Architect +> 状态:✅ 已批准(Supervisor 审核通过) + +--- + +## 一、技术评审结论 + +### 1.1 评审结果 + +| 评审项 | 结论 | +|--------|------| +| 需求可实现性 | ✅ 可实现 | +| 技术风险 | 🟡 中(3个需关注点) | +| 架构兼容性 | ✅ 与现有PRD架构一致 | +| 开发工作量 | 中等(预估15人天) | + +### 1.2 技术风险识别 + +| 风险项 | 等级 | 描述 | 应对策略 | +|--------|------|------|----------| +| **多标签页检测** | 🟡 中 | 同一知识防止多标签页同时学习,需前端实现 | 使用 BroadcastChannel API 或 localStorage 事件监听 | +| **视频播放进度防篡改** | 🟡 中 | 前端进度可被篡改 | 服务端校验播放时长与上报时长的合理性 | +| **考试超时定时任务** | 🟡 中 | 定时任务与用户交卷并发 | 乐观锁 + 幂等设计(需求已明确) | +| **大量学员排名计算** | 🟢 低 | 实时计算排名可能有性能问题 | 使用SQL窗口函数,加合理索引 | + +### 1.3 技术决策 + +| 决策项 | 决策 | 理由 | +|--------|------|------| +| 学习进度存储 | 单表设计 `km_knowledge_progress` | 需求明确,数据量可控(450学员×知识数) | +| 活跃检测实现 | 前端 JavaScript 实现 | 无需服务端介入,降低复杂度 | +| 视频播放器 | video.js | PRD已选型,成熟稳定 | +| 定时任务框架 | Spring @Scheduled | 单机部署足够,无需分布式调度 | + +--- + +## 二、模块架构设计 + +### 2.1 学员端模块划分 + +``` +module/ +├── student/ # 学员端模块(新增) +│ ├── controller/ +│ │ ├── StudentDashboardController.java # 工作台 +│ │ ├── StudentKnowledgeController.java # 知识学习 +│ │ ├── StudentExamController.java # 在线考试 +│ │ ├── StudentTrainingController.java # 我的培训 +│ │ └── StudentProfileController.java # 个人中心 +│ ├── service/ +│ │ ├── StudentDashboardService.java +│ │ ├── KnowledgeLearningService.java # 学习进度服务 +│ │ ├── ExamTakingService.java # 考试答题服务 +│ │ ├── TrainingProgressService.java # 培训进度服务 +│ │ └── StudentProfileService.java +│ ├── dto/ +│ │ ├── request/ +│ │ └── response/ +│ └── vo/ +│ +├── knowledge/ # 知识库模块(已有,扩展) +│ └── service/ +│ └── KnowledgeProgressService.java # 学习进度(新增) +│ +├── exam/ # 考试模块(已有,扩展) +│ ├── service/ +│ │ └── ExamRecordService.java # 考试记录(扩展) +│ └── task/ +│ └── ExamTimeoutTask.java # 超时自动交卷任务(新增) +│ +└── training/ # 培训模块(已有,扩展) + └── service/ + └── TrainingProgressService.java # 培训进度(扩展) +``` + +### 2.2 模块职责说明 + +| 模块 | 职责 | 依赖模块 | +|------|------|----------| +| **student** | 学员端入口,聚合学员相关功能 | knowledge, exam, training | +| **knowledge** | 知识库CRUD + 学习进度记录 | - | +| **exam** | 考试CRUD + 答题记录 + 超时任务 | - | +| **training** | 培训计划CRUD + 进度跟踪 | knowledge, exam | + +--- + +## 三、数据库设计 + +### 3.1 新增表结构 + +#### 3.1.1 知识学习进度表 (km_knowledge_progress) + +```sql +CREATE TABLE km_knowledge_progress ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', + user_id BIGINT NOT NULL COMMENT '学员ID', + knowledge_id BIGINT NOT NULL COMMENT '知识ID', + department_id BIGINT NOT NULL COMMENT '所属部门(冗余)', + status VARCHAR(20) NOT NULL DEFAULT 'NOT_STARTED' COMMENT '状态:NOT_STARTED/IN_PROGRESS/COMPLETED', + progress INT NOT NULL DEFAULT 0 COMMENT '进度百分比(0-100)', + duration BIGINT NOT NULL DEFAULT 0 COMMENT '学习时长(秒)', + video_position BIGINT DEFAULT 0 COMMENT '视频播放位置(秒)', + source VARCHAR(20) NOT NULL DEFAULT 'FREE' COMMENT '学习来源:FREE/TRAINING', + plan_id BIGINT DEFAULT NULL COMMENT '关联培训计划ID', + start_time DATETIME DEFAULT NULL COMMENT '首次学习时间', + complete_time DATETIME DEFAULT NULL COMMENT '完成时间', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + + UNIQUE KEY uk_user_knowledge (user_id, knowledge_id), + KEY idx_user_id (user_id), + KEY idx_knowledge_id (knowledge_id), + KEY idx_department_id (department_id), + KEY idx_plan_id (plan_id), + KEY idx_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识学习进度表'; +``` + +#### 3.1.2 考试记录表扩展 (ex_exam_record) + +```sql +-- 原有字段基础上新增 +ALTER TABLE ex_exam_record +ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'IN_PROGRESS' COMMENT '状态:IN_PROGRESS/SUBMITTED' AFTER answers, +ADD COLUMN last_save_time DATETIME DEFAULT NULL COMMENT '最后保存时间' AFTER status, +ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER last_save_time, +ADD COLUMN submit_source VARCHAR(20) DEFAULT NULL COMMENT '交卷来源:USER/SYSTEM_TIMEOUT' AFTER version; + +-- 新增索引 +ALTER TABLE ex_exam_record +ADD INDEX idx_status (status), +ADD INDEX idx_user_exam_status (user_id, exam_id, status); +``` + +#### 3.1.3 培训计划-考试关联表 (tr_plan_exam) + +> 注:PRD V1.0.1 已提到此表,这里补充完整DDL + +```sql +CREATE TABLE tr_plan_exam ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', + plan_id BIGINT NOT NULL COMMENT '培训计划ID', + exam_id BIGINT NOT NULL COMMENT '考试ID', + required TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否必考:1-必考,0-选考', + sort_order INT NOT NULL DEFAULT 0 COMMENT '排序', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + + UNIQUE KEY uk_plan_exam (plan_id, exam_id), + KEY idx_plan_id (plan_id), + KEY idx_exam_id (exam_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='培训计划-考试关联表'; +``` + +### 3.2 索引设计说明 + +| 表名 | 索引 | 用途 | +|------|------|------| +| km_knowledge_progress | uk_user_knowledge | 唯一约束,防止重复记录 | +| km_knowledge_progress | idx_plan_id | 按培训计划查询学习进度 | +| ex_exam_record | idx_user_exam_status | 查询用户某考试的进行中记录 | +| ex_exam_record | idx_status | 定时任务扫描进行中的记录 | + +--- + +## 四、API 接口设计 + +### 4.1 接口规范 + +- **基础路径**:`/api/student` +- **认证方式**:JWT Token(Header: Authorization: Bearer {token}) +- **权限要求**:角色 = STUDENT +- **响应格式**:统一 Result 封装 + +```java +{ + "code": 200, // 状态码 + "message": "success", // 消息 + "data": { } // 数据 +} +``` + +### 4.2 工作台接口 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 获取工作台数据 | GET | /api/student/dashboard | 统计卡片+待办列表 | + +**响应示例**: +```json +{ + "code": 200, + "data": { + "statistics": { + "pendingKnowledgeCount": 12, + "pendingExamCount": 3, + "trainingProgress": 75 + }, + "trainingPlans": [ + { + "id": 1, + "title": "2026年Q1安全规范培训", + "status": "IN_PROGRESS", + "progress": 60, + "endDate": "2026-03-31" + } + ], + "pendingExams": [ + { + "id": 1, + "title": "安全规范考核", + "endTime": "2026-01-15 18:00:00", + "remainingAttempts": 2 + } + ] + } +} +``` + +### 4.3 知识学习接口 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 知识列表 | GET | /api/student/knowledge | 分页查询,含学习状态 | +| 知识详情 | GET | /api/student/knowledge/{id} | 获取知识详情+学习进度 | +| 开始学习 | POST | /api/student/knowledge/{id}/start | 记录开始学习 | +| 更新进度 | POST | /api/student/knowledge/{id}/progress | 更新学习进度(定时上报) | +| 完成学习 | POST | /api/student/knowledge/{id}/complete | 标记学习完成 | + +**更新进度请求**: +```json +{ + "duration": 120, // 本次学习时长(秒) + "progress": 45, // 当前进度百分比 + "videoPosition": 180, // 视频播放位置(秒),仅视频 + "source": "TRAINING", // 学习来源 + "planId": 1 // 培训计划ID(来源为TRAINING时必填) +} +``` + +### 4.4 在线考试接口 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 我的考试列表 | GET | /api/student/exam | 分页查询分配给我的考试 | +| 考试详情 | GET | /api/student/exam/{id} | 考试信息+我的考试记录 | +| 开始考试 | POST | /api/student/exam/{id}/start | 创建考试记录,返回试题 | +| 继续考试 | GET | /api/student/exam/{id}/continue | 恢复进行中的考试 | +| 保存答案 | POST | /api/student/exam/{id}/save | 定时保存答案(每30秒) | +| 提交试卷 | POST | /api/student/exam/{id}/submit | 交卷,返回成绩+排名 | +| 考试结果 | GET | /api/student/exam/record/{recordId} | 查看某次考试详情 | + +**开始考试响应**: +```json +{ + "code": 200, + "data": { + "recordId": 123, + "examTitle": "安全规范考核", + "duration": 60, + "totalScore": 100, + "passScore": 60, + "questions": [ + { + "id": 1, + "type": "SINGLE", + "content": "高速公路救援作业时...", + "options": ["A. 50米", "B. 150米", "C. 200米", "D. 100米"], + "score": 5, + "sortOrder": 1 + } + ], + "startTime": "2026-01-15 10:00:00", + "endTime": "2026-01-15 11:00:00" + } +} +``` + +**提交试卷响应**: +```json +{ + "code": 200, + "data": { + "recordId": 123, + "score": 85, + "totalScore": 100, + "passed": true, + "rank": 5, + "totalParticipants": 43, + "answers": [ + { + "questionId": 1, + "myAnswer": "B", + "correctAnswer": "B", + "correct": true, + "score": 5, + "analysis": "根据规定,高速公路救援..." + } + ] + } +} +``` + +### 4.5 我的培训接口 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 培训计划列表 | GET | /api/student/training | 分页查询分配给我的培训 | +| 培训详情 | GET | /api/student/training/{id} | 计划详情+我的进度 | + +**培训详情响应**: +```json +{ + "code": 200, + "data": { + "id": 1, + "title": "2026年Q1安全规范培训", + "description": "掌握高速公路救援安全规范...", + "startDate": "2026-01-01", + "endDate": "2026-03-31", + "status": "IN_PROGRESS", + "progress": 75, + "completed": false, + "knowledgeList": [ + { + "id": 1, + "title": "高速救援SOP手册", + "type": "DOCUMENT", + "required": true, + "learningStatus": "COMPLETED", + "learningProgress": 100 + } + ], + "examList": [ + { + "id": 1, + "title": "安全规范考核", + "required": true, + "passed": true, + "bestScore": 85 + } + ] + } +} +``` + +### 4.6 个人中心接口 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 个人信息 | GET | /api/student/profile | 基本信息+学习统计 | +| 学习记录 | GET | /api/student/profile/learning | 学习历史列表 | +| 考试记录 | GET | /api/student/profile/exam | 考试历史列表 | + +--- + +## 五、核心业务逻辑设计 + +### 5.1 学习进度更新流程 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 前端页面 │────▶│ Controller │────▶│ Service │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ + │ 1.定时上报进度 │ │ + │ (每60秒) │ │ + │ │ │ + │ │ 2.更新学习进度表 │ + │ │ km_knowledge_ │ + │ │ progress │ + │ │ │ + │ │ 3.检查是否完成 │ + │ │ (视频≥90% 或 │ + │ │ 文档时长达标) │ + │ │ │ + │ │ 4.若来源=TRAINING │ + │ │ 更新培训计划进度 │ + │ │ │ + ◀───────────────────◀───────────────────│ + 返回更新结果 +``` + +**关键代码逻辑**: + +```java +@Service +public class KnowledgeLearningService { + + /** + * 更新学习进度 + */ + @Transactional + public void updateProgress(Long userId, Long knowledgeId, ProgressUpdateDTO dto) { + // 1. 获取或创建进度记录 + KnowledgeProgress progress = getOrCreateProgress(userId, knowledgeId); + + // 2. 累加学习时长 + progress.setDuration(progress.getDuration() + dto.getDuration()); + progress.setProgress(dto.getProgress()); + progress.setVideoPosition(dto.getVideoPosition()); + progress.setUpdateTime(LocalDateTime.now()); + + // 3. 检查是否完成 + Knowledge knowledge = knowledgeMapper.selectById(knowledgeId); + if (isCompleted(knowledge, progress)) { + progress.setStatus(LearningStatus.COMPLETED); + progress.setCompleteTime(LocalDateTime.now()); + + // 4. 若是培训任务学习,同步更新培训进度 + if (dto.getSource() == LearningSource.TRAINING && dto.getPlanId() != null) { + trainingProgressService.updateKnowledgeProgress(dto.getPlanId(), userId, knowledgeId); + } + } else { + progress.setStatus(LearningStatus.IN_PROGRESS); + } + + // 5. 保存 + knowledgeProgressMapper.updateById(progress); + } + + /** + * 判断是否完成学习 + */ + private boolean isCompleted(Knowledge knowledge, KnowledgeProgress progress) { + if (knowledge.getType() == KnowledgeType.VIDEO) { + // 视频:播放进度 >= 90% + return progress.getProgress() >= 90; + } else { + // 文档:学习时长 >= 预估阅读时间 + long estimatedTime = calculateEstimatedTime(knowledge); + return progress.getDuration() >= estimatedTime; + } + } +} +``` + +### 5.2 考试超时自动交卷 + +```java +@Component +public class ExamTimeoutTask { + + @Autowired + private ExamRecordMapper examRecordMapper; + + @Autowired + private ExamTakingService examTakingService; + + /** + * 每分钟执行一次,处理超时考试 + */ + @Scheduled(cron = "0 */1 * * * ?") + public void autoSubmitTimeoutExams() { + // 1. 查询所有超时的进行中考试记录 + List timeoutRecords = examRecordMapper.selectTimeoutRecords(); + + for (ExamRecord record : timeoutRecords) { + try { + // 2. 使用乐观锁提交 + examTakingService.submitBySystem(record.getId(), record.getVersion()); + } catch (OptimisticLockException e) { + // 已被用户提交,忽略 + log.info("考试记录已被提交: recordId={}", record.getId()); + } + } + } +} +``` + +**超时记录查询SQL**: +```sql +SELECT r.* +FROM ex_exam_record r +JOIN ex_exam e ON r.exam_id = e.id +JOIN ex_paper p ON e.paper_id = p.id +WHERE r.status = 'IN_PROGRESS' + AND DATE_ADD(r.start_time, INTERVAL p.duration MINUTE) < NOW() +``` + +### 5.3 排名计算逻辑 + +```java +/** + * 计算学员在某考试中的排名 + */ +public RankInfo calculateRank(Long examId, Long userId) { + // SQL 使用窗口函数计算排名 + String sql = """ + WITH ranked AS ( + SELECT + user_id, + MAX(score) as best_score, + MIN(CASE WHEN score = (SELECT MAX(score) FROM ex_exam_record WHERE exam_id = ? AND user_id = t.user_id AND status = 'SUBMITTED') THEN submit_time END) as first_best_time, + DENSE_RANK() OVER (ORDER BY MAX(score) DESC, MIN(submit_time) ASC) as rank + FROM ex_exam_record t + WHERE exam_id = ? AND status = 'SUBMITTED' + GROUP BY user_id + ) + SELECT rank, (SELECT COUNT(DISTINCT user_id) FROM ex_exam_record WHERE exam_id = ? AND status = 'SUBMITTED') as total + FROM ranked + WHERE user_id = ? + """; + + return jdbcTemplate.queryForObject(sql, new Object[]{examId, examId, examId, userId}, + (rs, rowNum) -> new RankInfo(rs.getInt("rank"), rs.getInt("total"))); +} +``` + +### 5.4 培训进度计算 + +```java +/** + * 计算培训计划进度 + */ +public int calculateTrainingProgress(Long planId, Long userId) { + // 获取必修知识数和已完成数 + int requiredKnowledgeCount = planKnowledgeMapper.countRequired(planId); + int completedKnowledgeCount = knowledgeProgressMapper.countCompletedByPlan(planId, userId); + + // 获取必考考试数和已通过数 + int requiredExamCount = planExamMapper.countRequired(planId); + int passedExamCount = examRecordMapper.countPassedByPlan(planId, userId); + + // 计算进度 + int totalRequired = requiredKnowledgeCount + requiredExamCount; + int totalCompleted = completedKnowledgeCount + passedExamCount; + + if (totalRequired == 0) return 100; + return (int) Math.round((double) totalCompleted / totalRequired * 100); +} +``` + +--- + +## 六、前端技术要点 + +### 6.1 活跃检测实现 + +```javascript +// 文档学习页面的活跃检测 +class ActivityDetector { + constructor(onInactive, onActive) { + this.lastActiveTime = Date.now(); + this.checkInterval = 60000; // 60秒检测一次 + this.inactiveThreshold = 60000; // 60秒无活动视为非活跃 + this.onInactive = onInactive; + this.onActive = onActive; + this.isActive = true; + + this.bindEvents(); + this.startCheck(); + } + + bindEvents() { + ['mousemove', 'click', 'scroll', 'keypress'].forEach(event => { + document.addEventListener(event, () => { + this.lastActiveTime = Date.now(); + if (!this.isActive) { + this.isActive = true; + this.onActive?.(); + } + }); + }); + } + + startCheck() { + setInterval(() => { + const inactive = Date.now() - this.lastActiveTime > this.inactiveThreshold; + if (inactive && this.isActive) { + this.isActive = false; + this.onInactive?.(); + } + }, this.checkInterval); + } +} + +// 使用 +const detector = new ActivityDetector( + () => { + pauseLearningTimer(); + showInactiveNotice(); + }, + () => { + resumeLearningTimer(); + hideInactiveNotice(); + } +); +``` + +### 6.2 多标签页检测 + +```javascript +// 使用 BroadcastChannel 检测多标签页 +class SingleTabLock { + constructor(knowledgeId) { + this.channel = new BroadcastChannel(`knowledge_${knowledgeId}`); + this.isLocked = false; + + // 发送占用消息 + this.channel.postMessage({ type: 'OCCUPY', timestamp: Date.now() }); + + // 监听其他标签页 + this.channel.onmessage = (event) => { + if (event.data.type === 'OCCUPY') { + // 检测到其他标签页,比较时间戳 + if (event.data.timestamp > this.occupyTime) { + this.showConflictNotice(); + } + } + }; + + this.occupyTime = Date.now(); + } + + showConflictNotice() { + alert('该知识已在其他窗口学习中'); + window.close(); + } + + release() { + this.channel.close(); + } +} +``` + +### 6.3 考试倒计时 + +```javascript +class ExamCountdown { + constructor(remainingSeconds, onTimeout) { + this.remaining = remainingSeconds; + this.onTimeout = onTimeout; + this.warningThreshold = 300; // 5分钟警告 + this.timerElement = document.getElementById('countdown'); + } + + start() { + this.timer = setInterval(() => { + this.remaining--; + this.updateDisplay(); + + if (this.remaining <= this.warningThreshold) { + this.timerElement.classList.add('warning'); + } + + if (this.remaining <= 0) { + clearInterval(this.timer); + this.onTimeout(); + } + }, 1000); + } + + updateDisplay() { + const minutes = Math.floor(this.remaining / 60); + const seconds = this.remaining % 60; + this.timerElement.textContent = + `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } + + getRemainingSeconds() { + return this.remaining; + } +} +``` + +--- + +## 七、安全设计 + +### 7.1 接口权限控制 + +```java +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth + // 学员端接口仅允许 STUDENT 角色访问 + .requestMatchers("/api/student/**").hasRole("STUDENT") + // 其他配置... + ); + return http.build(); + } +} +``` + +### 7.2 数据隔离 + +```java +@Aspect +@Component +public class DepartmentIsolationAspect { + + @Before("execution(* com.sino.training.module.student.service.*.*(..))") + public void checkDepartmentIsolation(JoinPoint joinPoint) { + // 获取当前用户 + UserContext user = SecurityUtils.getCurrentUser(); + + // 注入部门ID到查询条件 + // 确保学员只能访问本部门数据 + } +} +``` + +### 7.3 考试答案防篡改 + +```java +/** + * 提交考试时校验时长合理性 + */ +public void validateSubmitTime(ExamRecord record) { + long actualDuration = Duration.between(record.getStartTime(), LocalDateTime.now()).toSeconds(); + long allowedDuration = paper.getDuration() * 60 + 60; // 允许1分钟误差 + + if (actualDuration > allowedDuration) { + throw new BusinessException("考试时间异常"); + } +} +``` + +--- + +## 八、测试要点 + +### 8.1 单元测试 + +| 测试类 | 测试点 | +|--------|--------| +| KnowledgeLearningServiceTest | 进度更新、完成判定、时长累加 | +| ExamTakingServiceTest | 开始考试、保存答案、交卷评分 | +| ExamTimeoutTaskTest | 超时判定、乐观锁冲突处理 | +| TrainingProgressServiceTest | 进度计算、完成判定 | + +### 8.2 集成测试 + +| 场景 | 测试步骤 | 预期结果 | +|------|----------|----------| +| 断点续考 | 开始考试→保存答案→关闭浏览器→重新进入 | 答案恢复,倒计时继续 | +| 超时交卷 | 开始考试→等待超时 | 定时任务自动交卷 | +| 并发交卷 | 用户交卷同时定时任务触发 | 只有一方成功,无数据冲突 | +| 排名计算 | 多人交卷后查看排名 | 排名正确,同分按时间排序 | + +### 8.3 性能测试 + +| 场景 | 目标 | +|------|------| +| 100人并发答题 | 响应时间 < 500ms | +| 答案保存接口 | 成功率 > 99.99% | +| 排名计算 | 500人考试排名 < 100ms | + +--- + +## 九、部署配置 + +### 9.1 定时任务配置 + +```yaml +# application.yml +exam: + timeout: + enabled: true + cron: "0 */1 * * * ?" # 每分钟执行 +``` + +### 9.2 学习进度配置 + +```yaml +learning: + document: + min-duration-per-page: 60 # 每页最少阅读时间(秒) + video: + complete-threshold: 90 # 视频完成阈值(%) +``` + +--- + +## 十、开发任务拆分 + +| 任务 | 工作量 | 优先级 | 依赖 | +|------|--------|--------|------| +| 数据库表创建 | 0.5天 | P0 | - | +| 知识学习进度服务 | 2天 | P0 | 数据库 | +| 在线考试服务 | 3天 | P0 | 数据库 | +| 超时自动交卷任务 | 1天 | P0 | 考试服务 | +| 培训进度服务 | 2天 | P0 | 知识、考试服务 | +| 工作台聚合服务 | 1天 | P0 | 各子服务 | +| 个人中心服务 | 1天 | P1 | 各子服务 | +| 前端页面开发 | 4天 | P0 | API接口 | +| 单元测试 | 1天 | P0 | 服务开发 | +| 集成测试 | 1天 | P0 | 全部服务 | + +**总计:约17人天** + +--- + +**文档状态:待评审** + +> 请 Supervisor 审核技术设计方案,确认后可进入开发阶段。 diff --git a/training/codes/training-system/docs/code_review.md b/training/codes/training-system/docs/code_review.md new file mode 100644 index 0000000..b47ae6e --- /dev/null +++ b/training/codes/training-system/docs/code_review.md @@ -0,0 +1,336 @@ +# 代码审查报告 + +**项目**: 道路救援企业培训系统 +**审查日期**: 2026-02-04 +**审查员**: Code Review Agent +**审查范围**: 工作区已修改文件 + +--- + +## 审查结论 + +❌ **Request Changes(不通过)** + +存在 3 个高危安全漏洞,必须修复后才能合并。 + +--- + +## 一、安全问题 (Security Issues) + +### 1. [P0/高危] 文件删除路径遍历漏洞 + +**文件**: `src/main/java/com/sino/training/common/service/impl/LocalFileServiceImpl.java` +**行号**: 133-150 +**函数**: `delete(String fileUrl)` + +```java +public boolean delete(String fileUrl) { + if (StrUtil.isBlank(fileUrl)) { + return false; + } + // 将URL转换为文件路径 + String relativePath = fileUrl.replace("/uploads/", ""); + String fullPath = uploadPath + "/" + relativePath; + // ... +} +``` + +**问题**: 未校验 `fileUrl` 是否包含 `../` 路径遍历字符。攻击者可构造如 `/uploads/../../../etc/passwd` 的路径删除服务器任意文件。 + +**风险等级**: 高危 - 可导致任意文件删除 + +**建议**: +- 校验路径不包含 `..` 和 `\` +- 使用 `Path.normalize()` 规范化路径 +- 验证最终路径在 `uploadPath` 目录内 + +--- + +### 2. [P0/高危] 空指针异常风险 + +**文件**: `src/main/java/com/sino/training/module/exam/service/impl/ExamRecordServiceImpl.java` +**行号**: 142-143, 253-254 +**函数**: `submitExam()`, `buildExamPaper()` + +```java +Exam exam = examMapper.selectById(record.getExamId()); +Paper paper = paperMapper.selectById(exam.getPaperId()); // exam 可能为 null +``` + +**问题**: 未对 `exam` 和 `paper` 进行空值检查,若数据不存在将抛出 NPE。 + +**风险等级**: 高危 - 可导致服务崩溃 + +**建议**: +- 查询后立即检查空值 +- 抛出明确的业务异常 + +--- + +### 3. [P0/高危] JWT 密钥硬编码 + +**文件**: `src/main/resources/application.yml` +**行号**: 57-58 + +```yaml +jwt: + secret: training-system-jwt-secret-key-2026 +``` + +**问题**: +- JWT 密钥硬编码在配置文件中 +- 密钥强度不足(仅 35 字符) +- 密钥可预测(包含年份) + +**风险等级**: 高危 - 可导致 Token 伪造 + +**建议**: +- 使用环境变量 `${JWT_SECRET}` +- 密钥长度至少 256 位(32 字节) +- 使用随机生成的密钥 + +--- + +### 4. [P1/中危] CORS 配置过于宽松 + +**文件**: `src/main/java/com/sino/training/common/config/WebMvcConfig.java` +**行号**: 32-38 + +```java +registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowCredentials(true) +``` + +**问题**: 允许所有来源且允许携带凭证,违反 CORS 安全最佳实践。 + +**风险等级**: 中危 - 存在 CSRF 攻击风险 + +**建议**: 生产环境限制具体域名 + +--- + +### 5. [P1/中危] 前端 XSS 风险 + +**文件**: `src/main/resources/static/js/common.js` +**行号**: 200-212 +**函数**: `showMessage()` + +```javascript +const toastHtml = ` +
+ ${text} // 未转义直接插入 HTML +
+`; +container.insertAdjacentHTML('beforeend', toastHtml); +``` + +**问题**: `text` 参数未经 HTML 转义直接插入 DOM,存在 XSS 风险。 + +**风险等级**: 中危 - 可执行恶意脚本 + +**建议**: 对 `text` 进行 HTML 实体转义 + +--- + +### 6. [P2/低危] 敏感信息硬编码 + +**文件**: `src/main/resources/application.yml` +**行号**: 71-73 + +```yaml +server-url: http://192.168.1.163:8012 +file-base-url: http://192.168.1.163:8090/uploads +``` + +**问题**: 内网 IP 地址硬编码在配置文件中。 + +**建议**: 使用环境变量配置 + +--- + +## 二、代码质量问题 + +### 1. [P1/中危] 异常被静默吞没 + +**文件**: `src/main/java/com/sino/training/module/exam/service/impl/ExamRecordServiceImpl.java` +**行号**: 282-283 + +```java +} catch (Exception ignored) { +} +``` + +**问题**: 异常被完全忽略,不利于问题排查和监控。 + +**建议**: 至少记录 warn 级别日志 + +--- + +### 2. [P2/低危] 调试日志未清理 + +**文件**: `src/main/java/com/sino/training/common/service/impl/LocalFileServiceImpl.java` +**行号**: 106, 115, 174, 187 + +```java +log.info("父目录创建成功======: {}", ...); +log.info("文件上传成功=========: {}", ...); +``` + +**问题**: 包含调试标记 `======`,不符合日志规范。 + +**建议**: 清理调试标记 + +--- + +### 3. [P2/低危] 注释代码未删除 + +**文件**: `src/main/java/com/sino/training/common/service/impl/LocalFileServiceImpl.java` +**行号**: 97 + +```java +//String fullPath = uploadPath + "/" + relativePath; +``` + +**问题**: 注释的旧代码应删除。 + +--- + +### 4. [P2/低危] 空方法实现 + +**文件**: `src/main/java/com/sino/training/module/training/service/impl/PlanProgressServiceImpl.java` +**行号**: 117-123 + +```java +public void syncExamProgress(...) { + // 暂时不做处理... +} +``` + +**问题**: 空方法可能导致调用方误解。 + +**建议**: 添加 `@Deprecated` 或抛出 `UnsupportedOperationException` + +--- + +## 三、性能问题 + +### 1. [P1/中危] N+1 查询问题 + +**文件**: `src/main/java/com/sino/training/module/exam/service/impl/ExamRecordServiceImpl.java` +**行号**: 163-164, 287-288, 383-384 + +```java +for (PaperQuestion pq : paperQuestions) { + Question question = questionMapper.selectById(pq.getQuestionId()); + // ... +} +``` + +**问题**: 循环内单条查询数据库,50 道题产生 50 次 DB 查询。 + +**影响**: 性能下降,数据库压力增大 + +**建议**: 批量查询后用 Map 缓存 + +--- + +### 2. [P2/低危] 组织架构树多次查询 + +**文件**: `src/main/java/com/sino/training/module/system/service/impl/OrganizationServiceImpl.java` +**行号**: 42-45, 74-77, 98-106 + +**问题**: 每个节点单独查询子级,三层嵌套导致多次数据库访问。 + +**建议**: 一次查询所有数据后内存构建树 + +--- + +## 四、代码规范问题 + +### 1. [P2/低危] 路径拼接不一致 + +**文件**: `src/main/java/com/sino/training/common/service/impl/LocalFileServiceImpl.java` + +```java +// upload (第98行): 无斜杠 +String fullPath = uploadPath + relativePath; + +// delete (第140行): 有斜杠 +String fullPath = uploadPath + "/" + relativePath; +``` + +**问题**: 两处路径拼接方式不一致,可能导致文件删除失败。 + +--- + +### 2. [P2/低危] 缺少参数校验注解 + +**文件**: `src/main/java/com/sino/training/common/controller/FileController.java` +**行号**: 48 + +```java +public Result delete(@RequestParam String fileUrl) +``` + +**建议**: 添加 `@NotBlank` 注解 + +--- + +### 3. [P3/建议] 代码重复可简化 + +**文件**: `src/main/java/com/sino/training/module/exam/service/impl/ExamRecordServiceImpl.java` +**行号**: 209-213, 225-228 + +```java +List voList = new ArrayList<>(); +for (ExamRecord record : records) { + voList.add(convertToVO(record, false)); +} +return voList; +``` + +**建议**: 使用 Stream API 简化 + +--- + +## 五、问题统计 + +| 级别 | 数量 | 说明 | +|------|------|------| +| P0/高危 | 3 | 安全漏洞,必须立即修复 | +| P1/中危 | 4 | 安全/性能问题,优先处理 | +| P2/低危 | 7 | 代码规范问题,建议修复 | +| P3/建议 | 1 | 可选优化 | +| **总计** | **15** | | + +--- + +## 六、修复优先级 + +| 优先级 | 问题 | 文件 | +|--------|------|------| +| P0-1 | 路径遍历漏洞 | LocalFileServiceImpl.java:133 | +| P0-2 | 空指针风险 | ExamRecordServiceImpl.java:142 | +| P0-3 | JWT 密钥安全 | application.yml:58 | +| P1-1 | CORS 配置 | WebMvcConfig.java:32 | +| P1-2 | XSS 风险 | common.js:200 | +| P1-3 | 异常吞没 | ExamRecordServiceImpl.java:282 | +| P1-4 | N+1 查询 | ExamRecordServiceImpl.java:163 | + +--- + +## 七、审查总结 + +本次审查发现 **3 个高危安全漏洞**,涉及: + +1. **路径遍历** - 可导致任意文件删除 +2. **空指针异常** - 可导致服务崩溃 +3. **JWT 密钥泄露** - 可导致身份伪造 + +**结论**: ❌ **不通过**,需修复所有 P0 问题后重新审查。 + +--- + +*审查工具: Claude Code Review Agent* +*生成时间: 2026-02-04* diff --git a/training/codes/training-system/docs/database/mysqlCommand.md b/training/codes/training-system/docs/database/mysqlCommand.md new file mode 100644 index 0000000..697367e --- /dev/null +++ b/training/codes/training-system/docs/database/mysqlCommand.md @@ -0,0 +1,16 @@ +数据库创建: + +docker run -d \ +--name mysql8 \ +--restart=always \ +-p 3306:3306 \ +-v /data/mysql:/var/lib/mysql \ +-e MYSQL_ROOT_PASSWORD=Sino#650 \ +-e MYSQL_DATABASE=training_system \ +-e MYSQL_USER=training \ +-e MYSQL_PASSWORD=Training@2026 \ +mysql:8.0 + + + +数据库重启: \ No newline at end of file diff --git a/training/codes/training-system/docs/features/PRD.md b/training/codes/training-system/docs/features/PRD.md new file mode 100644 index 0000000..a108032 --- /dev/null +++ b/training/codes/training-system/docs/features/PRD.md @@ -0,0 +1,881 @@ +# 道路救援企业培训系统 - 产品需求文档 (PRD) + +> 版本:V1.0.1 +> 更新日期:2026-01-13 +> 状态:已确认 + +--- + +## 版本更新记录 + +### V1.0.1 (2026-01-13) + +**【培训计划】多考试任务支持** + +| 变更项 | 变更前 | 变更后 | +|-------|-------|-------| +| 考试任务数量 | 最多关联1个考试 | 支持关联多个考试 | +| 考试属性 | 无 | 支持设置必考/选考 | +| 考试排序 | 无 | 支持自定义排序 | + +**功能说明:** +- 创建/编辑培训计划时,可添加多个考试任务 +- 每个考试可设置为"必考"或"选考" +- 学员端展示所有关联考试及通过状态 +- 列表页显示考试任务总数 + +**数据库变更:** +- 新增 `tr_plan_exam` 关联表 +- 移除 `tr_plan` 表的 `exam_id` 字段 +- ⚠️ 不兼容历史数据,需重新初始化数据库 + +--- + +### V1.0.0 (2026-01-08) + +- 初始版本发布 +- 包含:系统基础、人员管理、知识库、考题管理、试卷管理、考试管理、培训计划六大模块 + +--- + +## 一、核心目标 (Mission) + +**为道路救援企业打造一站式内部培训平台,通过知识沉淀、在线考核、培训管理三大核心能力,提升500名员工的专业技能水平和服务标准化程度。** + +--- + +## 二、用户画像 (Persona) + +| 角色 | 人数估算 | 核心痛点 | 使用场景 | +|------|----------|----------|----------| +| **管理员** | 3-5人 | 人员管理分散,培训效果难追踪 | 配置系统、管理组织架构、分配权限 | +| **讲师(技师)** | 30-50人 | 知识传承依赖口口相传,出题组卷效率低 | 上传知识文档、创建题库、发布考试 | +| **学员** | 450人左右 | 学习资料分散、考试不便、进度不清晰 | 查阅知识库、完成培训任务、参加考试 | + +--- + +## 三、版本规划 + +### V1:最小可行产品 (MVP) + +#### 模块一:系统基础与人员管理 + +| 功能 | 说明 | +|----------|-----------------------------| +| 企业微信授权登录 | 企业微信授权登录 | +| 组织架构管理 | 中心 → 部门(≤8)→ 小组(≤10/部门)三级结构 | +| 员工管理 | 管理员手动录入员工信息,分配角色(管理员/讲师/学员) | +| 权限控制 | 基于角色的权限隔离,数据按部门隔离 | + +#### 模块二:知识库(重点) + +| 功能 | 说明 | +|------|------| +| 分类目录 | 支持多级分类(如:安全规范 > 高速救援 > 操作手册)| +| 文档管理 | 支持上传 PDF/Word/Excel/PPT 等常见文档格式 | +| 视频管理 | 支持上传视频文件,在线播放 | +| 在线预览 | 文档、视频无需下载即可在线查看 | +| 状态管理 | 草稿 → 已发布 → 已下架 | +| 部门隔离 | 各部门只能查看本部门的知识内容 | + +#### 模块三:考题管理 + +| 功能 | 说明 | +|------|------| +| 题型支持 | 单选题、多选题、判断题 | +| 题目解析 | 每道题必须填写答案解析 | +| 题库分类 | 按分类/章节管理题目 | +| 状态管理 | 草稿 → 已发布 → 已下架 | +| 部门隔离 | 讲师只能管理本部门题库 | + +#### 模块四:试卷管理 + +| 功能 | 说明 | +|------|------| +| 手动组卷 | 讲师手动从题库选题组成试卷 | +| 自动组卷 | 设置规则(单选X题+多选Y题+判断Z题),系统随机抽题 | +| 试卷配置 | 设置总分、题目分值、考试时长 | +| 试卷预览 | 组卷完成后可预览试卷效果 | +| 状态管理 | 草稿 → 已发布 → 已下架 | + +#### 模块五:考试管理 + +| 功能 | 说明 | +|------|------| +| 发布考试 | 选择试卷,设置考试名称、时间窗口(开始~结束时间)| +| 指定对象 | 支持指定部门 / 小组 / 个人参加考试 | +| 考试规则 | 设置及格线、限制考试次数、考试时长 | +| 在线答题 | 学员在线作答,自动计时,超时自动交卷 | +| 成绩查看 | 交卷后立即显示成绩和答案解析 | + +#### 模块六:培训计划 + +| 功能 | 说明 | +|------|------| +| 创建培训计划 | 设置计划名称、培训周期、培训目标 | +| 关联知识/考试 | 一个培训计划可包含多个知识文档 + 多场考试(V1.0.1+) | +| 考试属性设置 | 每个考试可设置必考/选考、自定义排序(V1.0.1+) | +| 分配学员 | 指定部门/小组/个人参加培训计划 | +| 进度跟踪 | 学员可查看自己的学习进度和考试状态 | +| 计划状态 | 未开始 / 进行中 / 已结束 | + +### V2 及以后版本 (Future Releases) + +| 版本 | 功能 | 价值 | +|------|------|------| +| **V2** | 统计报表中心 | 培训完成率、考试通过率、部门排名等数据看板 | +| **V2** | 线下培训签到 | 扫码签到/签退,记录实际培训时长 | +| **V2** | 错题本 | 学员自动收集错题,巩固薄弱知识点 | +| **V3** | 知识库增强 | 收藏、点赞、评论、全文搜索 | +| **V3** | 奖励体系 | 积分、勋章、学习排行榜 | +| **V3** | 证书管理 | 培训/考试 +--- + +## 四、关键业务逻辑 (Business Rules) + +### 4.1 权限规则 + +``` +管理员: + ├── 管理所有中心、部门、小组 + ├── 管理所有用户(增删改查、角色分配) + ├── 查看全平台数据 + └── 系统配置 + +讲师: + ├── 管理本部门知识库(上传/编辑/删除) + ├── 管理本部门题库(增删改查) + ├── 创建/管理本部门试卷和考试 + ├── 创建/管理本部门培训计划 + └── 查看本部门学员成绩 + +学员: + ├── 查看本部门知识库(只读) + ├── 参加分配给自己的培训计划 + ├── 参加分配给自己的考试 + └── 查看个人学习记录和成绩 +``` + +### 4.2 内容状态流转 + +``` +┌─────────┐ 发布 ┌─────────┐ 下架 ┌─────────┐ +│ 草稿 │ ───────────▶ │ 已发布 │ ───────────▶ │ 已下架 │ +│ DRAFT │ │PUBLISHED │ │ OFFLINE │ +└─────────┘ └─────────┘ └─────────┘ + ▲ │ │ + │ │ 重新上架 │ + │ ◀────────────────────────┘ + │ + └──────── 可继续编辑 ─────────────────────────────┘ +``` + +| 状态 | 说明 | 学员可见 | +|------|------|----------| +| **草稿 (DRAFT)** | 讲师编辑中,未完成 | ❌ 不可见 | +| **已发布 (PUBLISHED)** | 正式生效,可被使用 | ✅ 可见 | +| **已下架 (OFFLINE)** | 临时隐藏,保留数据 | ❌ 不可见 | + +### 4.3 考试规则 + +| 规则 | 说明 | +|------|------| +| 时间窗口 | 只有在考试开放时间内才能进入考试 | +| 限时作答 | 超过考试时长自动交卷 | +| 限制次数 | 达到最大次数后不可再考,取最高分 | +| 及格判定 | 分数 ≥ 及格线 为通过 | +| 答题顺序 | 自动组卷时题目顺序随机打乱 | + +### 4.4 数据隔离规则 + +| 数据类型 | 隔离方式 | +|----------|----------| +| 知识库 | 按部门隔离,只能看本部门 | +| 题库 | 按部门隔离,讲师只能操作本部门 | +| 试卷/考试 | 按部门隔离 | +| 培训计划 | 按部门隔离 | +| 员工信息 | 管理员可见全部,讲师可见本部门 | + +### 4.5 引用保护规则 + +| 场景 | 规则 | +|------|------| +| 题目下架 | 已被试卷引用的题目,下架前需确认警告 | +| 试卷下架 | 已被考试引用的试卷,下架前需确认警告 | +| 知识下架 | 已被培训计划引用的知识,下架前需确认警告 | + +--- + +## 五、数据契约 (Data Contract) + +### 5.1 核心实体 + +#### 用户 (sys_user) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| wx_userid | String | 企业微信用户ID | +| real_name | String | 姓名 | +| phone | String | 手机号 | +| role | Enum | 角色:ADMIN/LECTURER/STUDENT | +| department_id | Long | 所属部门 | +| group_id | Long | 所属小组(可空) | +| status | Enum | 状态:ENABLED/DISABLED | +| create_time | DateTime | 创建时间 | + +#### 组织架构 (sys_center / sys_department / sys_group) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| name | String | 名称 | +| parent_id | Long | 上级ID(部门关联中心,小组关联部门) | +| sort_order | Integer | 排序 | +| create_time | DateTime | 创建时间 | + +#### 知识分类 (km_category) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| name | String | 分类名称 | +| parent_id | Long | 父分类ID | +| department_id | Long | 所属部门 | +| sort_order | Integer | 排序 | + +#### 知识 (km_knowledge) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 标题 | +| category_id | Long | 所属分类 | +| type | Enum | 类型:DOCUMENT/VIDEO | +| file_url | String | 文件地址 | +| file_size | Long | 文件大小 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:DRAFT/PUBLISHED/OFFLINE | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | +| publish_time | DateTime | 发布时间 | + +#### 题目 (ex_question) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| type | Enum | 题型:SINGLE/MULTIPLE/JUDGE | +| content | String | 题干 | +| options | JSON | 选项列表(判断题为空) | +| answer | String | 正确答案 | +| analysis | String | 解析 | +| category_id | Long | 所属分类 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:DRAFT/PUBLISHED/OFFLINE | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | +| publish_time | DateTime | 发布时间 | + +#### 试卷 (ex_paper) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 试卷名称 | +| total_score | Integer | 总分 | +| duration | Integer | 考试时长(分钟) | +| pass_score | Integer | 及格分 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:DRAFT/PUBLISHED/OFFLINE | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | +| publish_time | DateTime | 发布时间 | + +#### 试卷题目关联 (ex_paper_question) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| paper_id | Long | 试卷ID | +| question_id | Long | 题目ID | +| score | Integer | 该题分值 | +| sort_order | Integer | 排序 | + +#### 考试 (ex_exam) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 考试名称 | +| paper_id | Long | 关联试卷 | +| start_time | DateTime | 开始时间 | +| end_time | DateTime | 结束时间 | +| max_attempts | Integer | 最大考试次数 | +| department_id | Long | 所属部门 | +| status | Enum | 状态:NOT_STARTED/IN_PROGRESS/ENDED | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | + +#### 考试对象 (ex_exam_target) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| exam_id | Long | 考试ID | +| target_type | Enum | 对象类型:DEPARTMENT/GROUP/USER | +| target_id | Long | 对象ID | + +#### 考试记录 (ex_exam_record) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| exam_id | Long | 考试ID | +| user_id | Long | 学员ID | +| attempt_no | Integer | 第几次考试 | +| score | Integer | 得分 | +| passed | Boolean | 是否通过 | +| start_time | DateTime | 开始时间 | +| submit_time | DateTime | 提交时间 | +| answers | JSON | 答题详情 | + +#### 培训计划 (tr_plan) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| title | String | 计划名称 | +| description | String | 计划描述 | +| start_date | Date | 开始日期 | +| end_date | Date | 结束日期 | +| department_id | Long | 所属部门 | +| exam_id | Long | 关联考试(可空) | +| status | Enum | 状态:NOT_STARTED/IN_PROGRESS/ENDED | +| creator_id | Long | 创建人 | +| create_time | DateTime | 创建时间 | + +#### 培训计划-知识关联 (tr_plan_knowledge) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| plan_id | Long | 计划ID | +| knowledge_id | Long | 知识ID | +| required | Boolean | 是否必修 | +| sort_order | Integer | 排序 | + +#### 培训计划-对象 (tr_plan_target) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| plan_id | Long | 计划ID | +| target_type | Enum | 对象类型:DEPARTMENT/GROUP/USER | +| target_id | Long | 对象ID | + +#### 培训进度 (tr_plan_progress) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| plan_id | Long | 计划ID | +| user_id | Long | 学员ID | +| knowledge_id | Long | 知识ID | +| completed | Boolean | 是否完成 | +| complete_time | DateTime | 完成时间 | + +--- + +## 六、原型设计(方案A - 经典管理后台风格) + +### 6.1 设计理念 + +左侧固定导航 + 右侧内容区,层级分明,适合功能复杂的企业管理系统。学习成本低,用户容易上手。 + +### 6.2 布局结构 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 │ 内容区域 │ +│ │ │ +│ 👥 人员管理 ▶ │ │ +│ ├ 组织架构 │ │ +│ ├ 员工管理 │ │ +│ └ 讲师管理 │ │ +│ │ │ +│ 📚 课程中心 ▶ │ │ +│ ├ 课程分类 │ │ +│ └ 课程列表 │ │ +│ │ │ +│ ✏️ 考题管理 ▶ │ │ +│ ├ 题库分类 │ │ +│ └ 题目列表 │ │ +│ │ │ +│ 📄 试卷管理 │ │ +│ │ │ +│ 📝 考试管理 │ │ +│ │ │ +│ 📅 培训计划 │ │ +│ │ │ +│ ────────────── │ │ +│ ⚙️ 系统设置 │ │ +│ │ │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +### 6.3 核心页面原型 + +#### 工作台首页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 ● │ 欢迎回来,张三 2026年1月8日 │ +│ │ │ +│ 👥 人员管理 ▶ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ │ 待学习课程 │ │ 待完成考试 │ │ 培训进度 │ │ +│ 📚 知识库 ▶ │ │ │ │ │ │ │ │ +│ │ │ 12 │ │ 3 │ │ 75% │ │ +│ ✏️ 考题管理 ▶ │ │ │ │ │ │ ████░░ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ 📄 试卷管理 │ │ +│ │ ┌──────────────────────────────────────────────────┐ │ +│ 📝 考试管理 │ │ 我的培训计划 │ │ +│ │ ├──────────────────────────────────────────────────┤ │ +│ 📅 培训计划 │ │ 📋 2026年Q1安全规范培训 进行中 60% │ │ +│ │ │ 📋 新员工入职培训 进行中 30% │ │ +│ │ │ 📋 高速救援操作规范 未开始 -- │ │ +│ │ └──────────────────────────────────────────────────┘ │ +│ │ │ +│ │ ┌──────────────────────────────────────────────────┐ │ +│ │ │ 待完成考试 │ │ +│ │ ├──────────────────────────────────────────────────┤ │ +│ │ │ 📝 安全规范考核 截止: 1月15日 [进入考试] │ │ +│ │ │ 📝 月度技能测试 截止: 1月20日 [进入考试] │ │ +│ │ └──────────────────────────────────────────────────┘ │ +│ │ │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +#### 知识库列表页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 │ 知识库 > 安全规范 │ +│ │ ───────────────────────────────────────────────── │ +│ 👥 人员管理 ▶ │ │ +│ │ [+ 新增知识] [所有状态 ▼] [🔍 搜索知识...] │ +│ 📚 知识库 ▼ │ │ +│ ┌ 知识分类 │ ┌────────────────────────────────────────────────┐ │ +│ └ 知识列表 ● │ │ 标题 类型 状态 更新时间 操作│ │ +│ │ ├────────────────────────────────────────────────┤ │ +│ ✏️ 考题管理 ▶ │ │ 高速救援SOP 📄文档 已发布 01-05 编辑│ │ +│ │ │ 安全操作视频 🎬视频 已发布 01-03 编辑│ │ +│ 📄 试卷管理 │ │ 应急处理流程 📄文档 草稿 01-02 编辑│ │ +│ │ │ 设备使用手册 📄文档 已下架 12-28 编辑│ │ +│ 📝 考试管理 │ └────────────────────────────────────────────────┘ │ +│ │ │ +│ 📅 培训计划 │ 共 24 条记录 < 1 2 3 4 5 > │ +│ │ │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +#### 在线考试页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 📝 2026年Q1安全规范考试 剩余时间: 45:23 │ +├────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ 第 3 题 / 共 20 题 [单选题] │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ │ │ +│ │ 高速公路救援作业时,警示标志应放置在故障车辆后方多少米处? │ │ +│ │ │ │ +│ │ ○ A. 50米 │ │ +│ │ ● B. 150米 │ │ +│ │ ○ C. 200米 │ │ +│ │ ○ D. 100米 │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ 答题卡: │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ ✓1 ✓2 ●3 ○4 ○5 ○6 ○7 ○8 ○9 ○10 │ │ +│ │ ○11 ○12 ○13 ○14 ○15 ○16 ○17 ○18 ○19 ○20 │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ [< 上一题] [下一题 >] [交卷] │ +│ │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +#### 试卷组卷页 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 🚗 道路救援培训系统 🔔 消息 👤 张三 ▼ │ +├──────────────────┬─────────────────────────────────────────────────────────┤ +│ │ │ +│ 📊 工作台 │ 创建试卷 > 第2步:选择题目 │ +│ │ ═══════════════════════════════════════════════════ │ +│ 👥 人员管理 ▶ │ ① 基本信息 ────── ② 选择题目 ────── ③ 预览确认 │ +│ │ ● │ +│ 📚 知识库 ▶ │ │ +│ │ ┌─────────────────────┐ ┌────────────────────────┐ │ +│ ✏️ 考题管理 ▶ │ │ 📂 题库 │ │ 📋 已选题目 (15) │ │ +│ │ │ ┌─────────────────┐ │ │ │ │ +│ 📄 试卷管理 ● │ │ │ [全选] 安全(48) │ │ │ 单选题 x10 30分 │ │ +│ │ │ │ ☑ 高速警示... │ │ │ 多选题 x3 15分 │ │ +│ 📝 考试管理 │ │ │ ☑ 夜间灯光... │ │ │ 判断题 x2 5分 │ │ +│ │ │ │ ☐ 拖车规范... │ │ │ ─────────────── │ │ +│ 📅 培训计划 │ │ └─────────────────┘ │ │ 总分: 50分 │ │ +│ │ │ │ │ │ │ +│ │ │ ── 或自动组卷 ── │ │ [自动组卷] │ │ +│ │ │ 单选: [10] 题 │ │ 单选: [10] 题 │ │ +│ │ │ 多选: [ 5] 题 │ │ 多选: [ 5] 题 │ │ +│ │ │ 判断: [ 5] 题 │ │ 判断: [ 5] 题 │ │ +│ │ │ [随机抽题] │ │ [确认随机抽取] │ │ +│ │ └─────────────────────┘ └────────────────────────┘ │ +│ │ │ +│ │ [上一步] [下一步:预览] │ +└──────────────────┴─────────────────────────────────────────────────────────┘ +``` + +--- + +## 七、架构设计蓝图 + +### 7.1 系统架构总览 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ 客户端层 │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ PC浏览器 │ │ 企业微信H5 │ │ 移动端浏览器 │ │ +│ │ (管理员/讲师) │ │ (学员) │ │ (学员) │ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ +└───────────┼─────────────────────┼─────────────────────┼─────────────────┘ + │ │ │ + └─────────────────────┼─────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 网关层 │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ Spring Boot 应用 │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ │ JWT认证 │ │ 权限拦截 │ │ 日志记录 │ │ 异常处理 │ │ │ +│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 业务层 │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ 认证模块 │ │ 组织模块 │ │ 知识库模块│ │ 考试模块 │ │ 培训模块 │ │ +│ │ │ │ │ │ │ │ │ │ │ │ +│ │ ·企微登录 │ │ ·组织架构 │ │ ·分类管理 │ │ ·题库管理 │ │ ·计划管理 │ │ +│ │ ·Token管理│ │ ·员工管理 │ │ ·知识CRUD │ │ ·试卷管理 │ │ ·任务分配 │ │ +│ │ ·权限校验 │ │ ·角色管理 │ │ ·文件上传 │ │ ·考试管理 │ │ ·进度跟踪 │ │ +│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ 数据层 │ +│ ┌─────────────────────────┐ ┌─────────────────────────┐ │ +│ │ MySQL 8.0 │ │ 文件存储服务 │ │ +│ │ (MyBatis Plus ORM) │ │ (本地/OSS/MinIO) │ │ +│ └─────────────────────────┘ └─────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 7.2 核心流程图 + +#### 用户登录流程(企业微信OAuth) + +```mermaid +sequenceDiagram + participant U as 用户 + participant FE as 前端页面 + participant BE as 后端服务 + participant WX as 企业微信API + participant DB as 数据库 + + U->>FE: 点击"企业微信登录" + FE->>WX: 跳转企微授权页面 + WX->>U: 显示授权确认 + U->>WX: 确认授权 + WX->>FE: 回调返回code + FE->>BE: 携带code请求登录 + BE->>WX: 用code换取access_token + WX-->>BE: 返回access_token + BE->>WX: 获取用户信息(userid) + WX-->>BE: 返回用户信息 + BE->>DB: 查询用户是否存在 + alt 用户存在 + DB-->>BE: 返回用户信息 + BE->>BE: 生成JWT Token + BE-->>FE: 返回Token+用户信息 + FE->>FE: 存储Token,跳转工作台 + else 用户不存在 + BE-->>FE: 返回错误:用户未录入 + FE->>U: 提示联系管理员 + end +``` + +#### 在线考试流程 + +```mermaid +sequenceDiagram + participant S as 学员 + participant FE as 前端页面 + participant BE as 后端服务 + participant DB as 数据库 + + S->>FE: 进入考试列表 + FE->>BE: 获取我的考试列表 + BE->>DB: 查询分配给该学员的考试 + DB-->>BE: 返回考试列表 + BE-->>FE: 返回考试列表(含剩余次数) + + S->>FE: 点击"开始考试" + FE->>BE: 请求开始考试 + BE->>DB: 校验考试状态/次数/时间窗口 + alt 校验通过 + BE->>DB: 创建考试记录(开始时间) + BE->>DB: 获取试卷题目 + DB-->>BE: 返回题目列表 + BE-->>FE: 返回试卷内容+考试时长 + FE->>FE: 启动倒计时 + + loop 答题过程 + S->>FE: 选择/修改答案 + FE->>FE: 本地暂存答案 + FE->>BE: 定时自动保存(每30秒) + BE->>DB: 更新答题记录 + end + + alt 主动交卷 + S->>FE: 点击"交卷" + else 时间到 + FE->>FE: 倒计时结束 + end + + FE->>BE: 提交试卷 + BE->>BE: 自动判分(客观题) + BE->>DB: 保存成绩+答案详情 + BE-->>FE: 返回考试成绩 + FE->>S: 显示成绩+答案解析 + else 校验失败 + BE-->>FE: 返回错误(次数用尽/不在时间窗口) + FE->>S: 提示无法参加考试 + end +``` + +#### 培训计划执行流程 + +```mermaid +flowchart TB + subgraph 讲师操作 + A[创建培训计划] --> B[设置基本信息] + B --> C[关联知识内容] + C --> D[关联考试-可选] + D --> E[指定培训对象] + E --> F[发布培训计划] + end + + subgraph 学员学习 + F --> G[学员收到培训通知] + G --> H[进入培训计划] + H --> I[学习知识内容] + I --> J{知识学完?} + J -->|否| I + J -->|是| K{有关联考试?} + K -->|否| L[培训完成] + K -->|是| M[参加考试] + M --> N{考试通过?} + N -->|是| L + N -->|否| O{还有考试次数?} + O -->|是| M + O -->|否| P[培训未通过] + end + + subgraph 进度追踪 + I --> Q[更新学习进度] + M --> R[记录考试成绩] + L --> S[标记培训完成] + end +``` + +### 7.3 技术选型 + +| 层次 | 技术 | 版本 | 说明 | +|------|------|------|------| +| **构建** | Maven | 3.8+ | 依赖管理 | +| **框架** | Spring Boot | 3.1.2 | 主框架 | +| **ORM** | MyBatis Plus | 3.5.3+ | 简化CRUD | +| **数据库** | MySQL | 8.0 | 主数据库 | +| **认证** | java-jwt | 4.4+ | JWT Token生成验证 | +| **加密** | Spring Security | 6.x | BCrypt密码加密 | +| **文档** | Springdoc OpenAPI | 2.2+ | API文档 | +| **工具** | Lombok | 1.18+ | 简化代码 | +| **JSON** | Jackson | 内置 | JSON序列化 | +| **文件** | 本地存储 / MinIO | - | V1先用本地,V2可扩展 | +| **前端** | HTML + CSS + JS | - | 原生技术栈 | + +#### 关键依赖库 + +| 用途 | 库 | 说明 | +|------|-----|------| +| 企业微信SDK | weixin-java-cp | 企业微信Java SDK | +| 文件预览 | kkFileView / OnlyOffice | 文档在线预览(可选外部服务) | +| 视频播放 | video.js | 前端视频播放器 | +| Excel导入导出 | EasyExcel | 阿里开源,性能好 | +| 工具库 | Hutool | 常用工具集合 | + +### 7.4 风险评估 + +| 风险点 | 等级 | 描述 | 应对策略 | +|--------|------|------|----------| +| **企业微信对接** | 🟡 中 | 需要企业微信管理员配置应用 | 提前准备配置文档,预留账号密码登录作为备选 | +| **文件预览** | 🟡 中 | 不同格式文档预览兼容性 | V1优先支持PDF+图片+视频,复杂格式提示下载 | +| **大文件上传** | 🟡 中 | 视频文件可能较大 | 限制单文件大小,支持分片上传 | +| **考试并发** | 🟢 低 | 500人规模并发压力不大 | 合理设计索引,必要时加缓存 | +| **数据隔离** | 🟢 低 | 部门数据隔离逻辑 | 统一在Service层实现隔离过滤 | + +### 7.5 项目结构 + +``` +training-system/ +├── pom.xml +├── src/ +│ └── main/ +│ ├── java/com/sino/training/ +│ │ ├── TrainingApplication.java # 启动类 +│ │ │ +│ │ ├── common/ # 通用模块 +│ │ │ ├── config/ # 配置类 +│ │ │ ├── exception/ # 异常处理 +│ │ │ ├── result/ # 统一响应 +│ │ │ ├── interceptor/ # 拦截器 +│ │ │ └── utils/ # 工具类 +│ │ │ +│ │ ├── module/ # 业务模块 +│ │ │ ├── auth/ # 认证模块 +│ │ │ ├── system/ # 系统管理 +│ │ │ ├── knowledge/ # 知识库模块 +│ │ │ ├── exam/ # 考试模块 +│ │ │ └── training/ # 培训模块 +│ │ │ +│ │ └── integration/ # 外部集成 +│ │ └── wechat/ # 企业微信 +│ │ +│ └── resources/ +│ ├── application.yml # 主配置 +│ ├── application-dev.yml # 开发环境 +│ ├── application-prod.yml # 生产环境 +│ ├── mapper/ # MyBatis XML +│ ├── static/ # 静态资源 +│ └── templates/ # 页面模板 +│ +├── docs/ +│ └── PRD.md # 产品需求文档 +│ +└── sql/ + └── init.sql # 数据库初始化脚本 +``` + +--- + +## 八、附录 + +### 8.1 角色菜单权限对照表 + +| 菜单 | 管理员 | 讲师 | 学员 | +|------|--------|------|------| +| 工作台 | ✅ | ✅ | ✅ | +| 组织架构管理 | ✅ | ❌ | ❌ | +| 员工管理 | ✅ | 👁️ 本部门只读 | ❌ | +| 讲师管理 | ✅ | ❌ | ❌ | +| 知识库-分类管理 | ✅ | ✅ 本部门 | ❌ | +| 知识库-知识列表 | ✅ | ✅ 本部门 | 👁️ 本部门已发布 | +| 考题管理-题库分类 | ✅ | ✅ 本部门 | ❌ | +| 考题管理-题目列表 | ✅ | ✅ 本部门 | ❌ | +| 试卷管理 | ✅ | ✅ 本部门 | ❌ | +| 考试管理 | ✅ | ✅ 本部门 | 👁️ 我的考试 | +| 培训计划 | ✅ | ✅ 本部门 | 👁️ 我的培训 | +| 系统设置 | ✅ | ❌ | ❌ | + +### 8.2 状态枚举定义 + +```java +// 内容状态 +public enum ContentStatus { + DRAFT, // 草稿 + PUBLISHED, // 已发布 + OFFLINE // 已下架 +} + +// 用户角色 +public enum UserRole { + ADMIN, // 管理员 + LECTURER, // 讲师 + STUDENT // 学员 +} + +// 用户状态 +public enum UserStatus { + ENABLED, // 启用 + DISABLED // 禁用 +} + +// 题目类型 +public enum QuestionType { + SINGLE, // 单选题 + MULTIPLE, // 多选题 + JUDGE // 判断题 +} + +// 知识类型 +public enum KnowledgeType { + DOCUMENT, // 文档 + VIDEO // 视频 +} + +// 考试/培训目标类型 +public enum TargetType { + DEPARTMENT, // 部门 + GROUP, // 小组 + USER // 个人 +} + +// 考试状态 +public enum ExamStatus { + NOT_STARTED, // 未开始 + IN_PROGRESS, // 进行中 + ENDED // 已结束 +} + +// 培训计划状态 +public enum PlanStatus { + NOT_STARTED, // 未开始 + IN_PROGRESS, // 进行中 + ENDED // 已结束 +} +``` + +--- + +**文档状态:已确认,等待开发启动** diff --git a/training/codes/training-system/docs/features/V2-StatisticsCenter.md b/training/codes/training-system/docs/features/V2-StatisticsCenter.md new file mode 100644 index 0000000..9944e94 --- /dev/null +++ b/training/codes/training-system/docs/features/V2-StatisticsCenter.md @@ -0,0 +1,554 @@ +# V2 统计报表中心 - 产品需求文档 + +> 版本:V2.0.0-draft +> 作者:Product Agent +> 日期:2026-02-04 +> 状态:待评审 + +--- + +## 一、功能概述 + +### 1.1 背景与目标 + +**背景**:V1 版本完成了培训系统的基础功能,但管理层无法量化培训效果,讲师无法追踪学员学习情况,缺乏数据支撑决策。 + +**目标**:构建统计报表中心,让培训效果可量化、可追踪、可对比。 + +### 1.2 目标用户 + +| 角色 | 核心诉求 | +|------|----------| +| **管理员** | 全局数据概览,跨部门对比,发现问题部门 | +| **讲师** | 本部门数据,学员学习进度,考试成绩分布 | +| **学员** | 个人学习记录,成绩趋势,与平均水平对比 | + +### 1.3 功能边界 + +| 范围 | 说明 | +|------|------| +| **包含** | 数据看板、培训统计、考试统计、学员统计、数据导出 | +| **不包含** | 实时监控、预测分析、自定义报表设计器 | + +--- + +## 二、功能清单 + +### 2.1 模块总览 + +``` +统计报表中心 +├── 2.1 数据概览看板 +│ ├── 关键指标卡片 +│ ├── 趋势图表 +│ └── 快捷入口 +│ +├── 2.2 培训统计 +│ ├── 培训计划完成率 +│ ├── 知识学习统计 +│ └── 部门培训排名 +│ +├── 2.3 考试统计 +│ ├── 考试通过率 +│ ├── 成绩分布 +│ └── 题目正确率分析 +│ +├── 2.4 学员统计 +│ ├── 学员学习排行 +│ ├── 个人学习报告 +│ └── 学习时长统计 +│ +└── 2.5 数据导出 + ├── Excel 导出 + └── 定期报表 +``` + +--- + +## 三、详细需求 + +### 3.1 数据概览看板 + +#### 3.1.1 功能描述 + +为不同角色提供个性化的数据概览首页,一目了然掌握关键指标。 + +#### 3.1.2 管理员看板 + +**关键指标卡片(4个)**: + +| 指标 | 计算方式 | 对比 | +|------|----------|------| +| 总学员数 | 状态=启用的学员数量 | 较上月增减 | +| 本月培训完成率 | 本月已完成培训人次 / 本月应完成培训人次 | 较上月变化 | +| 本月考试通过率 | 本月通过考试人次 / 本月参考人次 | 较上月变化 | +| 活跃学员数 | 本月有学习行为的学员数 | 较上月增减 | + +**趋势图表**: +- 近6个月培训完成率趋势(折线图) +- 近6个月考试通过率趋势(折线图) +- 部门培训完成率排名(横向柱状图,TOP 8) + +**快捷入口**: +- 查看详细培训报表 +- 查看详细考试报表 +- 导出月度汇总 + +#### 3.1.3 讲师看板 + +**关键指标卡片(4个)**: + +| 指标 | 范围 | +|------|------| +| 本部门学员数 | 本部门 | +| 本部门培训完成率 | 本部门本月 | +| 本部门考试通过率 | 本部门本月 | +| 待批阅数 | 如有主观题(V2暂无) | + +**趋势图表**: +- 本部门近6个月培训完成率趋势 +- 本部门学员学习进度分布(饼图:已完成/进行中/未开始) + +#### 3.1.4 学员看板 + +**关键指标卡片(4个)**: + +| 指标 | 说明 | +|------|------| +| 我的培训进度 | 已完成/总计划数 | +| 我的考试成绩 | 最近一次考试分数 | +| 学习排名 | 在本部门的排名 | +| 累计学习时长 | 总学习时长 | + +**图表**: +- 我的成绩趋势(近5次考试) +- 与部门平均分对比 + +#### 3.1.5 验收标准 + +```gherkin +Feature: 数据概览看板 + +Scenario: 管理员查看全局看板 + Given 我是管理员角色 + When 我进入统计报表中心 + Then 我应该看到4个关键指标卡片 + And 我应该看到培训完成率趋势图 + And 我应该看到部门排名图表 + And 所有数据应反映全平台数据 + +Scenario: 讲师查看部门看板 + Given 我是讲师角色 + When 我进入统计报表中心 + Then 所有数据应仅包含本部门数据 + And 我不应该看到其他部门的数据 + +Scenario: 指标卡片显示环比变化 + Given 当前月份培训完成率为 80% + And 上月培训完成率为 75% + When 我查看培训完成率卡片 + Then 应显示 "+5%" 的环比增长标记 + And 增长应显示为绿色 +``` + +--- + +### 3.2 培训统计 + +#### 3.2.1 培训计划完成率 + +**筛选条件**: +- 时间范围:本月/本季度/本年度/自定义 +- 部门:全部/指定部门(管理员可选) +- 培训计划:全部/指定计划 + +**统计维度**: + +| 维度 | 指标 | +|------|------| +| 按计划 | 计划名称、应参加人数、实际完成人数、完成率 | +| 按部门 | 部门名称、计划数、完成率、排名 | +| 按时间 | 月度完成率趋势 | + +**列表展示**: + +| 培训计划 | 部门 | 应参加 | 已完成 | 完成率 | 状态 | +|----------|------|--------|--------|--------|------| +| 2026Q1安全培训 | 救援一部 | 50 | 45 | 90% | 进行中 | +| 新员工入职培训 | 救援二部 | 20 | 20 | 100% | 已结束 | + +#### 3.2.2 知识学习统计 + +**统计内容**: +- 知识总数、已发布数 +- 学习总人次 +- 热门知识 TOP 10(按学习人次) +- 最少学习知识(提醒优化或下架) + +#### 3.2.3 验收标准 + +```gherkin +Feature: 培训统计 + +Scenario: 按部门查看培训完成率 + Given 我是管理员 + When 我选择按部门维度查看 + Then 应显示所有部门的培训完成率列表 + And 列表应按完成率降序排列 + And 应显示部门排名 + +Scenario: 筛选指定时间范围 + Given 我在培训统计页面 + When 我选择时间范围为 "2026年1月" + Then 所有数据应仅包含该月份的培训数据 + +Scenario: 导出培训报表 + Given 我在培训统计页面 + When 我点击导出按钮 + Then 应下载 Excel 文件 + And 文件应包含当前筛选条件下的所有数据 +``` + +--- + +### 3.3 考试统计 + +#### 3.3.1 考试通过率 + +**筛选条件**: +- 时间范围:本月/本季度/本年度/自定义 +- 部门:全部/指定部门 +- 考试:全部/指定考试 + +**统计指标**: + +| 指标 | 说明 | +|------|------| +| 参考人数 | 实际参加考试的人数 | +| 通过人数 | 分数 >= 及格线的人数 | +| 通过率 | 通过人数 / 参考人数 | +| 平均分 | 所有考生的平均分数 | +| 最高分 / 最低分 | 分数极值 | + +#### 3.3.2 成绩分布 + +**图表展示**: +- 分数段分布(柱状图):0-59 / 60-69 / 70-79 / 80-89 / 90-100 +- 各部门平均分对比(横向柱状图) + +**列表展示**: + +| 考试名称 | 参考人数 | 通过率 | 平均分 | 最高分 | 最低分 | +|----------|----------|--------|--------|--------|--------| +| 安全规范考核 | 120 | 85% | 78.5 | 98 | 42 | +| 月度技能测试 | 95 | 72% | 71.2 | 100 | 35 | + +#### 3.3.3 题目正确率分析 + +**功能描述**:分析每道题的正确率,发现难题和易错题。 + +**统计内容**: + +| 字段 | 说明 | +|------|------| +| 题目内容 | 题干摘要(前50字) | +| 题型 | 单选/多选/判断 | +| 作答人次 | 该题被作答的总次数 | +| 正确率 | 正确人次 / 作答人次 | +| 错误选项分布 | 各错误选项的选择占比 | + +**排序**: +- 默认按正确率升序(易错题优先) +- 可切换按作答人次排序 + +**用途**: +- 正确率 < 30% 的题目:可能题目有问题或知识点难度大 +- 正确率 > 95% 的题目:可能过于简单 + +#### 3.3.4 验收标准 + +```gherkin +Feature: 考试统计 + +Scenario: 查看考试成绩分布 + Given 我在考试统计页面 + When 我选择某场考试 + Then 应显示分数段分布柱状图 + And 应显示通过率、平均分等指标 + +Scenario: 查看题目正确率 + Given 我在考试统计页面 + When 我点击 "题目分析" + And 选择某场考试 + Then 应显示该考试所有题目的正确率 + And 默认按正确率升序排列 + And 可以查看每道题的错误选项分布 + +Scenario: 识别易错题 + Given 某道题的正确率为 25% + When 我查看题目分析列表 + Then 该题应标记为 "易错题" + And 应显示主要错误选项 +``` + +--- + +### 3.4 学员统计 + +#### 3.4.1 学员学习排行 + +**排行维度**: +- 学习时长排行(本月/本季度) +- 考试成绩排行(平均分/最高分) +- 培训完成数排行 + +**展示内容**: + +| 排名 | 学员 | 部门 | 学习时长 | 完成培训数 | 平均分 | +|------|------|------|----------|------------|--------| +| 1 | 张三 | 救援一部 | 45h | 8 | 92 | +| 2 | 李四 | 救援二部 | 42h | 7 | 88 | + +**范围控制**: +- 管理员:可查看全平台排行 +- 讲师:仅可查看本部门排行 +- 学员:可查看本部门排行,自己会高亮显示 + +#### 3.4.2 个人学习报告 + +**入口**: +- 讲师点击学员姓名进入 +- 学员点击"我的学习报告"进入 + +**报告内容**: + +| 模块 | 内容 | +|------|------| +| 基本信息 | 姓名、部门、入职时间、角色 | +| 学习概览 | 累计学习时长、完成培训数、参加考试数 | +| 培训记录 | 参加的培训计划列表,包含进度和状态 | +| 考试记录 | 参加的考试列表,包含成绩和是否通过 | +| 成绩趋势 | 近10次考试成绩折线图 | +| 能力雷达图 | 按知识分类的掌握程度(基于考试正确率) | + +#### 3.4.3 学习时长统计 + +**统计规则**: +- 知识学习:从打开到关闭/切换的时长(上限30分钟/次) +- 视频学习:实际播放时长 +- 考试时长:从开始到交卷的时长 + +**展示**: +- 个人:日/周/月学习时长统计 +- 部门:部门平均学习时长、学习时长分布 + +#### 3.4.4 验收标准 + +```gherkin +Feature: 学员统计 + +Scenario: 查看学习排行榜 + Given 我是讲师 + When 我进入学员排行榜页面 + Then 应显示本部门学员排行 + And 不应显示其他部门学员 + +Scenario: 查看个人学习报告 + Given 我是讲师 + When 我点击某学员的姓名 + Then 应进入该学员的学习报告页面 + And 应显示该学员的培训记录 + And 应显示该学员的考试记录 + And 应显示成绩趋势图 + +Scenario: 学员查看自己的报告 + Given 我是学员 + When 我点击 "我的学习报告" + Then 应显示我的学习报告 + And 应显示我在部门中的排名 +``` + +--- + +### 3.5 数据导出 + +#### 3.5.1 Excel 导出 + +**支持导出的报表**: + +| 报表 | 内容 | 权限 | +|------|------|------| +| 培训完成率报表 | 按计划/部门的完成率明细 | 管理员、讲师 | +| 考试成绩报表 | 学员成绩明细 | 管理员、讲师 | +| 学员学习报表 | 学员学习时长和完成情况 | 管理员、讲师 | +| 题目分析报表 | 题目正确率明细 | 管理员、讲师 | + +**导出规则**: +- 导出当前筛选条件下的数据 +- 文件名格式:`报表类型_日期_导出人.xlsx` +- 单次导出上限:10000 条记录 + +#### 3.5.2 验收标准 + +```gherkin +Feature: 数据导出 + +Scenario: 导出考试成绩报表 + Given 我在考试统计页面 + And 当前筛选条件为 "2026年1月" + When 我点击导出按钮 + Then 应下载 Excel 文件 + And 文件名应为 "考试成绩报表_20260204_张三.xlsx" + And 数据应仅包含2026年1月的考试成绩 + +Scenario: 导出数据量限制 + Given 当前筛选条件下有 15000 条记录 + When 我点击导出按钮 + Then 应提示 "数据量超过限制,请缩小筛选范围" +``` + +--- + +## 四、数据模型(建议) + +### 4.1 新增统计表(可选,用于性能优化) + +#### 日统计汇总表 (stat_daily_summary) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| stat_date | Date | 统计日期 | +| department_id | Long | 部门ID(0=全平台) | +| total_users | Integer | 学员总数 | +| active_users | Integer | 活跃学员数 | +| training_complete_count | Integer | 培训完成人次 | +| exam_pass_count | Integer | 考试通过人次 | +| exam_total_count | Integer | 考试参与人次 | +| total_learning_minutes | Long | 总学习时长(分钟) | + +#### 学习时长记录表 (stat_learning_log) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| user_id | Long | 用户ID | +| knowledge_id | Long | 知识ID | +| start_time | DateTime | 开始时间 | +| end_time | DateTime | 结束时间 | +| duration_seconds | Integer | 学习时长(秒) | +| create_time | DateTime | 创建时间 | + +--- + +## 五、页面原型 + +### 5.1 管理员数据看板 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 统计报表中心 │ +├────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ 总学员数 │ │ 培训完成率 │ │ 考试通过率 │ │ 活跃学员数 │ │ +│ │ 486 │ │ 82% │ │ 78% │ │ 312 │ │ +│ │ +12 ↑ │ │ +5% ↑ │ │ -2% ↓ │ │ +28 ↑ │ │ +│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ +│ │ +│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ 培训完成率趋势 │ │ 部门培训完成率排名 │ │ +│ │ │ │ │ │ +│ │ 100%│ │ │ 救援一部 ████████████ 92% │ │ +│ │ 80%│ ╭──╮ ╭──╮ │ │ 救援三部 ██████████ 85% │ │ +│ │ 60%│╭──╯ ╰──╮╭╯ ╰── │ │ 救援二部 █████████ 80% │ │ +│ │ 40%│ ╰ │ │ 救援四部 ███████ 72% │ │ +│ │ └───────────────── │ │ 综合部 ██████ 65% │ │ +│ │ 9 10 11 12 1 2 │ │ │ │ +│ └─────────────────────────────────┘ └─────────────────────────────────┘ │ +│ │ +│ [查看培训详情] [查看考试详情] [导出月度报表] │ +│ │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +### 5.2 考试统计页面 + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ 统计报表中心 > 考试统计 │ +├────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ 时间范围: [本月 ▼] 部门: [全部 ▼] 考试: [全部 ▼] [查询] [导出] │ +│ │ +│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ 成绩分布 │ │ 汇总指标 │ │ +│ │ │ │ │ │ +│ │ ┌───┐ │ │ 参考人数: 320 │ │ +│ │ │ │ ┌───┐ │ │ 通过人数: 256 │ │ +│ │ ┌───┐│ │ │ │ ┌───┐ │ │ 通过率: 80% │ │ +│ │ │ ││ │ │ │ │ │ ┌───┐ │ │ 平均分: 76.5 │ │ +│ │ │ ││ │ │ │ │ │ │ │ │ │ 最高分: 100 │ │ +│ │ └───┘└───┘ └───┘ └───┘ └───┘ │ │ 最低分: 32 │ │ +│ │ 0-59 60-69 70-79 80-89 90-100 │ │ │ │ +│ └─────────────────────────────────┘ └─────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────────────────┐ │ +│ │ 考试名称 参考人数 通过率 平均分 最高分 操作 │ │ +│ ├──────────────────────────────────────────────────────────────────────┤ │ +│ │ 安全规范考核 120 85% 78.5 98 [详情][分析] │ │ +│ │ 月度技能测试 95 72% 71.2 100 [详情][分析] │ │ +│ │ 新员工入职考试 45 91% 82.3 96 [详情][分析] │ │ +│ └──────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ < 1 2 3 > │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 六、非功能需求 + +| 类型 | 要求 | +|------|------| +| **性能** | 看板页面加载时间 < 2秒 | +| **性能** | 报表查询响应时间 < 3秒(万级数据) | +| **兼容性** | 支持 Chrome、Edge、Firefox 最新版本 | +| **数据安全** | 严格按角色和部门隔离数据 | +| **导出** | Excel 导出支持 .xlsx 格式 | + +--- + +## 七、不做什么(Out of Scope) + +| 排除项 | 原因 | +|--------|------| +| 实时数据监控 | V2 不需要实时性,T+1 统计即可 | +| 自定义报表设计器 | 复杂度高,用户需求不明确 | +| 数据预测/AI分析 | 数据量不足,价值有限 | +| 打印报表 | 使用 Excel 导出替代 | + +--- + +## 八、里程碑建议 + +| 阶段 | 内容 | 建议周期 | +|------|------|----------| +| M1 | 数据看板(管理员+讲师+学员) | - | +| M2 | 培训统计 + 考试统计 | - | +| M3 | 学员统计 + 数据导出 | - | +| M4 | 测试 + 优化 | - | + +--- + +## 九、开放问题 + +| 问题 | 待确认 | +|------|--------| +| 学习时长统计是否需要精确到秒级? | 建议分钟级即可 | +| 是否需要支持定时自动发送报表邮件? | 建议 V3 考虑 | +| 能力雷达图的维度如何定义? | 建议按知识分类一级目录 | + +--- + +**文档状态:待评审** diff --git a/training/codes/training-system/docs/features/student.md b/training/codes/training-system/docs/features/student.md new file mode 100644 index 0000000..4f3c9ed --- /dev/null +++ b/training/codes/training-system/docs/features/student.md @@ -0,0 +1,664 @@ +# 学员端功能需求文档 + +> 版本:V1.0.1 +> 创建日期:2026-01-15 +> 更新日期:2026-01-15 +> 状态:✅ 已批准(Supervisor 复审通过) + +--- + +## 版本更新记录 + +### V1.0.1 (2026-01-15) + +**根据Supervisor审查意见修订:** + +| 修订项 | 修订内容 | +|--------|----------| +| 知识学习入口 | 补充培训任务学习场景、返回逻辑、来源区分 | +| 断点续考机制 | 补充定时任务策略、超时判定规则、并发处理 | +| 排名计算规则 | 明确计算时机、统计范围、口径定义 | +| 学习时长防刷 | 新增活跃检测机制 | +| 数据表设计 | km_knowledge_progress 新增 department_id 字段 | +| 页面路径 | 调整为复用现有路径,按角色控制 | +| 解析显示策略 | 明确交卷后立即显示解析 | +| 非功能需求 | 补充可测试的性能指标 | + +--- + +## 一、概述 + +### 1.1 目标用户 +- **角色**:学员(STUDENT) +- **人数**:约450人 +- **入口**:PC浏览器(与管理端共用系统,按角色显示不同菜单) + +### 1.2 核心目标 +为学员提供便捷的在线学习平台,支持知识学习、在线考试、培训计划跟踪,帮助学员提升专业技能。 + +--- + +## 二、功能模块总览 + +| 模块 | 功能点 | 优先级 | +|------|--------|--------| +| 工作台 | 学习概览、待办事项 | P0 | +| 知识学习 | 知识浏览、学习进度、时长统计 | P0 | +| 在线考试 | 考试列表、在线答题、断点续考、成绩排名 | P0 | +| 我的培训 | 培训计划、进度跟踪、完成判定 | P0 | +| 个人中心 | 学习记录、考试记录、证书(预留) | P1 | + +--- + +## 三、详细功能需求 + +### 3.1 工作台(首页) + +#### 用户故事 +> 作为学员,我希望登录后看到学习概览和待办事项,以便快速了解学习状态和接下来要做的事。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 学习统计卡片 | 显示:待学习课程数、待完成考试数、培训进度百分比 | +| 我的培训计划 | 列表展示分配给我的培训计划(最多显示5条),显示进度 | +| 待完成考试 | 列表展示待完成的考试(最多显示5条),显示截止时间 | +| 快捷入口 | 点击可直接进入对应详情页 | + +#### 验收标准 +- [ ] 登录后默认进入工作台页面 +- [ ] 统计数据实时准确(待学习=未完成的必修知识数,待考试=未通过的必考考试数) +- [ ] 培训进度百分比 = 已完成必修项 / 总必修项 × 100% +- [ ] 点击培训计划可跳转到计划详情 +- [ ] 点击考试可跳转到考试页面 +- [ ] 只显示"进行中"状态的培训计划和处于"时间窗口内"的考试 + +--- + +### 3.2 知识学习 + +#### 用户故事 +> 作为学员,我希望浏览本部门的知识库,学习文档和视频,并能看到自己的学习进度。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 知识分类导航 | 左侧树形结构展示知识分类 | +| 知识列表 | 按分类筛选,显示知识标题、类型、学习状态 | +| 知识详情/预览 | 文档在线预览、视频在线播放 | +| 学习进度记录 | 记录学习完成状态、学习时长 | +| 强制学习机制 | 视频必须播放完成、文档必须阅读完成才记录为"已完成" | + +#### 3.2.1 知识列表页 + +**页面元素**: +- 分类筛选(树形) +- 知识卡片列表:标题、类型图标(文档/视频)、学习状态标签、时长/大小 +- 搜索框(按标题搜索) + +**学习状态**: +| 状态 | 说明 | 显示样式 | +|------|------|----------| +| 未学习 | 从未打开过 | 灰色标签 | +| 学习中 | 已开始但未完成 | 蓝色标签 + 进度% | +| 已完成 | 满足完成条件 | 绿色标签 ✓ | + +#### 3.2.2 知识详情页 + +**文档类型**: +- 在线预览(PDF直接显示,Word/Excel/PPT转换预览) +- 学习计时:进入页面开始计时,离开页面停止 +- 完成条件:停留时间 ≥ 预估阅读时间(可配置,默认按页数计算) + +**视频类型**: +- 在线播放(支持进度条、全屏) +- 播放进度记录:记录当前播放位置,下次打开从断点继续 +- 完成条件:播放进度 ≥ 90%(可配置) +- 禁止拖动快进(强制学习模式下) + +#### 3.2.3 学习入口场景(🔴 修订项1) + +**场景一:自由学习(从知识库进入)** +- 入口:左侧菜单"知识库" → 知识列表 → 知识详情 +- 学习完成后:停留在知识详情页,可点击"返回列表" +- 进度记录:标记来源为 `FREE`(自由学习) + +**场景二:培训任务学习(从培训计划进入)** +- 入口:培训详情页 → 点击知识项 → 知识详情 +- 页面顶部显示:返回培训计划入口(如:"← 返回《2026年Q1安全规范培训》") +- 学习完成后: + - 自动弹出提示:"学习完成!是否返回培训计划?" + - 用户可选择【返回培训】或【继续浏览】 +- 进度记录:标记来源为 `TRAINING`(培训任务),关联 `plan_id` + +**进度记录来源区分**: +| 来源 | 场景 | 影响 | +|------|------|------| +| FREE | 自由学习 | 仅记录个人学习进度 | +| TRAINING | 培训任务学习 | 同时更新培训计划进度 | + +> **说明**:无论从哪个入口学习,学习时长和完成状态都会记录。区分来源是为了后续统计分析(如:培训驱动的学习 vs 自主学习)。 + +#### 3.2.4 学习时长防刷机制(🟡 优化项1) + +**文档类型防刷策略**: +- 每60秒检测一次用户活跃状态 +- 活跃判定:60秒内有鼠标移动、点击、滚动任一事件 +- 非活跃时:暂停计时,页面显示"检测到您暂时离开,学习计时已暂停" +- 恢复活跃后:继续计时 + +**视频类型防刷策略**: +- 依赖视频播放事件,暂停时不计时长 +- 无需额外活跃检测 + +**多标签页处理**: +- 同一知识同时只能在一个标签页学习 +- 检测到多标签页打开同一知识时,后打开的标签页提示"该知识已在其他窗口学习中" + +#### 验收标准 +- [ ] 只显示本部门、已发布状态的知识 +- [ ] 分类树正确展示,点击分类筛选对应知识 +- [ ] 文档能正常在线预览(至少支持PDF) +- [ ] 视频能正常播放,支持暂停、音量调节 +- [ ] 视频播放进度自动保存,刷新页面后从断点继续 +- [ ] 强制学习模式下,视频不能快进跳过 +- [ ] 学习时长准确记录(精确到秒) +- [ ] 满足完成条件后自动标记为"已完成" +- [ ] 学习进度与培训计划进度联动 +- [ ] 🔴 从培训计划进入的知识学习,顶部显示返回入口 +- [ ] 🔴 学习完成后弹出返回培训计划提示(仅培训任务学习场景) +- [ ] 🟡 文档学习时,非活跃状态暂停计时 +- [ ] 🟡 同一知识不能在多个标签页同时学习 + +--- + +### 3.3 在线考试 + +#### 用户故事 +> 作为学员,我希望参加分配给我的考试,并能查看成绩和排名。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 考试列表 | 展示分配给我的所有考试 | +| 考试详情 | 显示考试信息、我的考试记录 | +| 在线答题 | 单选/多选/判断题作答 | +| 断点续考 | 中途退出后可继续答题 | +| 自动交卷 | 时间到自动提交 | +| 成绩查看 | 显示得分、答案解析 | +| 成绩排名 | 显示我在本次考试中的排名 | + +#### 3.3.1 考试列表页 + +**列表字段**: +| 字段 | 说明 | +|------|------| +| 考试名称 | 点击进入考试详情 | +| 考试时间 | 开始时间 ~ 结束时间 | +| 考试状态 | 未开始/进行中/已结束 | +| 我的状态 | 未参加/考试中/已完成 | +| 最高成绩 | 多次考试取最高分 | +| 剩余次数 | 最大次数 - 已考次数 | +| 操作 | 进入考试/查看成绩 | + +**筛选条件**: +- 考试状态:全部/进行中/已结束 +- 我的状态:全部/未完成/已完成 + +#### 3.3.2 考试详情页(进入考试前) + +**显示信息**: +- 考试名称、考试时间窗口 +- 考试规则:时长、总分、及格线、最大次数 +- 我的考试记录表格(第N次、得分、是否通过、时间) +- 【开始考试】/【继续考试】按钮 + +**按钮逻辑**: +| 场景 | 按钮状态 | +|------|----------| +| 不在时间窗口内 | 禁用,提示"考试未开始"或"考试已结束" | +| 次数已用完 | 禁用,提示"考试次数已用完" | +| 有进行中的考试记录 | 显示【继续考试】 | +| 可以开始新考试 | 显示【开始考试】 | + +#### 3.3.3 在线答题页 + +**页面布局**: +``` +┌─────────────────────────────────────────────────────────┐ +│ 考试名称 剩余时间: 45:23 │ +├─────────────────────────────────────────────────────────┤ +│ 第 3 题 / 共 20 题 [单选题] │ +│ ───────────────────────────────────────────────────── │ +│ 题干内容... │ +│ │ +│ ○ A. 选项A │ +│ ● B. 选项B(已选中) │ +│ ○ C. 选项C │ +│ ○ D. 选项D │ +├─────────────────────────────────────────────────────────┤ +│ 答题卡: [1✓] [2✓] [3●] [4○] [5○] ... │ +├─────────────────────────────────────────────────────────┤ +│ [上一题] [下一题] [交卷] │ +└─────────────────────────────────────────────────────────┘ +``` + +**功能说明**: +| 功能 | 说明 | +|------|------| +| 倒计时 | 显示剩余时间,最后5分钟变红色提醒 | +| 题目导航 | 点击答题卡数字可跳转到对应题目 | +| 答案自动保存 | 选择答案后自动保存到服务器(每30秒或切题时) | +| 上一题/下一题 | 切换题目 | +| 交卷 | 弹出确认框,确认后提交 | +| 自动交卷 | 时间结束自动提交当前答案 | + +**断点续考机制**: +- 考试开始后创建考试记录,状态为"进行中" +- 答案实时保存到服务器 +- 中途关闭浏览器/断网,考试记录保持"进行中" +- 重新打开考试,检测到进行中记录,恢复到上次状态 +- 倒计时从剩余时间继续(服务端计算:考试时长 - 已用时间) +- 超时未提交的考试,由定时任务自动交卷 + +#### 3.3.5 超时自动交卷机制(🔴 修订项2) + +**超时判定规则**: +``` +超时时间点 = 考试记录开始时间 + 考试时长(分钟) +``` + +**示例**: +- 学员A在 10:00 开始考试,考试时长60分钟 +- 超时时间点 = 10:00 + 60分钟 = 11:00 +- 无论考试时间窗口是否结束,11:00后该考试记录即视为超时 + +**定时任务策略**: +| 配置项 | 值 | 说明 | +|--------|-----|------| +| 执行频率 | 每1分钟 | Cron: `0 */1 * * * ?` | +| 扫描范围 | 状态为IN_PROGRESS的考试记录 | 只处理进行中的记录 | +| 超时判定 | 当前时间 > 开始时间 + 考试时长 | 精确到秒 | +| 处理动作 | 自动提交当前已保存的答案,计算成绩 | 标记为系统自动交卷 | + +**并发提交处理(乐观锁)**: +``` +场景:学员在超时前1秒点击交卷,同时定时任务也在处理该记录 +``` + +**处理策略**: +1. 考试记录表增加 `version` 字段(乐观锁) +2. 提交时检查 `status = IN_PROGRESS AND version = 当前版本` +3. 更新时 `version = version + 1` +4. 若更新失败(version已变),说明已被其他请求处理,直接返回已提交的结果 + +**状态流转**: +``` +IN_PROGRESS ──用户主动交卷──▶ SUBMITTED(来源:USER) + │ + └──────定时任务超时交卷──▶ SUBMITTED(来源:SYSTEM_TIMEOUT) +``` + +**交卷来源标记**: +| 来源 | 说明 | +|------|------| +| USER | 用户主动交卷 | +| SYSTEM_TIMEOUT | 系统超时自动交卷 | + +> **注意**:即使考试时间窗口已结束,只要考试记录未超时,学员仍可继续答题(但无法开始新的考试) + +#### 3.3.6 考试结果页 + +**显示内容**: +| 项目 | 说明 | +|------|------| +| 得分 | 大字显示,及格绿色/不及格红色 | +| 是否通过 | 通过✓ / 未通过✗ | +| 排名 | "您的成绩排名第 X 名(共 Y 人参加)" | +| 答题详情 | 每道题的正确答案、我的答案、解析 | +| 操作按钮 | 【再考一次】(有次数)/ 【返回列表】 | + +**答案解析显示策略(🟡 优化项4)**: +- 交卷后立即显示所有题目的答案解析 +- 无论考试是否通过,都显示解析 +- 目的:培训学习为主,帮助学员理解错误原因 + +#### 3.3.7 排名计算规则(🔴 修订项3) + +**计算时机**: +- 实时计算:每次交卷时计算排名 +- 非定时计算:不采用批量定时计算方式 + +**统计范围**: +- 范围:当前考试的所有参与者(被分配且已交卷的学员) +- 跨部门:若考试分配给多个部门,排名为所有参与者的总排名 + +**排名规则**: +| 优先级 | 规则 | 说明 | +|--------|------|------| +| 1 | 最高成绩降序 | 成绩高的排名靠前 | +| 2 | 达到最高成绩的时间升序 | 成绩相同时,先达到该成绩的排名靠前 | + +**"共Y人参加"统计口径**: +| 统计项 | 是否计入 | +|--------|----------| +| 已交卷学员 | ✅ 计入 | +| 正在考试中(未交卷) | ❌ 不计入 | +| 被分配但未参加 | ❌ 不计入 | + +**排名示例**: +``` +考试:安全规范考核 +分配对象:部门A(30人)、部门B(20人) + +参与情况: +- 部门A:25人已交卷,5人未参加 +- 部门B:18人已交卷,2人正在考试中 + +排名统计:共 43 人参加(25 + 18 = 43) + +排名结果: +第1名:张三(95分,首次达到时间 10:30) +第2名:李四(95分,首次达到时间 10:45) +第3名:王五(90分) +... +``` + +**实现建议**: +- 交卷时实时计算排名并返回 +- 排名计算SQL示例思路: + ```sql + SELECT user_id, MAX(score) as best_score, MIN(submit_time) as first_best_time + FROM ex_exam_record + WHERE exam_id = ? AND status = 'SUBMITTED' + GROUP BY user_id + ORDER BY best_score DESC, first_best_time ASC + ``` + +#### 验收标准 +- [ ] 只显示分配给当前学员的考试 +- [ ] 考试状态、我的状态准确显示 +- [ ] 不在时间窗口内无法进入考试 +- [ ] 次数用完无法再次考试 +- [ ] 答题过程中答案实时保存 +- [ ] 刷新页面/重新进入可继续答题(断点续考) +- [ ] 倒计时准确,超时自动交卷 +- [ ] 交卷后立即显示成绩和排名 +- [ ] 答案解析正确显示 +- [ ] 多次考试取最高分显示 +- [ ] 🔴 定时任务每分钟执行,自动处理超时考试记录 +- [ ] 🔴 并发交卷场景下数据一致(乐观锁机制) +- [ ] 🔴 排名实时计算,仅统计已交卷学员 +- [ ] 🔴 排名规则:最高分优先,同分按首次达到时间排序 + +--- + +### 3.4 我的培训 + +#### 用户故事 +> 作为学员,我希望查看分配给我的培训计划,跟踪学习进度,完成培训任务。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 培训列表 | 展示分配给我的培训计划 | +| 培训详情 | 显示计划内容、学习进度 | +| 进度跟踪 | 实时显示知识学习、考试完成情况 | +| 完成判定 | 根据必修项判定培训是否完成 | +| 证书预留 | 培训完成后预留证书/徽章展示位置 | + +#### 3.4.1 培训列表页 + +**列表字段**: +| 字段 | 说明 | +|------|------| +| 培训名称 | 点击进入详情 | +| 培训周期 | 开始日期 ~ 结束日期 | +| 状态 | 未开始/进行中/已结束 | +| 我的进度 | 进度条 + 百分比 | +| 完成状态 | 未完成/已完成 | + +#### 3.4.2 培训详情页 + +**页面结构**: +``` +┌─────────────────────────────────────────────────────────┐ +│ 2026年Q1安全规范培训 │ +│ ───────────────────────────────────────────────────── │ +│ 培训周期:2026-01-01 ~ 2026-03-31 │ +│ 培训目标:掌握高速公路救援安全规范... │ +│ 我的进度:████████░░░░ 75% │ +├─────────────────────────────────────────────────────────┤ +│ 📚 学习内容 (3/5) │ +│ ├─ ✅ 高速救援SOP手册 [必修] [已完成] │ +│ ├─ ✅ 安全操作视频 [必修] [已完成] │ +│ ├─ 🔵 应急处理流程 [必修] [学习中 60%] │ +│ ├─ ⚪ 设备使用指南 [选修] [未学习] │ +│ └─ ✅ 案例分析 [选修] [已完成] │ +├─────────────────────────────────────────────────────────┤ +│ 📝 考试任务 (1/2) │ +│ ├─ ✅ 安全规范考核 [必考] [已通过 85分] │ +│ └─ ⚪ 操作技能测试 [必考] [未参加] │ +├─────────────────────────────────────────────────────────┤ +│ 🏆 完成奖励 │ +│ └─ 完成培训后可获得【安全规范认证】徽章(敬请期待) │ +└─────────────────────────────────────────────────────────┘ +``` + +**进度计算规则**: + +``` +培训进度 = (已完成必修知识数 + 已通过必考考试数) / (必修知识总数 + 必考考试总数) × 100% +``` + +**完成判定规则**: +| 条件 | 说明 | +|------|------| +| 所有必修知识已完成 | ✓ | +| 所有必考考试已通过 | ✓ | +| 选修知识 | 不影响完成判定 | +| 选考考试 | 不影响完成判定 | + +#### 验收标准 +- [ ] 只显示分配给当前学员的培训计划 +- [ ] 进度百分比计算准确(只计算必修项) +- [ ] 学习内容、考试任务分开展示 +- [ ] 显示每项的必修/选修、必考/选考标签 +- [ ] 点击知识内容可跳转到知识详情页 +- [ ] 点击考试任务可跳转到考试页面 +- [ ] 所有必修项完成后,培训状态变为"已完成" +- [ ] 证书/徽章区域预留(V1显示"敬请期待") + +--- + +### 3.5 个人中心 + +#### 用户故事 +> 作为学员,我希望查看自己的学习记录、考试历史和获得的证书。 + +#### 功能清单 + +| 功能项 | 说明 | +|--------|------| +| 个人信息 | 显示姓名、部门、角色 | +| 学习统计 | 总学习时长、完成课程数 | +| 学习记录 | 知识学习历史列表 | +| 考试记录 | 考试历史列表 | +| 我的证书 | 证书/徽章展示(预留) | + +#### 验收标准 +- [ ] 正确显示当前用户信息 +- [ ] 学习时长统计准确(累计所有知识学习时长) +- [ ] 学习记录按时间倒序展示 +- [ ] 考试记录显示每次考试的详情 +- [ ] 证书模块预留(V1显示"暂无证书") + +--- + +## 四、数据契约补充 + +### 4.1 新增/修改实体 + +#### 知识学习进度表 (km_knowledge_progress) - 新增 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| user_id | Long | 学员ID | +| knowledge_id | Long | 知识ID | +| department_id | Long | **🟡 新增** 所属部门(冗余字段,便于按部门统计) | +| status | Enum | 状态:NOT_STARTED/IN_PROGRESS/COMPLETED | +| progress | Integer | 进度百分比(0-100) | +| duration | Long | 学习时长(秒) | +| video_position | Long | 视频播放位置(秒),仅视频类型 | +| source | Enum | **🔴 新增** 学习来源:FREE/TRAINING | +| plan_id | Long | **🔴 新增** 关联培训计划ID(来源为TRAINING时有值) | +| start_time | DateTime | 首次学习时间 | +| complete_time | DateTime | 完成时间 | +| update_time | DateTime | 最后更新时间 | + +#### 考试记录表 (ex_exam_record) - 补充字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| status | Enum | **新增** 状态:IN_PROGRESS/SUBMITTED | +| last_save_time | DateTime | **新增** 最后保存时间(断点续考用) | +| version | Integer | **🔴 新增** 乐观锁版本号 | +| submit_source | Enum | **🔴 新增** 交卷来源:USER/SYSTEM_TIMEOUT | + +### 4.2 新增枚举 + +```java +// 学习状态 +public enum LearningStatus { + NOT_STARTED, // 未学习 + IN_PROGRESS, // 学习中 + COMPLETED // 已完成 +} + +// 🔴 学习来源 +public enum LearningSource { + FREE, // 自由学习(从知识库直接进入) + TRAINING // 培训任务学习(从培训计划进入) +} + +// 考试记录状态 +public enum ExamRecordStatus { + IN_PROGRESS, // 考试中(用于断点续考) + SUBMITTED // 已提交 +} + +// 🔴 交卷来源 +public enum SubmitSource { + USER, // 用户主动交卷 + SYSTEM_TIMEOUT // 系统超时自动交卷 +} +``` + +--- + +## 五、接口预留(V2/V3功能) + +### 5.1 证书/徽章接口(预留) + +``` +POST /api/certificate/generate # 生成证书 +GET /api/certificate/my # 我的证书列表 +GET /api/certificate/{id} # 证书详情 +GET /api/badge/my # 我的徽章列表 +``` + +### 5.2 错题本接口(V2预留) + +``` +GET /api/wrong-questions # 错题列表 +POST /api/wrong-questions/review # 标记已复习 +``` + +--- + +## 六、非功能需求 + +| 项目 | 要求 | 测试指标(🟡 优化项5) | +|------|------|------------------------| +| 页面加载 | 页面加载 < 2秒 | 95%请求 < 2秒 | +| 考试提交 | 考试提交 < 1秒 | 99%请求 < 1秒 | +| 并发支持 | 支持100人同时在线考试 | 100并发答题,响应时间 < 500ms,成功率 > 99.9% | +| 答案保存 | 答案自动保存 | 保存成功率 > 99.99%,保存延迟 < 300ms | +| 数据安全 | 考试答案定时自动保存,防止数据丢失 | 断网重连后数据无丢失 | +| 兼容性 | 支持主流浏览器 | Chrome 90+、Edge 90+、Firefox 90+ | + +--- + +## 七、不做什么(Out of Scope) + +| 功能 | 原因 | +|------|------| +| 移动端适配 | V1仅支持PC浏览器 | +| 防作弊功能 | 产品确认不需要 | +| 学习日历 | 产品确认不需要 | +| 错题本 | V2规划 | +| 社交功能(评论、点赞) | V3规划 | + +--- + +## 八、页面清单(🟡 优化项3) + +> **说明**:采用与管理端复用同一套系统的方案,按角色控制菜单和功能显示,不使用独立的 `/student/` 路径前缀。 + +| 页面 | 路径 | 角色可见 | 说明 | +|------|------|----------|------| +| 工作台 | /index.html | 全角色 | 根据角色显示不同内容 | +| 知识列表 | /knowledge/list.html | 全角色 | 学员只读,讲师可管理 | +| 知识详情 | /knowledge/view.html?id={id} | 全角色 | 学员增加学习进度记录 | +| 考试列表 | /exam/my-exams.html | 学员 | 我的考试列表 | +| 考试详情 | /exam/detail.html?id={id} | 学员 | 考试信息页 | +| 在线答题 | /exam/answer.html?id={id} | 学员 | 答题页面 | +| 考试结果 | /exam/result.html?recordId={id} | 学员 | 成绩结果页 | +| 培训列表 | /training/my-training.html | 学员 | 我的培训列表 | +| 培训详情 | /training/detail.html?id={id} | 学员 | 培训计划详情 | +| 个人中心 | /profile/index.html | 全角色 | 个人信息页 | +| 学习记录 | /profile/learning.html | 学员 | 学习记录列表 | +| 考试记录 | /profile/exam-history.html | 学员 | 考试记录列表 | +| 我的证书 | /profile/certificate.html | 学员 | 证书展示(预留) | + +**菜单权限控制**: +| 菜单项 | 管理员 | 讲师 | 学员 | +|--------|--------|------|------| +| 工作台 | ✅ | ✅ | ✅ | +| 知识库 | ✅ 管理 | ✅ 管理 | ✅ 只读学习 | +| 考题管理 | ✅ | ✅ | ❌ | +| 试卷管理 | ✅ | ✅ | ❌ | +| 考试管理 | ✅ | ✅ | ❌ | +| 我的考试 | ❌ | ❌ | ✅ | +| 培训计划 | ✅ | ✅ | ❌ | +| 我的培训 | ❌ | ❌ | ✅ | +| 个人中心 | ✅ | ✅ | ✅ | +| 系统设置 | ✅ | ❌ | ❌ | + +--- + +## 九、修订项对照表 + +| 修订项 | 类型 | 章节位置 | 状态 | +|--------|------|----------|------| +| 知识学习入口场景 | 🔴 必须修正 | 3.2.3 | ✅ 已补充 | +| 断点续考超时机制 | 🔴 必须修正 | 3.3.5 | ✅ 已补充 | +| 排名计算规则 | 🔴 必须修正 | 3.3.7 | ✅ 已补充 | +| 学习时长防刷机制 | 🟡 建议优化 | 3.2.4 | ✅ 已采纳 | +| 数据表department_id | 🟡 建议优化 | 4.1 | ✅ 已采纳 | +| 页面路径设计 | 🟡 建议优化 | 8 | ✅ 已调整 | +| 答案解析显示策略 | 🟡 建议优化 | 3.3.6 | ✅ 已明确 | +| 非功能需求测试指标 | 🟡 建议优化 | 6 | ✅ 已补充 | + +--- + +**文档状态:✅ 已批准** + +> **Supervisor 复审结论**:通过 +> **复审日期**:2026-01-15 +> **下一阶段**:技术评审 → 开发 diff --git a/training/codes/training-system/docs/features/知识资源_prd.md b/training/codes/training-system/docs/features/知识资源_prd.md new file mode 100644 index 0000000..41638f5 --- /dev/null +++ b/training/codes/training-system/docs/features/知识资源_prd.md @@ -0,0 +1,562 @@ +# 知识资源模块 PRD + +> 版本:V1.1(定稿) +> 创建日期:2026-04-28 +> 定稿日期:2026-04-28 +> 状态:已定稿 +> 工作目录:knowledge-resource-20260428-1000 + +--- + +## 版本记录 + +| 版本 | 日期 | 说明 | +|------|------|------| +| V1.0-Draft | 2026-04-28 | Round 1 初稿 | +| V1.1-Draft | 2026-04-28 | Round 2:根据用户确认更新标签删除规则、文件夹层级上限(5层)、素材重新上架、ADMIN跨部门转移、文件大小上限确认 | +| V1.2-Draft | 2026-04-28 | Round 3:确认子文件夹可见范围默认继承父文件夹,可独立修改,修改父文件夹不级联更新子文件夹 | +| V1.1 | 2026-04-28 | 补充需求:上传存储改为腾讯云 COS;新增素材预览弹窗功能(支持放大,参照现有知识库实现)| + +--- + +## 一、模块定位 + +### 1.1 背景与目标 + +**背景**:现有「知识库」模块(km_* 表)定位于发布可供学员学习的结构化知识内容,强调发布/下架状态和学习进度跟踪。企业同时存在另一类需求——各部门需要一个素材仓库,用于沉淀和共享多种格式的原始素材(视频、音频、图片、文档),并通过灵活的文件夹层级和可见范围控制实现部门内/跨部门的素材管理。 + +[CONTEXT-003:现有知识库模块定位——聚焦可发布学习内容,与本模块定位互补而非替代] + +**目标**: +- 为企业各部门提供结构化的素材仓库,支持多格式文件的上传与管理 +- 通过「标签组+标签」体系为素材打上横向标签,便于检索和分类 +- 通过「文件夹可见范围」实现细粒度的访问控制,支持全公司/本部门/指定人员三级 + +### 1.2 模块边界 + +| 维度 | 知识资源模块(本模块)| 现有知识库模块 | +|------|------|------| +| 定位 | 素材仓库,原始素材管理 | 学习内容库,可发布学习 | +| 内容类型 | 视频、音频、图片、文档 | 文档、视频 | +| 状态机 | 正常/已下架(无发布流程)| 草稿/已发布/已下架 | +| 组织方式 | 多层文件夹(最多 5 层)| 多级分类树 | +| 可见控制 | 文件夹级:全公司/本部门/指定人员 | 部门隔离(同部门可见)| +| 标签体系 | 有(标签组+标签)| 无 | +| 学员访问 | V1 不开放,V2 规划 | 学员可访问已发布内容 | +| 数据表前缀 | kr_* | km_* | + +[CONTEXT-001:系统整体架构——模块并列关系确认] [CONTEXT-003:现有知识库模块边界] + +### 1.3 目标用户 + +[CONTEXT-001:6.1 角色定义] + +| 角色 | 操作范围 | 核心诉求 | +|------|------|------| +| 管理员(ADMIN) | 全平台所有部门的文件夹和素材 | 全局素材管理,标签体系维护 | +| 讲师(LECTURER) | 仅本部门(department_id 匹配)的文件夹和素材 | 本部门素材上传、文件夹组织 | +| 学员(STUDENT) | V1 不涉及(V2 按可见范围开放)| - | + +--- + +## 二、功能清单 + +### 2.1 标签管理 + +[CONTEXT-004:sys_department 表——标签组记录所属部门依赖此表] + +| 功能名称 | 功能描述 | 操作角色 | +|---------|---------|---------| +| 标签组列表 | 分页展示所有标签组,显示:组名、所属部门、创建人、标签数量、状态、创建时间 | ADMIN / LECTURER(本部门)| +| 新建标签组 | 创建标签组,记录所属部门和创建人 | ADMIN / LECTURER | +| 编辑标签组 | 修改标签组名称 | ADMIN / LECTURER(本部门)| +| 删除标签组 | 删除标签组(组下无任何标签时方可删除,否则提示先清空标签)| ADMIN / LECTURER(本部门)| +| 启用/停用标签组 | 切换标签组状态;停用后该组下所有标签对外不可见 | ADMIN / LECTURER(本部门)| +| 查看组内标签 | 展开查看该标签组下的所有标签列表 | ADMIN / LECTURER(本部门)| +| 新建标签 | 在指定标签组下新建标签 | ADMIN / LECTURER(本部门)| +| 编辑标签 | 修改标签名称 | ADMIN / LECTURER(本部门)| +| 删除标签 | 删除标签(有引用时弹出确认框,确认后历史关联软删除并标注「已删除」)| ADMIN / LECTURER(本部门)| +| 启用/停用标签 | 切换标签状态;停用后不可被素材选用,已关联素材保留历史关联 | ADMIN / LECTURER(本部门)| + +### 2.2 知识素材 + +[CONTEXT-004:sys_user——上传人记录依赖;sys_department——部门隔离依赖] + +#### 2.2.1 文件夹管理 + +| 功能名称 | 功能描述 | 操作角色 | +|---------|---------|---------| +| 文件夹树查看 | 展示当前部门下的多层文件夹树形结构 | ADMIN / LECTURER(本部门)| +| 新建文件夹 | 在当前层级下创建文件夹,必须设置可见范围 | ADMIN / LECTURER(本部门)| +| 重命名文件夹 | 修改文件夹名称 | ADMIN / LECTURER(本部门)| +| 删除文件夹 | 仅空文件夹(无子文件夹且无素材)可删除 | ADMIN / LECTURER(本部门)| +| 设置/修改可见范围 | 设置文件夹可见范围:全公司 / 本部门 / 指定人员 | ADMIN / LECTURER(本部门)| + +#### 2.2.2 知识素材管理 + +| 功能名称 | 功能描述 | 操作角色 | +|---------|---------|---------| +| 上传素材 | 上传视频/音频/图片/文档至指定文件夹,文件存储至腾讯云 COS,记录上传部门/上传人/上传时间 | ADMIN / LECTURER(本部门)| +| 素材列表 | 展示当前文件夹下的素材列表,含:文件名、类型、大小、上传部门、上传人、上传时间、状态 | ADMIN / LECTURER(本部门)| +| 素材预览 | 点击素材名称/缩略图,弹出预览框在线预览;预览框支持放大(全屏或最大化)| ADMIN / LECTURER(本部门)| +| 重命名素材 | 修改素材显示名称(不影响原文件名)| ADMIN / LECTURER(本部门)| +| 删除素材 | 逻辑删除素材(COS 存储文件保留,不物理删除)| ADMIN / LECTURER(本部门)| +| 下架素材 | 将素材标记为「已下架」,下架后不可被引用和查看 | ADMIN / LECTURER(本部门)| +| 批量转移 | 批量选择素材,转移到同部门下的其他文件夹 | ADMIN / LECTURER(本部门)| + +--- + +## 三、核心业务规则 + +### 3.1 标签管理规则 + +[CONTEXT-001:6.2 权限分层——部门级数据隔离原则] + +**标签组归属规则**: +- LECTURER 创建的标签组自动归属本部门(department_id 取 Token 中的 department_id) +- ADMIN 创建标签组时须选择所属部门 +- LECTURER 只能查看和操作本部门的标签组 + +**标签组状态规则**: +- 启用(status=1):该组下的标签可被素材使用 +- 停用(status=0):整组标签不可被新素材选用;已关联素材的历史标签记录保留,展示时标注「已停用」 + +**标签组删除前置条件**: +- 组下还有标签(含停用),不允许删除标签组,提示:「标签组下还有 N 个标签,请先删除所有标签后再操作」 +- 停用标签组时,若组内有启用中的标签,弹出确认提示:「该操作将同时停用组内 N 个标签,是否确认?」确认后联动将组内所有 status=1 的标签变更为 status=0 + +**标签删除规则**: +- 若标签已被素材引用,允许删除,但须弹出确认对话框,提示:「该标签已被 N 个素材引用,删除后引用记录将被清除,是否确认?」 +- 用户确认后执行逻辑删除(deleted=1),并软删除 kr_material_tag 中对应关联记录(deleted=1),素材列表展示中该位置标注「标签已删除」 + +**标签状态规则**: +- 停用标签:不可被新素材选用;已关联的历史记录保留,展示时标注「已停用」 +- 删除标签:逻辑删除(deleted=1),历史关联软删除并标注「已删除」 + +### 3.2 文件夹规则 + +**多层结构**:文件夹最多支持 **5 层**嵌套(根层级为第 1 层,最深到第 5 层)。超出 5 层时,「新建子文件夹」按钮置灰并提示:「已达最大层级限制(5层)」 + +**可见范围规则**: + +| 可见范围值 | 说明 | V1 管理侧行为 | +|---------|------|------| +| 全公司(visibility=0)| 全平台所有用户可见 | ADMIN 和所有部门 LECTURER 可见 | +| 本部门(visibility=1)| 仅本部门用户可见 | 仅本部门 ADMIN / LECTURER 可见 | +| 指定人员(visibility=2)| 仅被指定人员可见 | 通过 kr_folder_visibility_user 表记录;通过部门+人员选择器选择 | + +**可见范围设置交互**: +- 选择「指定人员」时,弹出人员选择器:先选部门,再选部门下的具体人员(sys_user 列表),支持多选 +- 至少选择 1 名人员,否则不允许提交 + +**可见范围继承规则**: +- 新建子文件夹时,可见范围**默认继承父文件夹**的设置(含指定人员列表) +- 创建后可单独修改子文件夹的可见范围,修改仅影响该文件夹本身,不影响父文件夹及其他同级文件夹 +- 修改父文件夹可见范围时,**不自动级联更新**已存在的子文件夹 + +**部门隔离规则**: +- LECTURER 只能看到本部门下的文件夹,无法跨部门查看或操作 +- ADMIN 可查看所有部门的文件夹,切换部门查看时需有部门筛选入口 + +[CONTEXT-001:6.3 控制逻辑——数据级 Service 层注入 department_id 过滤] + +**文件夹删除约束**: +- 文件夹内有子文件夹 → 不允许删除,提示:「请先删除子文件夹」 +- 文件夹内有素材(含已下架)→ 不允许删除,提示:「请先删除或转移文件夹内的素材」 +- 同时满足以上两条均为空时,方可执行删除(逻辑删除) + +### 3.3 素材规则 + +**支持的文件类型**: + +[CONTEXT-003:3.2 校验逻辑——文档格式参照现有知识库] + +| 类型 | 支持格式 | +|------|------| +| 视频 | MP4、AVI、MOV、FLV 等常见视频格式 | +| 音频 | MP3、WAV、AAC、FLAC 等常见音频格式 | +| 图片 | JPG、PNG、GIF、BMP、WEBP | +| 文档 | PDF、Word(doc/docx)、Excel(xls/xlsx)、PPT(ppt/pptx)| + +**文件大小限制**:沿用现有知识库配置,文档 ≤100MB,视频/音频 ≤2GB,图片 ≤50MB;由系统配置项控制 + +**上传记录**:每次上传自动记录: +- `upload_dept_id`(即 department_id):上传部门,取 Token 中的 department_id +- `uploader_id`:上传人,取 Token 中的 user_id +- `upload_time`:服务端时间 + +**素材状态规则**: +- 上传完成后即为「正常(ACTIVE,status=1)」,可被查看 +- 下架(OFFLINE,status=0):不可被查看,但数据保留 +- 支持重新上架:下架后可重新恢复为「正常(ACTIVE)」状态,操作入口与下架同位置(切换按钮或操作菜单) +- 删除(逻辑删除 deleted=1):从列表消失,存储文件保留(软删除) + +**批量转移规则**: +- LECTURER:只能转移到**同部门**下的其他文件夹,目标文件夹选择器中仅展示本部门文件夹 +- ADMIN:可跨部门转移,目标文件夹选择器展示所有部门的文件夹;跨部门转移后素材的 department_id 更新为目标文件夹的 department_id +- 转移后,素材的 folder_id 更新(ADMIN 跨部门时同步更新 department_id);上传人(uploader_id)、原始上传时间(upload_time)等元数据不变 + +**素材操作与部门隔离**:系统用户(LECTURER)只能操作本部门下文件夹内的素材,Service 层强制注入 department_id 过滤 + +### 3.4 存储规则 + +[CODEMAP:common/service/FileService——现有 FileService 接口,新增 COS 实现替换本地实现] +[CONTEXT-001:7.1 可扩展模块——文件存储抽象 FileStorageService 接口设计] + +**存储后端**:腾讯云 COS(Cloud Object Storage),替代现有本地文件存储(LocalFileServiceImpl) + +**实现方式**: +- 后端新增 `CosFileServiceImpl` 实现现有 `FileService` 接口,通过配置切换(`file.storage.type=cos`) +- 所有上传调用路径不变(仍走 `/api/file/upload` 或新增 `/api/knowledge-resource/material/upload`),底层由 COS SDK 处理 +- `file_url` 字段存储 COS 对象路径(相对路径或完整 URL),访问时通过 `getAccessUrl()` 生成带签名的临时访问链接 + +**COS 目录约定**:`knowledge-resource/{department_id}/{folder_id}/{filename}` + +**删除行为**:素材逻辑删除时,COS 文件**不物理删除**,保留原始文件(与现有知识库一致) + +**访问签名**:COS 对象设为私有读,访问时后端生成临时签名 URL(有效期建议 2 小时),前端通过签名 URL 展示/预览 + +### 3.5 预览规则 + +[CODEMAP:common/config/PreviewConfig——现有 kkFileView 预览配置,文档预览复用] +[CODEMAP:common/FileService.getPreviewUrl()——现有预览 URL 生成方法,文档类型复用] + +**预览触发**:点击素材列表中的素材名称或缩略图图标,前端弹出预览对话框(Modal/Drawer),不跳转新页面 + +**预览框交互**: +- 支持**放大**:提供「全屏」或「最大化」按钮,点击后预览框铺满整个浏览器视口 +- 支持关闭:点击右上角关闭按钮或按 Esc 键关闭弹框 +- 放大/缩小状态切换:全屏模式下可恢复默认尺寸 + +**按文件类型的预览实现**: + +| 文件类型 | 预览方式 | 技术实现 | 参照现有知识库 | +|---------|---------|---------|-------------| +| 文档(PDF/Word/Excel/PPT)| kkFileView 在线预览 | 后端调用 `getPreviewUrl()` 生成 kkFileView 预览地址,前端 iframe 嵌入 | ✅ 与现有知识库文档预览完全一致 | +| 视频(MP4/AVI/MOV/FLV)| HTML5 video 标签在线播放 | 前端使用 `