diff --git a/docs/development/rbac-r2/00_current_state.md b/docs/development/rbac-r2/00_current_state.md new file mode 100644 index 000000000..555d04778 --- /dev/null +++ b/docs/development/rbac-r2/00_current_state.md @@ -0,0 +1,196 @@ +# RailwaySystem 一期 RBAC 当前状态审计报告 (Current State Audit) + +- **工程版本**: RailwaySystem 一期正式发布版本 (Commit: `e4a07a2f47d1e78a1d3cefda0228b1c65e00bad8`) +- **审计日期**: 2026-09-15 +- **审计目标**: 彻底排查系统当前在认证主体、注册策略、角色模型、权限字符串、实体绑定、数据范围与 IDOR 风险方面的技术现状,为 R.2 三角色(Admin, Teacher, Student)RBAC 闭环提供客观准确的事实依据。 + +--- + +## 1. 认证主体与安全框架机制现状 + +### 1.1 认证基础设施 (Spring Security + JWT) +- **核心依赖**: Spring Boot Security + JJWT + Redis。 +- **登录端点**: `POST /login` (`com.ruoyi.web.controller.system.SysLoginController`)。 +- **认证载体**: 认证成功后,系统在内存及 Redis 中维护 `com.ruoyi.common.core.domain.model.LoginUser`: + - `user`: 关联底层系统用户实体 `com.ruoyi.common.core.domain.entity.SysUser` (表 `sys_user`)。 + - `roles`: 用户持有的角色标识集合 `Set` (对应 `sys_role.role_key`)。 + - `permissions`: 用户拥有的功能权限集合 `Set` (对应 `sys_menu.perms`)。 + - `token`: 客户端请求头中携带的 `Bearer `,由 `JwtAuthenticationTokenFilter` 拦截解析并放入 `SecurityContextHolder`。 + +### 1.2 动态菜单与前端路由生成逻辑 +1. 客户端登录成功后发起 `GET /getRouters` 请求,进入 `SysMenuController.getRouters()`。 +2. 调用 `SysMenuServiceImpl.selectMenuTreeByUserId(userId)`: + - 若 `userId = 1L` (即超级管理员),直接无条件查询整张 `sys_menu` 表中所有状态为正常 (`status='0'`) 且为目录/菜单类型 (`menu_type IN ('M', 'C')`) 的记录,组装为树形路由返回。 + - 若为普通用户,通过关联表 `sys_user_role -> sys_role_menu -> sys_menu` 进行筛选,仅返回该用户角色分配的可见菜单。 +3. 前端在 Vuex `permission` store (`store/modules/permission.js`) 中通过 `filterAsyncRouter` 动态生成 Vue-Router 路由表并挂载至侧边栏。 + +### 1.3 按钮权限生效与 Controller 权限校验 +- **后端方法级安全**: + - 全局配置开启 `@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)`。 + - Controller 方法使用 `@PreAuthorize("@ss.hasPermi('train:teacher:list')")` 或 `@ss.hasAnyPermi('...')` 注解。 + - 由 Spring EL 表达式调用容器内注册名为 `ss` 的组件 `com.ruoyi.framework.web.service.PermissionService`。 +- **前端按钮级控制**: + - 登录后调用 `GET /getInfo` 获取 `roles` 与 `permissions` 集合,存储于 `store.getters.permissions`。 + - 自定义指令 `v-hasPermi="['train:teacher:add']"` 在 DOM 渲染时比对权限集合,若不满足则通过 `el.parentNode.removeChild(el)` 物理移除 DOM 节点。 + +### 1.4 超级管理员通配逻辑 +- **实现位置**: `SysPermissionService.java` 与 `PermissionService.java`。 +- **判断逻辑**: + - 在 `SysPermissionService.getRolePermission()` 中:`if (user.isAdmin()) { roles.add("admin"); }`。 + - 在 `SysPermissionService.getMenuPermission()` 中:`if (user.isAdmin()) { perms.add("*:*:*"); }`。 + - 在 `PermissionService.hasPermissions()` 中:`permissions.contains("*:*:*") || permissions.contains(permission)`。 +- **审计结论**: 当前生产系统 100% 依赖超级管理员 `*:*:*` 通配权限在运行,系统从未对普通角色或受限用户进行过真实路由加载与接口鉴权。 + +--- + +## 2. 用户注册功能现状审计 + +### 2.1 注册页面与入口代码现状 +- **前端存在注册页面**: `build_ui/src/views/register.vue` (完整包含用户名、密码、确认密码、验证码表单)。 +- **登录页入口暴露**: `build_ui/src/views/login.vue` 中第 61 行存在写死的局部变量: + ```javascript + // 注册开关 + register: true, + ``` + 并在模板中渲染了超链接: + ```html +
+ 立即注册 +
+ ``` + +### 2.2 为什么点击注册出现“当前系统没有开启注册功能!” +- **后端拦截真相**: + - 后端注册接口 `POST /register` 存在于 `SysRegisterController.java`。 + - 控制器入口第一行执行配置项检查: + ```java + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + ``` + - 数据库 `sys_config` 表中配置键 `sys.account.registerUser` 的当前值为 `false` (或未设为 `true`)。 +- **审计结论**: + 1. 并非后端接口缺失或路由崩溃,而是标准的受控业务配置拦截。 + 2. 在铁路职工技能培训和专业认证系统中,账号具有强实名与组织隶属要求,**严禁开放互联网自主注册**。 + 3. 当前前端显示“立即注册”而点击后报错,给用户造成“系统功能损坏”的误解。 + 4. **正式一期建议**: 将 `login.vue` 中的 `register` 设置为 `false`(彻底隐藏注册超链接),并在登录卡片底部增加提示:“*账号由培训管理部门统一创建与维护*”。保持后端受控关闭状态不变。 + +--- + +## 3. Teacher 实体与账号绑定现状审计 + +### 3.1 实体与数据表结构检查 +- **数据库表 `train_teacher`**: + - 在数据库 V2 迁移脚本 `003_alter_master_tables.sql` 中,已执行: + ```sql + ALTER TABLE train_teacher ADD COLUMN user_id BIGINT; + COMMENT ON COLUMN train_teacher.user_id IS 'sys_user登录账号逻辑关联ID'; + ``` + - 物理表结构中已存在 `user_id` 列,但**无外键约束,无唯一索引,允许为 NULL**。 +- **Java 领域实体 `TrainTeacher.java`**: + - 存在属性 `private Long userId;` 及其 getter/setter。 +- **MyBatis 映射 `TrainTeacherMapper.xml`**: + - `` 已配置。 + - `insertTrainTeacher` 与 `updateTrainTeacher` 中包含 `user_id,`。 + +### 3.2 业务逻辑与控制器检查 +- **`TrainTeacherController.java` 与 `TrainTeacherServiceImpl.java`**: + - 全类无任何针对 `userId` 的校验、赋值、账号生成或绑定逻辑。 + - 管理员通过后台添加或修改教师时,表单不接收 `userId`,也不生成 `sys_user` 记录。 +- **前端 `views/train/teacher/index.vue`**: + - 教师录入表单仅包含:教师姓名、性别、出生日期、联系电话、邮箱、所属单位、职务职称、备注。 + - 完全无“关联登录账号”、“用户名”或“初始密码”字段。 +- **审计结论**: + - `TEACHER_ACCOUNT_BINDING_CURRENT = NONE` + - 虽有列名预留,但业务链路、控制层与展示层中账号绑定机制完全为 0。教师人员根本无法登录系统。 + +--- + +## 4. Student 实体与账号绑定现状审计 + +### 4.1 实体与数据表结构检查 +- **数据库表 `train_student`**: + - 在 V2 迁移脚本 `003_alter_master_tables.sql` 中已执行: + ```sql + ALTER TABLE train_student ADD COLUMN user_id BIGINT; + COMMENT ON COLUMN train_student.user_id IS 'sys_user账号逻辑关联'; + ``` + - 物理表结构中已存在 `user_id` 列,但同样**无外键、无唯一约束,全部为 NULL**。 +- **Java 领域实体 `TrainStudent.java`**: + - 存在 `private Long userId;` 及其 getter/setter。 +- **MyBatis 映射 `TrainStudentMapper.xml`**: + - 映射标签与 insert/update 动态 SQL 中存在 `user_id`。 + +### 4.2 业务逻辑与控制器检查 +- **`TrainStudentController.java` 与 `TrainStudentServiceImpl.java`**: + - 仅处理学员档案信息录入与身份证加密存储(密文、Hash、后4位),完全没有与 `sys_user` 的联动。 +- **前端 `views/train/student/index.vue`**: + - 仅维护学员姓名、性别、身份证号、手机、邮箱、单位、来源类型等,无登录账号绑定功能。 +- **审计结论**: + - `STUDENT_ACCOUNT_BINDING_CURRENT = NONE` + - 学员主体与系统用户完全解耦,学员无法登录系统,无法查看个人培训档案与课表。 + +--- + +## 5. 系统角色模型 (SysRole) 现状审计 + +### 5.1 数据库已有预置角色 +通过检索 `sys_role` 初始化数据,系统当前仅定义了以下 2 个角色: +1. `role_id = 1`: 角色名称: `超级管理员`,角色字符: `admin`,数据范围: `1 (全部数据权限)`。 +2. `role_id = 2`: 角色名称: `普通角色`,角色字符: `common`,数据范围: `2 (自定义数据权限)`。 + +### 5.2 培训专属角色现状 +- 经全量检查,系统中**不存在**以下任何角色定义: + - `train_admin` (培训管理员) + - `train_teacher` (培训教师) + - `train_student` (参训学员) +- 审计结论: + - `TRAIN_ADMIN_ROLE_CURRENT = NONE` + - `TRAIN_TEACHER_ROLE_CURRENT = NONE` + - `TRAIN_STUDENT_ROLE_CURRENT = NONE` + +--- + +## 6. 权限字符串与前后端契约审计 + +### 6.1 前后端与数据库权限命名现状 +全量扫描发现以下三大典型不一致与缺失: + +| 模块类别 | 典型权限前缀 | 前端现状 | 后端现状 | 数据库 `sys_menu.perms` 现状 | 判定结论 | +| :--- | :--- | :--- | :--- | :--- | :--- | +| 教师管理 | `train:teacher:*` | `train:teacher:add/edit` | `train:teacher:list/add/edit/query` | **缺失对应按钮权限行** | 前后端命名一致,但 DB 权限行缺失 | +| 学员管理 | `train:student:*` | `train:student:add/query` | `train:student:list/add/query` | **缺失对应按钮权限行** | 前后端一致,DB 缺失 | +| 电子班牌 | `system:board:*` vs `train:board:*` | `system:board:add/edit/remove/export` | `system:board:list/add/query/edit/remove/export` | **缺失** | 命名混用 `system:`,未归入 `train:` | +| 遗留模块 | `train:auth:*` / `train:role:*` | `train:auth:add...`, `train:role:add...` | **无对应 Controller** | **无** | 废弃遗留前端页面引入的野权限 | +| 考勤报到 | `train:report:*`, `train:attendance:*` | 已正式统一 | 已正式统一 | 最近 Migration 已对齐 | **MATCH** (标准契约) | + +--- + +## 7. 数据范围隔离与 IDOR 风险审计 + +### 7.1 数据范围现状 +- `TEACHER_DATA_SCOPE_CURRENT = NONE (无任何基于当前教师身份的隔离逻辑)` +- `STUDENT_DATA_SCOPE_CURRENT = NONE (无任何基于当前学员身份的隔离逻辑)` + +### 7.2 IDOR (越权直接对象引用) 漏洞排查 +当前所有 Controller 端点直接接收前端提交的主键参数,且直接调用底层无范围过滤的查询/更新方法: +- **学员档案越权**: `GET /train/student/{sId}`,只要具备查询权限,输入任意 `sId` 即可获取他人隐私档案。 +- **课表越权**: `GET /train/plans/{planId}/timetable`,无计划归属校验。 +- **考勤越权**: `GET /train/checkin/list`,传入任意 `studentId` 可获取全量打卡记录。 +- **学员画像越权**: `GET /train/portrait/{portraitId}`,无身份校验直接输出他人综合评定。 +- **审计结论**: `IDOR_RISK_COUNT = 8 (高危端点)`,若直接开放多角色登录而未重构数据范围,系统将面临严重横向越权风险。 + +--- + +## 8. 当前状态审计总结表 + +| 审计维度 | 当前客观状态 | 严重度 / 影响 | +| :--- | :--- | :--- | +| **当前认证主体** | `sys_user` (LoginUser),通过 JWT 认证,超管使用 `*:*:*` 通配放行 | 正常,但无非超管业务应用 | +| **教师账号绑定** | `NONE` (物理列存在,业务全链路 0 绑定) | **阻断** Teacher 无法登录 | +| **学员账号绑定** | `NONE` (物理列存在,业务全链路 0 绑定) | **阻断** Student 无法登录 | +| **自主注册现状** | 前端界面暴露链接,后端配置受控关闭 (400 提示) | 体验缺陷,应安全隐藏入口 | +| **业务专属角色** | `train_admin`, `train_teacher`, `train_student` 全部缺失 | **缺失** 基础角色模型 | +| **数据范围隔离** | 教师与学员数据范围隔离为 `NONE`,IDOR 风险端点达 8 处 | **高危** 缺乏横向隔离 | +| **权限命名空间** | 存在 `system:board:*` 混杂及 DB 菜单按钮 perms 缺失 | 规范缺陷,需发布补丁脚本 | diff --git a/docs/development/rbac-r2/01_identity_binding_design.md b/docs/development/rbac-r2/01_identity_binding_design.md new file mode 100644 index 000000000..fa20b4189 --- /dev/null +++ b/docs/development/rbac-r2/01_identity_binding_design.md @@ -0,0 +1,132 @@ +# RailwaySystem 身份绑定与账号生命周期设计 (Identity Binding Design) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 建立 `sys_user` 系统登录账户与 `train_teacher` (教师)、`train_student` (学员) 业务主体之间的确定性逻辑绑定模型,规范生命周期流转与唯一性约束。 + +--- + +## 1. 业务主体关联方案比较与最终选型 + +为了将系统登录账户(`sys_user`)与铁路培训业务实体绑定,针对系统架构演进,深入比较以下两个设计方案: + +### 1.1 方案 A:在业务主体表中直联 `user_id` (推荐方案) +- **模型设计**: + - `train_teacher` 表中直接维护 `user_id BIGINT` 列,逻辑关联 `sys_user.user_id`。 + - `train_student` 表中直接维护 `user_id BIGINT` 列,逻辑关联 `sys_user.user_id`。 +- **架构优势**: + 1. **零新增物理表**: 在前置 V2 数据库迁移脚本 `003_alter_master_tables.sql` 中,已在 `train_teacher` 和 `train_student` 表物理添加了 `user_id` 列,现有实体类和 Mapper XML 均已就绪。 + 2. **极速单表/外联查询**: 获取当前登录人的教师 ID 或学员 ID 时,直接执行 `SELECT t_id FROM train_teacher WHERE user_id = #{userId}`,单索引点查,响应时延 < 1ms。 + 3. **改造面最小**: 领域模型与既有 MyBatis 实体完全兼容,无需重构现有领域对象树。 +- **劣势与局限**: + - 若未来同一个自然人同时具备“讲师”和“学员”双重身份,直联模式需要跨两张表查询或处理两份主体数据。 + +### 1.2 方案 B:引入统一业务身份映射表 `train_user_identity` +- **模型设计**: + - 新建映射表 `train_user_identity (id, user_id, identity_type, business_id, status, create_time)`。 + - `identity_type`: `TEACHER` 对应 `t_id`, `STUDENT` 对应 `s_id`。 +- **架构优势**: + 1. 结构高度解耦,天然支持未来“一人多身份”、“多端身份动态切换”等超大规模平台需求。 +- **劣势与局限**: + 1. 需要新建物理表、维护新的外键与索引,增加数据库 Migration 的复杂度和风险。 + 2. 每次根据登录人推导数据范围均需多一层 JOIN 或一次额外的主键转换查询。 + 3. 大幅增加现有 Service 和 Mapper 的代码改造量,过度设计(Over-Engineering)。 + +### 1.3 综合决策对比矩阵与最终推荐 + +| 评估维度 | 方案 A:直联 `user_id` (现有预留) | 方案 B:统一映射表 `train_user_identity` | 权重 / 判定 | +| :--- | :---: | :---: | :---: | +| **与现有 DDL 契合度** | **100% (列已存在,实体已映射)** | 0% (需新建表与迁移脚本) | 方案 A 显著优胜 | +| **查询性能与简单度** | **极佳 (单表直查,无多余 JOIN)** | 一般 (多一层关系表关联) | 方案 A 胜 | +| **现有代码修改量** | **极小 (仅需在 Service 层赋值)** | 较大 (需新增 Mapper/Service) | 方案 A 胜 | +| **数据一致性与约束** | **单列唯一索引即刻保证** | 需要多列复合唯一约束 | 方案 A 胜 | +| **一期交付稳定性** | **极高 (稳定无副作用)** | 中 (存在映射脏数据风险) | 方案 A 胜 | +| **未来多身份扩展性** | 弱 (仅支持单一身份绑定) | **强 (天生支持多重身份)** | 方案 B 胜 | + +**最终冻结决策**: **采用方案 A(RECOMMENDED_IDENTITY_MODEL = DIRECT_USER_ID)**。 +以最小的改动、最高的稳定性,完美利用已有的 Kingbase DDL 物理列,达成一期业务 100% 闭环。 + +--- + +## 2. 多身份策略与唯一性约束 (Multi-Identity Policy) + +### 2.1 一期多身份决策:严格单身份互斥 +- 在铁路职工技能培训体系中,角色的职责边界极为森严: + - **教务管理员 (Admin)**: 负责全局资源调配、排课、分班与监控。 + - **任课教师 (Teacher)**: 专职负责授课、课次考勤与教学答疑。 + - **参训职工 (Student)**: 专职参与培训、打卡签到与考核画像。 +- **冻结决策**: **一期系统严格不支持一个账号具备多重业务身份**。 + - 严禁同一个 `sys_user` 既绑定教师又绑定学员。 + - 严禁超级管理员账号 `admin (userId=1)` 绑定为普通业务教师或学员。 + +### 2.2 数据库物理约束与保障机制 +为了在数据库持久层杜绝脏数据与多重绑定,在数据库 Migration 脚本中为两张表增加唯一索引: +```sql +-- 1. 教师账号唯一索引 (忽略 NULL,只对已绑定的 user_id 施加唯一约束) +CREATE UNIQUE INDEX IF NOT EXISTS uq_train_teacher_user_id +ON train_teacher(user_id) +WHERE user_id IS NOT NULL AND del_flag = '0'; + +-- 2. 学员账号唯一索引 (忽略 NULL,只对已绑定的 user_id 施加唯一约束) +CREATE UNIQUE INDEX IF NOT EXISTS uq_train_student_user_id +ON train_student(user_id) +WHERE user_id IS NOT NULL AND del_flag = '0'; +``` + +### 2.3 业务服务层互斥前置校验防线 +在新建或绑定账号时,在 Java Service 事务中实施互斥断言: +```java +public void validateUserIdentityExclusive(Long userId) { + if (userId == null) return; + if (SecurityUtils.isAdmin(userId)) { + throw new ServiceException("系统超级管理员不可绑定为业务教师或学员"); + } + if (teacherMapper.countByUserId(userId) > 0) { + throw new ServiceException("该登录账号已绑定为教师身份,不可重复绑定"); + } + if (studentMapper.countByUserId(userId) > 0) { + throw new ServiceException("该登录账号已绑定为学员身份,不可重复绑定"); + } +} +``` + +--- + +## 3. 用户创建与账号生命周期联动机制 + +### 3.1 账号创建推荐模式:表单内可选“同时开通系统账号” +- **业务流程**: + 1. 管理员在录入教师或学员档案时,表单底部勾选【开通系统登录账号】开关(默认勾选)。 + 2. 系统自动推荐登录账号格式: + - **教师账号**: 优先采用工号(如 `T2026001`),若未填工号则采用手机号。 + - **学员账号**: 优先采用学员编号/工号(如 `GH001`),若无则采用手机号。 + 3. **初始密码策略**: + - 统一设置初始受控密码:`Railway@2026`。 + - 采用 BCrypt 强哈希加密存储入库。 + - 强制将 `sys_user.pwd_update_date` 设为 `NULL`,触发前端首次登录时强制要求用户修改密码的安全拦截。 + 4. **角色自动赋予**: + - 创建教师账号时,自动关联向 `sys_user_role` 插入 `role_id = `。 + - 创建学员账号时,自动关联向 `sys_user_role` 插入 `role_id = `。 + 5. 整个创建过程置于单个本地数据库事务中(`@Transactional(rollbackFor = Exception.class)`),若任意一步失败,整体回滚,杜绝孤立僵尸账号。 + +### 3.2 补录与独立绑定能力 (兜底机制) +- 对于历史导入或初次创建时未开通账号的主体,在教师/学员管理列表的操作列增加【开通账号】或【重置密码】按钮,支持补录生成 `sys_user` 并回填 `user_id`。 + +--- + +## 4. 删除、停用与离职审计策略 + +在国家关键信息基础设施与铁路行业规范中,系统必须满足操作可追溯与数据终身审计要求: + +### 4.1 离职与停用策略 (Status Synchronization) +- 当业务教师被设置为停用 (`train_teacher.status = 0`),或学员由于违反纪律被停用 (`train_student.status = 0`): + - 业务 Service 在同一事务中联动更新关联的 `sys_user.status = '1'` (停用)。 + - 清理该用户在 Redis 中的在线 Token 会话缓存,实现“秒级下线”。 +- 当业务教师重新启用 (`status = 1`),联动更新 `sys_user.status = '0'` (正常)。 + +### 4.2 软删除与历史归档策略 (Zero Physical Deletion) +- **红线要求**: **系统严禁对任何已产生过教学、考勤、报到记录的账号执行物理 `DELETE`**! +- 当管理员在界面上删除教师或学员时: + - 业务表更新 `del_flag = '2'`。 + - 关联系统用户表更新 `sys_user.del_flag = '2'`。 + - 保留 `train_teacher.user_id` 与 `train_student.user_id` 的数值指针不变,确保历史系统操作日志 (`sys_oper_log`)、课次考勤打卡流水 (`train_student_checkin`) 中的 `create_by` 和 `user_id` 在任何时候均可完整追溯责任人。 diff --git a/docs/development/rbac-r2/02_role_permission_matrix.md b/docs/development/rbac-r2/02_role_permission_matrix.md new file mode 100644 index 000000000..474a6986f --- /dev/null +++ b/docs/development/rbac-r2/02_role_permission_matrix.md @@ -0,0 +1,124 @@ +# RailwaySystem 角色体系与权限矩阵设计 (Role & Permission Matrix) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 建立三角色(Admin, Teacher, Student)标准化角色定义,冻结权限命名空间,输出全量权限比对矩阵、角色菜单矩阵与角色操作权限矩阵。 + +--- + +## 1. 预置业务角色模型设计 (Role Model) + +针对一期业务闭环与后续演进,系统正式规划 3 个预置业务角色(写入 `sys_role`): + +| 角色标识 (role_key) | 角色名称 (role_name) | 角色排序 (role_sort) | 数据范围 (data_scope) | 状态 (status) | 职责与业务定义 | +| :--- | :--- | :---: | :---: | :---: | :--- | +| `train_admin` | 培训管理员 | 3 | `1` (全部数据权限) | `0` (正常) | 负责全局师资、学员、计划、排课、报到、考勤、场地等业务的集中调度与管理。 | +| `train_teacher` | 培训教师 | 4 | `2` (自定义业务推导) | `0` (正常) | 仅能查看与本人授课相关的课程、课表、培训班及学员,具备课次考勤查看与合规操作权限。 | +| `train_student` | 培训学员 | 5 | `2` (本人私有推导) | `0` (正常) | 仅能查看本人的参训班级、课程日程、个人报到状态、个人考勤、学时累计与个人画像。 | + +*注:超级管理员 `admin (role_id=1)` 仍保留在系统最顶层,作为系统底层运维、字典与安全审计兜底。* + +--- + +## 2. 权限命名空间统一冻结方案 (FINAL_PERMISSION_NAMESPACE_PROPOSAL) + +### 2.1 规范原则 +所有业务领域权限字符串必须严格遵循标准三段式格式: +``` +train:: +``` +- ``: 业务领域名称,全部小写单数或固定模块名(如 `teacher`, `student`, `course`, `classroom`, `site`, `plan`, `lesson`, `enrollment`, `report`, `attendance`, `board`, `duration`, `portrait`)。 +- ``: 具体操作谓词,标准化定义: + - 查询类:`list` (列表分页), `query` (详情单查), `export` (报表导出) + - 变更类:`add` (新增), `edit` (编辑修改), `remove` (删除/作废) + - 核心状态机动作类:`confirm` (确认), `checkin` (打卡), `complete` (归档完成) + +### 2.2 命名空间收敛与历史兼容 +1. **统一班牌命名空间**: + - 纠正历史版本中混杂的 `system:board:*`,在 R.2B 中全量收敛为 `train:board:list/add/query/edit/remove/export`。 +2. **严守已正式冻结的 Action 契约**: + - `train:report:confirm` (报到确认) —— **严格保留** + - `train:attendance:checkin` (课次签到打卡) —— **严格保留** + - `train:attendance:edit` (考勤人工修正) —— **严格保留** + - `train:plan:complete` (结束培训计划) —— **严格保留** + +--- + +## 3. 全量权限字符串审计与对齐矩阵 (PERMISSION_MISMATCH_MATRIX) + +本矩阵排查了当前代码库中所有业务模块的权限字符串匹配状态: + +| 业务模块 (Module) | 前端权限 (Frontend Perm) | 后端权限 (Backend Perm) | 数据库 sys_menu.perms | 现状评定 (Status) | R.2 规范与整改方案 | +| :--- | :--- | :--- | :--- | :---: | :--- | +| **教师管理** | `train:teacher:add/edit` | `train:teacher:list/query/add/edit` | **缺失按钮行** | `MISSING` | 在 `005_...sql` 中为 `sys_menu` 补全对应按钮权限行 | +| **学员管理** | `train:student:add/query` | `train:student:list/query/add` | **缺失按钮行** | `MISSING` | 补齐 `train:student:list/add/edit/query/export` | +| **课程管理** | `train:course:add/edit` | `train:course:list/query/add/edit` | **缺失按钮行** | `MISSING` | 补齐 `train:course:list/query/add/edit` | +| **教室资源** | `train:classroom:edit` | `train:classroom:list/edit` | **缺失按钮行** | `MISSING` | 补齐 `train:classroom:list/edit` | +| **实训场地** | `train:site:edit` | `train:site:list/edit` | **缺失按钮行** | `MISSING` | 补齐 `train:site:list/edit` | +| **培训计划** | `train:plan:add/edit/query/complete` | `train:plan:list/query/add/edit/complete` | **缺失按钮行** | `MISSING` | 补齐 `train:plan:*` 按钮行 | +| **入班分班** | `train:enrollment:add/edit/remove/query/list/import` | `train:enrollment:list/query/add/edit/remove/import` | **缺失按钮行** | `MISSING` | 补齐 `train:enrollment:*` 按钮行 | +| **排课编排** | `train:lesson:add/edit/remove` | `train:lesson:query/add/edit/remove` | **缺失按钮行** | `MISSING` | 补齐 `train:lesson:*` 按钮行 | +| **现场报到** | `train:report:confirm/export` | `train:report:list/confirm/export` | `train:report:list/confirm/export` | `MATCH` | 完全合规,无需调整 | +| **考勤管理** | `train:attendance:export` | `train:attendance:list/checkin/edit/export` | `train:attendance:list/checkin/edit/export` | `MATCH` | 前端增加操作按钮权限控制 | +| **电子班牌** | `system:board:add/edit/remove/export` | `system:board:list/query/add/edit/remove/export` | **缺失/未归入train** | `MISMATCH` | 前后端与 SQL 统一切换为 `train:board:*` | +| **学习时长** | `train:learning:export` | `train:learning:list/export` | `train:learning:list/export` | `MATCH` | 完全合规 | +| **学员画像** | `train:portrait:export` | `train:portrait:query/export` | `train:portrait:query/export` | `MATCH` | 完全合规 | +| **宿舍管理** | `train:dormitory:list/edit` | `train:dormitory:list/edit` | **缺失按钮行** | `MISSING` | 补齐 `train:dormitory:list/edit` | +| **废弃遗留** | `train:auth:*`, `train:role:*` | **无对应控制器** | **无** | `LEGACY` | 清理废弃未使用的历史遗留权限标签 | + +--- + +## 4. 角色-菜单矩阵 (ROLE_MENU_MATRIX) + +本矩阵定义了不同身份用户登录系统后,前端侧边栏动态加载的菜单可见性: + +| 菜单项 / 业务模块 | 路由路径 (Path) | 管理员 (Admin) | 教师 (Teacher) | 学员 (Student) | 交互与展示说明 | +| :--- | :--- | :---: | :---: | :---: | :--- | +| **系统控制台首页** | `/index` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 管理员全局概览 | +| **教师工作台首页** | `/train/teacher/dashboard` | `HIDDEN` | `VISIBLE` | `HIDDEN` | 教师专属 Landing 页 (我的课表/班级) | +| **学员培训中心首页** | `/train/student/portal` | `HIDDEN` | `HIDDEN` | `VISIBLE` | 学员专属 Landing 页 (报到/课表/考勤) | +| **师资队伍管理** | `/business/teacher` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 教师仅能由管理员维护 | +| **学员档案管理** | `/business/student` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 学员全量档案仅管理员可见 | +| **课程标准化管理** | `/business/course` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 课程全量字典与学时标准 | +| **教学场地/设施** | `/business/classroom`, `/business/site` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 物理教学资源管理 | +| **培训计划管理** | `/business/training-plan` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 全局立项与归档操作 | +| **课表编排日历** | `/business/timetable/lesson` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 全局智能排课与冲突消解 | +| **我的授课日程** | `/train/teacher/timetable` | `HIDDEN` | `VISIBLE` | `HIDDEN` | 教师专有:仅呈现本人授课课次 | +| **我的培训课表** | `/train/student/timetable` | `HIDDEN` | `HIDDEN` | `VISIBLE` | 学员专有:仅呈现本人参训课表 | +| **我的教学班级** | `/train/teacher/classes` | `HIDDEN` | `VISIBLE` | `HIDDEN` | 教师专有:所授课班级及学员花名册 | +| **现场报到管理** | `/business/training-report` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 管理端集中报到审核核销 | +| **我的报到信息** | `/train/student/report` | `HIDDEN` | `HIDDEN` | `VISIBLE` | 学员专有:个人报到凭证与状态核对 | +| **课次考勤管理** | `/business/attendance` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 管理端全局考勤大盘与异常处理 | +| **班级授课考勤** | `/train/teacher/attendance` | `HIDDEN` | `CONDITIONAL` | `HIDDEN` | 仅支持教师查看本人课次考勤流水 | +| **我的考勤打卡** | `/train/student/attendance` | `HIDDEN` | `HIDDEN` | `VISIBLE` | 学员专有:仅查阅本人签到签退流水 | +| **智慧电子班牌** | `/business/board` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 终端物理状态与大屏绑定 | +| **学员学习时长** | `/business/duration` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 全局学时统计 | +| **我的学时档案** | `/train/student/duration` | `HIDDEN` | `HIDDEN` | `VISIBLE` | 学员个人有效学时汇算明细 | +| **学员画像大盘** | `/business/portrait` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 全员综合评价分析 | +| **我的个人画像** | `/train/student/portrait` | `HIDDEN` | `HIDDEN` | `VISIBLE` | 学员本人的雷达评价与成长轨迹 | +| **系统与操作日志** | `/system/log/operlog` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 安全审计人员专属 | +| **系统用户/角色管理** | `/system/user`, `/system/role` | `VISIBLE` | `HIDDEN` | `HIDDEN` | 基础用户权限分配 | + +--- + +## 5. 角色-操作权限矩阵 (ROLE_ACTION_MATRIX) + +本矩阵明确在各业务功能的操作点上,不同角色的允许状态(ALLOW / DENY / CONDITIONAL): + +| 业务操作动作 (Action) | 管理员 (Admin) | 教师 (Teacher) | 学员 (Student) | 业务安全规则与执行逻辑 | +| :--- | :---: | :---: | :---: | :--- | +| **创建/编辑教师主体** | `ALLOW` | `DENY` | `DENY` | 仅教务管理员具备师资建档权限 | +| **创建/编辑学员档案** | `ALLOW` | `DENY` | `DENY` | 仅教务管理员具备学员建档与身份证录入权限 | +| **开通/绑定登录账号** | `ALLOW` | `DENY` | `DENY` | 管理员受控统一分配账号 | +| **创建/修改培训计划** | `ALLOW` | `DENY` | `DENY` | 培训计划属于全局业务管理资源 | +| **执行编排排课** | `ALLOW` | `DENY` | `DENY` | 智能排课引擎与冲突消解仅限管理端 | +| **确认 Soft Warning 排课** | `ALLOW` | `DENY` | `DENY` | 普通保存流程仅限教务操作员确认 | +| **学员批量分配入班** | `ALLOW` | `DENY` | `DENY` | 计划学员编组仅限管理员操作 | +| **现场报到确认** | `ALLOW` | `DENY` | `DENY` | 线下现场审核证件并核销报到 | +| **课次签到打卡** | `CONDITIONAL` | `DENY` | `ALLOW` | 学员本人通过终端打卡;管理员受控补签 | +| **课次签退打卡** | `CONDITIONAL` | `DENY` | `ALLOW` | 学员离场打卡;签退时间严格冻结防篡改 | +| **考勤人工修正 (迟到等)** | `ALLOW` | `CONDITIONAL` | `DENY` | 管理员全权修正;教师经授权可提交修正申请 | +| **结束计划与联动归档** | `ALLOW` | `DENY` | `DENY` | 仅管理员可终结培训计划全生命周期 | +| **查看他人敏感信息** | `ALLOW` | `DENY` | `DENY` | 教师/学员严禁读取未授权的他人身份证与隐私 | +| **导出业务分析报表** | `ALLOW` | `CONDITIONAL` | `DENY` | 教师仅能导出所带班级名册;学员无导出权 | +| **用户与系统权限维护** | `ALLOW` | `DENY` | `DENY` | 严防普通用户提权 | diff --git a/docs/development/rbac-r2/03_data_scope_design.md b/docs/development/rbac-r2/03_data_scope_design.md new file mode 100644 index 000000000..f72afa7c4 --- /dev/null +++ b/docs/development/rbac-r2/03_data_scope_design.md @@ -0,0 +1,137 @@ +# RailwaySystem 数据范围与 IDOR 安全防护设计 (Data Scope & IDOR Prevention) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 建立从安全凭证(`SecurityContext`)到业务主体 ID 的单向信任推导链条,杜绝前端参数篡改越权,消除全站直接对象引用(IDOR)安全漏洞。 + +--- + +## 1. 数据范围核心设计原则 (Zero-Trust Frontend) + +在多角色(Admin, Teacher, Student)登录体系中,数据范围安全必须建立在**零信任前端参数**的基石之上: + +``` ++─────────────────────────────────────────────────────────────────────────────+ +| 零信任推导核心防线 (Secure Chain) | +| | +| 客户端 HTTP Request | +| │ (仅携带 Bearer JWT Token,禁止/忽略 query 或 body 中的 identityId) | +| ▼ | +| Spring Security Filter: JwtAuthenticationTokenFilter | +| │ (验签并提取 claims 中的 userId) | +| ▼ | +| SecurityContextHolder.getContext().getAuthentication() | +| │ (获取受信任的 LoginUser.getUserId()) | +| ▼ | +| Service 内部数据推导 (SecurityContextHelper) | +| ├─ 若拥有 train_teacher 角色: | +| │ SELECT t_id FROM train_teacher WHERE user_id = #{userId} | +| ├─ 若拥有 train_student 角色: | +| │ SELECT s_id FROM train_student WHERE user_id = #{userId} | +| └─ 若拥有 admin / train_admin 角色: | +| 按管理员角色规则放行或基于组织机构过滤 | ++─────────────────────────────────────────────────────────────────────────────+ +``` + +### 1.1 核心铁律 +1. **禁止直接接收外部身份 ID**: 在教师和学员的私有业务接口中,接口方法参数严禁声明 `@RequestParam String studentId` 或 `@PathVariable String teacherId`。 +2. **强制服务端单向解析**: 所有业务查询条件中的 `studentId`、`teacherId` 必须统一在 Service 事务内由 `SecurityUtils.getUserId()` 动态查询或注入。 +3. **严格归属校验 (Ownership Assertion)**: 在涉及具体资源主键操作(如查看某个具体 `reportId`、`checkinId`、`enrollmentId`)时,SQL 中必须强制拼接 `AND student_id = #{currentStudentId}` 或 `AND teacher_id = #{currentTeacherId}`,不满足条件时直接返回 `404 Not Found` 或 `403 Forbidden`,坚决不泄露数据存在性。 + +--- + +## 2. 教师 (Teacher) 业务数据范围推导链条 + +教师登录后,其可访问的所有教学资源必须严格基于其受聘授课关系动态推导: + +``` + ┌──────────────────────┐ + │ sys_user (userId) │ + └──────────┬───────────┘ + │ WHERE user_id = #{userId} + ▼ + ┌──────────────────────┐ + │ train_teacher (t_id)│ + └──────────┬───────────┘ + │ + ┌───────────────────────┼────────────────────────┐ + │ (排课直接授课) │ (计划课程关联) │ (关联教学班级) + ▼ ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│timetable_lesson │ │train_plan_course │ │ train_class_plan │ +│WHERE teacher_id │ │WHERE teacher_id │ │ WHERE plan_id IN │ +│ = #{t_id} │ │ = #{t_id} │ │ (授课班级集合) │ +└────────┬─────────┘ └──────────────────┘ └────────┬─────────┘ + │ │ + ▼ ▼ +┌──────────────────┐ ┌──────────────────┐ +│student_checkin │ │training_enrollment +│(本人课次考勤流水) │ │(我带的班级学员名单)│ +└──────────────────┘ └──────────────────┘ +``` + +- **授课课表数据**: 仅能查到 `train_timetable_lesson.teacher_id = #{currentTeacherId}` 的课次。 +- **关联班级数据**: 仅能查到由当前教师担任授课任务的 `plan_id` 集合。 +- **班级学员名单**: 仅能读取自己任教班级名单,**严禁读取全校其他培训班的学员档案**,且读取名单时必须对身份证号、住址等个人隐私字段进行脱敏(仅显示后四位)。 + +--- + +## 3. 学员 (Student) 私有数据范围推导链条 + +学员登录后,进入极简个人培训中心,数据范围完全收敛于“本人”: + +``` + ┌──────────────────────┐ + │ sys_user (userId) │ + └──────────┬───────────┘ + │ WHERE user_id = #{userId} + ▼ + ┌──────────────────────┐ + │ train_student (s_id)│ + └──────────┬───────────┘ + │ + ┌─────────────┬───────────────┼───────────────┬─────────────┐ + ▼ ▼ ▼ ▼ ▼ +┌───────┐ ┌──────────────┐ ┌───────────┐ ┌──────────────┐ ┌────────────┐ +│ 个人 │ │ 参训班级名单 │ │ 个人报到 │ │ 个人考勤记录 │ │ 个人画像/ │ +│ 档案 │ │ Enrollment │ │ Report │ │ Checkin │ │ 学时累计 │ +│(只读) │ │WHERE s_id=...│ │WHERE s_id │ │WHERE s_id=...│ │WHERE s_id │ +└───────┘ └──────┬───────┘ └───────────┘ └──────────────┘ └────────────┘ + │ + ▼ + ┌──────────────┐ + │ 班级参训课表 │ + │(仅限正常在训)│ + └──────────────┘ +``` + +- **学员档案**: 仅能读取与更新本人通讯电话、邮箱。 +- **学员课表**: 仅能读取本人已分班入班(且状态为待报到、已报到或已完成)的班级课次,严禁获取其他班级的教学日程。 +- **考勤与报到**: 报到记录、打卡记录、学时累计与学员画像全部以 `student_id = #{currentStudentId}` 为严格物理隔离边界。 + +--- + +## 4. 全站 IDOR 漏洞排查与加固矩阵 (IDOR_RISK_MATRIX) + +本矩阵针对现有 Controller 方法进行逐一安全排查,明确其在多角色模式下的越权风险等级与防护要求: + +| 序号 | 业务接口端点 | 当前实现机制 | 越权风险评定 | 潜在越权利用场景 | R.2 加固与防护措施 | +| :---: | :--- | :--- | :---: | :--- | :--- | +| 01 | `GET /train/student/{sId}` | 直接根据路径参数 `sId` 查询并返回档案明细 | **AT_RISK (高危)** | 学员登录后篡改 `sId` 抓取全校任意职工身份证号与家庭隐私 | **管理端专有**:普通学员严禁访问此端点;学员本人查阅统一走 `/train/me/student` | +| 02 | `GET /train/teacher/{tId}` | 直接根据路径参数 `tId` 查询教师档案 | **AT_RISK (中危)** | 任意登录人员枚举读取全校师资档案 | **管理端专有**;教师本人查阅统一走 `/train/me/teacher` | +| 03 | `GET /train/plans/{planId}/timetable` | 直接根据 `planId` 查询整班课次 | **AT_RISK (高危)** | 未参训学员或未授课教师随意侦测其他班级排课安排 | 校验当前登录人是否在该计划的教师或学员名单内,否则直接拒止 | +| 04 | `GET /train/plans/{planId}/enrollments` | 直接根据 `planId` 查询全班学员名册 | **AT_RISK (高危)** | 普通学员篡改 `planId` 窃取其他班级全部学员花名册及电话 | **严禁学员访问**;教师仅能查阅所带班级名册且敏感字段脱敏 | +| 05 | `GET /train/enrollments/{enrollmentId}` | 直接根据主键 `enrollmentId` 查询 | **AT_RISK (中危)** | 水平越权读取其他学员入班凭证 | 增加 `student_id = currentStudentId` 强行归属校验 | +| 06 | `POST /train/attendance/sign-in` | Body 接收 `studentId`, `lessonId` | **AT_RISK (高危)** | 学员 A 抓包伪造请求体中的 `studentId`,为旷课学员 B 代签 | 接口服务端强制断言 `body.studentId == currentStudentId`,否则拦截 | +| 07 | `PUT /train/attendance/{checkinId}` | 接收 `checkinId` 与修改状态 | **AT_RISK (高危)** | 普通学员或无关人员抓包篡改他人考勤记录 | 仅限拥有 `train:attendance:edit` 的管理员或授权教师操作 | +| 08 | `GET /train/checkin/{checkinId}` | 直接按 ID 查询单条打卡记录 | **AT_RISK (低危)** | 水平遍历探测其他人员打卡位置与时间 | 增加当前登录人身份归属断言 | +| 09 | `GET /train/portrait/{portraitId}` | 直接按画像 ID 返回综合能力雷达 | **AT_RISK (高危)** | 学员枚举读取其他学员测评与能力短板数据 | 学员查阅统一走 `/train/me/portrait`,底层绑定本人 ID | +| 10 | `GET /train/duration/{durId}` | 直接按主键读取学时记录 | **AT_RISK (中危)** | 水平越权探测他人学时与上线情况 | 学员统一走 `/train/me/duration`,强制本人绑定 | +| 11 | `POST /train/plans` (新增计划) | `@PreAuthorize` 鉴权 | `SAFE` | 非管理员无权限直接返回 403 | 角色与菜单隔离即可防御 | +| 12 | `POST /train/plans/{id}/lessons` (排课) | `@PreAuthorize` 鉴权 | `SAFE` | 非教务人员无排课权限 | 角色与功能权限隔离即可防御 | + +### 4.1 审计总结 +- **高危越权端点统计**: `IDOR_RISK_COUNT = 8`。 +- **防护治理策略**: + 1. **管理接口封闭**: 原有 `/train/student/**`、`/train/teacher/**`、`/train/enrollments/**` 仅保留给 `train_admin`。 + 2. **自服务通道独立**: 为 Teacher 与 Student 开辟全新的 `/train/me/**` API,彻底切断直接对象引用漏洞源。 diff --git a/docs/development/rbac-r2/04_api_design.md b/docs/development/rbac-r2/04_api_design.md new file mode 100644 index 000000000..3d67bc5c6 --- /dev/null +++ b/docs/development/rbac-r2/04_api_design.md @@ -0,0 +1,188 @@ +# RailwaySystem 自助服务接口体系设计 (Self-Service API Design) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 建立专属的 Teacher 和 Student 自服务 API 契约(`/train/me/**`),彻底切断客户端参数越权可能性,确保后端 100% 服务端自主推导身份。 + +--- + +## 1. API 设计原则与边界 + +1. **禁止复用 Admin 全量管理接口**: + - 严禁让教师或学员调用 `/train/student/list` 或 `/train/plans/{id}/timetable` 并寄希望于前端过滤。 + - 前端代码可被逆向、调试或通过 Postman 直接调用,任何客户端过滤均等于零安全防护。 +2. **专属 `/train/me/**` 统一前缀**: + - 所有自服务请求均指向当前登录人上下文。 + - 接口入参完全不接收 `studentId`、`teacherId` 等身份主键,参数只允许包含时间范围(如 `startDate`, `endDate`)或分页标记。 +3. **严格对应真实业务能力 (No Fictional APIs)**: + - 仅设计当前数据库与领域服务已真实支撑的业务模块(课表、入班名单、报到、考勤、学时、画像)。 + - 当前一期暂未实现正式闭环的成绩填报或复杂选课,一律不虚构开放。 + +--- + +## 2. 教师自服务接口规格 (Teacher Self-Service APIs) + +### 2.1 获取教师本人档案信息 +- **端点**: `GET /train/me/teacher` +- **鉴权要求**: `@PreAuthorize("@ss.hasRole('train_teacher')")` +- **响应示例**: + ```json + { + "code": 200, + "msg": "操作成功", + "data": { + "tId": "1540e76a34c54596b0e365ae8804ef68", + "tName": "DEMO_E2E_20260915_122800_王老师", + "tGender": "1", + "tPhone": "13900000000", + "tEmail": "wang@railway.cn", + "orgName": "机务段技能实训中心", + "jobTitle": "高级讲师", + "status": 1 + } + } + ``` + +### 2.2 获取我参与任课的培训班列表 +- **端点**: `GET /train/me/plans` +- **逻辑**: 通过 `train_timetable_lesson` 与 `train_plan_course` 逆向查出教师受聘的全部有效培训班。 +- **参数**: `pageNum`, `pageSize`, `planStatus` (可选) +- **响应示例**: + ```json + { + "code": 200, + "rows": [ + { + "planId": "613b316621e5466c9039650ced299aa1", + "planName": "DEMO_E2E_20260915_122800_铁路综合培训第一期", + "startDate": "2026-09-15", + "endDate": "2026-09-20", + "planStatus": 0, + "myCourseCount": 2, + "myLessonCount": 2 + } + ], + "total": 1 + } + ``` + +### 2.3 获取我的授课课表日程 +- **端点**: `GET /train/me/timetable` +- **参数**: `startDate` (如 `2026-09-15`), `endDate` (如 `2026-09-21`) +- **逻辑**: 仅查询 `teacher_id = #{currentTeacherId}` 的已排课次。 +- **响应示例**: + ```json + { + "code": 200, + "data": [ + { + "lessonId": 97, + "planId": "613b316621e5466c9039650ced299aa1", + "planName": "铁路综合培训第一期", + "cId": "f4a28c4079fe47a1a542d3a34768f9fd", + "courseName": "铁路安全理论", + "venueType": 2, + "venueName": "实训区A", + "startTime": "2026-09-15 08:30:00", + "endTime": "2026-09-15 10:00:00", + "status": 1 + } + ] + } + ``` + +### 2.4 获取我所带班级学员花名册 (敏感信息脱敏) +- **端点**: `GET /train/me/students` +- **参数**: `planId` (必填,后端校验教师是否有该班任教关系) +- **安全保障**: 身份证号脱敏为后四位,手机号中间四位脱敏。 +- **响应示例**: + ```json + { + "code": 200, + "rows": [ + { + "studentId": "f1b524da49da4a54a6d832ab22c2d092", + "studentName": "张三", + "maskedPhone": "138****0001", + "maskedIdCard": "************1234", + "orgName": "车辆段检修一车间", + "reportStatus": 1, + "enrollmentStatus": 0 + } + ], + "total": 2 + } + ``` + +### 2.5 获取我授课课次的学员出勤情况 +- **端点**: `GET /train/me/lessons/{lessonId}/attendance` +- **逻辑**: 后端严格核验 `lessonId` 的授课教师是否为当前登录人,然后输出该课次应到学员的打卡流水。 + +--- + +## 3. 学员自服务接口规格 (Student Self-Service APIs) + +### 3.1 获取学员个人档案 +- **端点**: `GET /train/me/student` +- **鉴权要求**: `@PreAuthorize("@ss.hasRole('train_student')")` +- **响应字段**: 学员工号、姓名、性别、联系电话、所属单位、照片凭证引用。身份证号仅向本人展示后四位。 + +### 3.2 获取我的参训班级与生命周期状态 +- **端点**: `GET /train/me/enrollments` +- **逻辑**: 从 `train_training_enrollment` 查询 `student_id = #{currentStudentId}`。 +- **状态字段**: + - `enrollmentStatus`: `0 (待报到)`, `1 (已报到)`, `2 (请假)`, `3 (退训)`, `4 (已完成)`。 + - `planId`, `planName`, `startTime`, `endTime`, `reportDeadline`。 + +### 3.3 获取我的参训课表 +- **端点**: `GET /train/me/timetable` +- **参数**: `startDate`, `endDate`, `viewType` (`day`/`week`) +- **逻辑**: 关联当前学员所在有效班级(`enrollmentStatus IN (0, 1, 4)`),汇总返回所有授课课次、场地位置与任课教师姓名。 + +### 3.4 获取我的报到凭据与状态 +- **端点**: `GET /train/me/report` +- **逻辑**: 查询当前学员在最新培训计划中的报到记录(`train_class_report`),返回报到时间、分配教室、分配场地与当前报到状态。 + +### 3.5 获取我的考勤打卡流水 +- **端点**: `GET /train/me/attendance` +- **参数**: `planId` (可选), `month` (可选) +- **逻辑**: 查询 `train_student_checkin WHERE student_id = #{currentStudentId}`。 +- **响应示例**: + ```json + { + "code": 200, + "rows": [ + { + "checkinId": 10, + "lessonId": 97, + "courseName": "铁路安全理论", + "checkinTime": "2026-09-15 08:28:10", + "checkoutTime": "2026-09-15 12:48:42", + "attendanceStatus": 2, + "statusLabel": "迟到", + "manualFlag": 1, + "adjustReason": "因列车调度交接延误10分钟" + } + ] + } + ``` + +### 3.6 获取我的累计学习时长 +- **端点**: `GET /train/me/duration` +- **逻辑**: 归集 `train_study_duration`,按理论课时与实训课时分别统计有效累计秒数与达标折算学时。 + +### 3.7 获取我的多维成长画像雷达 +- **端点**: `GET /train/me/portrait` +- **逻辑**: 查询 `train_student_portrait WHERE student_id = #{currentStudentId}`,返回出勤率、测评平均分、技能掌握度等 5 维雷达数据。 + +--- + +## 4. 身份异常与边界错误码规范 + +| HTTP 状态码 | 业务错误代码 (Code) | 错误提示说明 (Message) | 触发边界与处理方式 | +| :---: | :---: | :--- | :--- | +| `401` | `401` | "认证凭据无效或会话已过期" | Token 缺失、伪造或 Redis 缓存失效,引导至登录页 | +| `403` | `403` | "非教师身份,禁止访问教师专属工作台" | 学员账号或非授课管理员访问 `/train/me/teacher/**` | +| `403` | `403` | "非学员身份,禁止访问学员个人培训中心" | 教师或管理员账号访问 `/train/me/student/**` | +| `400` | `40041` | "当前登录账号未绑定任何业务档案,请联系管理员" | 存在 `sys_user` 账号,但 `train_teacher/student` 表中未关联对应主体 | +| `403` | `40042` | "您并非该班级或课次的任课教师,无权查看学员信息" | 教师尝试横向探测试图读取无关班级名册 | diff --git a/docs/development/rbac-r2/05_ui_design.md b/docs/development/rbac-r2/05_ui_design.md new file mode 100644 index 000000000..c85c3f235 --- /dev/null +++ b/docs/development/rbac-r2/05_ui_design.md @@ -0,0 +1,133 @@ +# RailwaySystem 前端多角色视图与交互方案设计 (UI & UX Design) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 优化统一登录入口与注册策略,确立三角色登录后动态 Landing 重定向机制,设计轻量、高复用度的教师工作台与学员个人中心。 + +--- + +## 1. 登录入口与注册策略设计 (Registration Policy) + +### 1.1 正式策略参数表 +- **自主注册策略 (`SELF_REGISTRATION_POLICY`)**: `DISABLED` +- **登录页注册链接 (`LOGIN_PAGE_REGISTER_LINK`)**: `HIDE` (隐藏) +- **登录页受控提示 (`LOGIN_PAGE_NOTE`)**: `账号由培训管理部门统一创建与授权维护` + +### 1.2 登录界面改造交互规范 +在 `build_ui/src/views/login.vue` 中: +1. 将局部数据项直接固化为受控策略: + ```javascript + // 关闭自主注册链接渲染 + register: false, + ``` +2. 移除原有的 `立即注册` 元素。 +3. 在登录表单底部密码框与登录按钮之间增加一行浅灰色居中文本提示: + ```html + + ``` +4. **业务价值**: 彻底消除用户点击“立即注册”后弹出“当前系统没有开启注册功能!”的报错负反馈,向评审专家和用户清晰传递“严密组织管控、受控实名体系”的企业级安全定位。 + +--- + +## 2. 登录后动态 Landing 重定向机制 (Landing Strategy) + +系统全员使用同一个登录入口 `http://192.168.139.230:8082/login`。用户验证成功颁发 JWT 后,前端路由守卫(`permission.js`)执行如下分流重定向策略: + +``` + 用户输入账号密码 + │ + ▼ + POST /login (200 OK) + │ + ▼ + GET /getInfo (拉取 roles 与 permissions) + │ + ▼ + 角色判定 (Role Branching) + │ + ┌───────────────────────────┼───────────────────────────┐ + ▼ ▼ ▼ +拥有 admin 或 train_admin 拥有 train_teacher 拥有 train_student + │ │ │ + ▼ ▼ ▼ + Landing 重定向至: Landing 重定向至: Landing 重定向至: + /index /train/teacher/dashboard /train/student/portal + (系统综合驾驶舱) (教师授课工作台) (学员个人培训中心) +``` + +- **兜底方案 (Fallback)**: 若用户被赋予复合角色或自定义角色,前端自动跳转至 `asyncRoutes` 生成的动态路由树的第一个叶子节点页面,杜绝 404 或白屏。 + +--- + +## 3. 教师端页面架构方案 (Teacher Portal) + +### 3.1 核心设计理念:轻量工作台,杜绝全套克隆 +- 教师端绝不照搬或复制一套庞大的管理端 CRUD 界面。 +- 教师关注的核心目标极其聚焦:**“我今天什么时间在哪个场地有课?来了多少学员?是谁没来?”**。 + +### 3.2 页面结构与功能布局 +教师登录后展示两级导航或扁平化工作台: + +1. **教师授课工作台 (`/train/teacher/dashboard`)**: + - **顶部指标卡片**: + - 今日待授课次数量(如:`2 节`) + - 当前任教培训班总数(如:`1 个班`) + - 本周授课累计学时(如:`8 课时`) + - **核心日历/日程看板 (My Timetable)**: + - 复用 Element UI 的 `el-calendar` 或 FullCalendar 组件,以时间轴形式高亮渲染当前教师的授课排课卡片。 + - 卡片显示:课程名称(如《钢结构实训》)、实训场地(如实训区 A)、起止时间(08:30 - 10:00)。 + - 点击课次卡片:右侧弹出该课次学员即时出勤抽屉,显示打卡名单与迟到/缺勤人员。 +2. **任教班级与学员名册 (`/train/teacher/classes`)**: + - 列表展示当前教师授课的培训计划(班级名称、培训周期、计划状态)。 + - 点击“查看花名册”:弹出学员表格,展示学员姓名、单位、报到状态与出勤率概览(身份证脱敏为后四位,手机号中间脱敏,保护学员隐私)。 + +--- + +## 4. 学员端页面架构方案 (Student Portal) + +### 4.1 核心设计理念:个人培训档案,极简清晰 +- 学员作为铁路一线职工,需要操作极其简便、关键业务一目了然的个人看板。 +- 主页为单页响应式布局,分为四大信息模块: + +``` ++─────────────────────────────────────────────────────────────────────────────+ +| 【模块 1: 我的当前培训状态条】 | +| 当前班级: 铁路综合培训第一期 | 培训周期: 2026-09-15 至 2026-09-20 | +| [立项编制] ────> [✔ 待报到] ────> [已报到] ────> [在训学习] ────> [结训归档] | +| 状态: 已成功报到 (报到时间: 2026-09-15 12:35) | 分配场地: 实训区A | ++─────────────────────────────────────────────────────────────────────────────+ +| 【模块 2: 我的今日课程与日程安排】 | +| ┌─────────────────────────┐ ┌─────────────────────────┐ | +| │ 上午 08:30 - 10:00 │ │ 下午 14:00 - 16:30 │ | +| │ 《铁路安全理论》 │ │ 《钢结构实训》 │ | +| │ 地点: 101教室 │ │ 地点: 实训区A │ | +| │ 讲师: 王老师 │ │ 讲师: 王老师 │ | +| └─────────────────────────┘ └─────────────────────────┘ | ++─────────────────────────────────────────────────────────────────────────────+ +| 【模块 3: 我的出勤与学时汇算】 │ 【模块 4: 我的综合能力画像】 | +| - 正常出勤: 1 次 │ - 出勤表现: 100 分 | +| - 迟到记录: 1 次 (人工修正延误) │ - 理论考核: 92 分 | +| - 累计学时: 4.5 / 24 课时 │ - 实操水平: 95 分 | +| - 签退状态: 已锁定不可篡改 │ - (ECharts 五维雷达图直观渲染) | ++────────────────────────────────────┴────────────────────────────────────────+ +``` + +### 4.2 组件高度复用 +- 模块 1 复用 Element UI 的 `el-steps` 步骤条。 +- 模块 2 复用现有的课表时间计算与场地标签。 +- 模块 3 复用现有的考勤标签状态着色(正常绿、迟到黄、缺勤红)。 +- 模块 4 直接复用管理端已有的 `views/train/portrait/index.vue` 内部封装的 ECharts 雷达渲染引擎,仅入参改为 `/train/me/portrait` 接口数据。 + +--- + +## 5. 跨角色访问越权拦截与前端路由守卫 + +1. **路由强拦截**: + - 若学员尝试手动在浏览器地址栏键入 `/business/teacher` 或 `/business/training-plan`: + - Vue-Router 动态路由根本未注册该 path,直接命中通配路由触发 `404 Not Found`。 +2. **接口强隔离**: + - 即使恶意用户通过 DevTools 抓包提取管理端 API 发送请求,后端 Spring Security 校验缺失 `train:teacher:list` 权限,直接返回 `403 Forbidden`。 +3. **实现成本与性能评估**: + - 视图层改动完全局限在 `build_ui/src/views/train/teacher/` 与 `build_ui/src/views/train/student/` 两个自服务文件夹内,不影响管理端 18 个已有模块的任何已有逻辑。 diff --git a/docs/development/rbac-r2/06_migration_plan.md b/docs/development/rbac-r2/06_migration_plan.md new file mode 100644 index 000000000..922cc3a7b --- /dev/null +++ b/docs/development/rbac-r2/06_migration_plan.md @@ -0,0 +1,112 @@ +# RailwaySystem 数据库迁移方案设计 (Migration Plan) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **适用数据库**: 国产主库 KingbaseES V009R003C018 (JDBC 依赖 8.6.0, schema: public) +- **核心目标**: 规范 R.2B 阶段所需的数据库变更脚本清单、序号规则、幂等性保障与零风险回滚方案,确保 100% 兼容现有生产业务。 + +--- + +## 1. 现有正式 Migration 脚本全量排查 + +在设计新脚本前,已完成对代码库中所有既有 SQL 脚本的编号与职责排查: + +### 1.1 `RuoYi-Vue/sql/system/kingbase/` (系统级通用补丁) +- `001_sys_oper_log_cost_time.sql`: 操作日志表新增 `cost_time` 耗时列 (BIGINT DEFAULT 0)。 +- `002_legacy_analytics_permissions.sql`: 学员画像与学习时长按钮权限补齐。 +- `003_report_attendance_action_permissions.sql`: 报到确认与课次考勤核心 Action 权限补齐。 +- **最新可用起始编号**: `004`。 + +### 1.2 `RuoYi-Vue/sql/train/v2/` (培训业务领域专用) +- `000_preflight.sql` ~ `010_verify.sql` (覆盖 V2 架构演进全量表调整与数据过渡)。 +- **红线警示**: `007_dicts.sql` 严禁在生产或后续环节执行 (**DO NOT EXECUTE**)。 + +--- + +## 2. R.2 计划发布的数据库迁移脚本清单 (PROPOSED_MIGRATIONS) + +为避免目录混杂,R.2 的 RBAC 迁移脚本统一归入 `RuoYi-Vue/sql/system/kingbase/` 序列: + +``` +RuoYi-Vue/sql/system/kingbase/ +├── 004_rbac_identity_binding.sql # 物理约束与身份唯一性索引 +└── 005_training_roles_permissions.sql # 角色预置、菜单补全与权限映射 +``` + +### 2.1 脚本 1: `004_rbac_identity_binding.sql` +- **执行目标**: + 1. 幂等确认 `train_teacher` 和 `train_student` 的 `user_id BIGINT` 列。 + 2. 建立针对非空 `user_id` 的部分唯一索引(Partial Unique Index),保障一个登录账户只能绑定一个业务主体。 + 3. 增加注释与完整性元数据。 +- **DDL 设计核心内容**: + ```sql + BEGIN; + + -- 1. 确保列存在 (兼容早期分支) + ALTER TABLE train_teacher ADD COLUMN IF NOT EXISTS user_id BIGINT; + ALTER TABLE train_student ADD COLUMN IF NOT EXISTS user_id BIGINT; + + -- 2. 建立教师账号唯一索引 (忽略已删除 del_flag='2' 与空值) + DROP INDEX IF EXISTS uq_train_teacher_user_id; + CREATE UNIQUE INDEX uq_train_teacher_user_id + ON train_teacher(user_id) + WHERE user_id IS NOT NULL AND del_flag = '0'; + + -- 3. 建立学员账号唯一索引 + DROP INDEX IF EXISTS uq_train_student_user_id; + CREATE UNIQUE INDEX uq_train_student_user_id + ON train_student(user_id) + WHERE user_id IS NOT NULL AND del_flag = '0'; + + COMMENT ON COLUMN train_teacher.user_id IS '关联sys_user.user_id (唯一绑定教师登录账号)'; + COMMENT ON COLUMN train_student.user_id IS '关联sys_user.user_id (唯一绑定学员登录账号)'; + + COMMIT; + ``` + +### 2.2 脚本 2: `005_training_roles_permissions.sql` +- **执行目标**: + 1. 预置 3 个业务角色 (`train_admin`, `train_teacher`, `train_student`) 写入 `sys_role`。 + 2. 补齐 `sys_menu` 中历史遗漏的基础模块按钮级权限(`train:teacher:*`, `train:student:*`, `train:course:*`, `train:plan:*`, `train:lesson:*` 等)。 + 3. 将 `system:board:*` 权限标识收敛对齐为 `train:board:*`。 + 4. 插入 Teacher 工作台菜单与 Student 个人中心菜单节点。 + 5. 幂等配置 `sys_role_menu` 角色菜单权限映射。 +- **DML 设计核心内容**: + ```sql + BEGIN; + + -- 1. 预置角色 (使用特定固定 role_id 3, 4, 5,采用 ON CONFLICT DO NOTHING 保证幂等) + INSERT INTO sys_role (role_id, role_name, role_key, role_sort, data_scope, menu_check_strictly, dept_check_strictly, status, del_flag, create_by, create_time, remark) + VALUES + (3, '培训管理员', 'train_admin', 3, '1', 1, 1, '0', '0', 'admin', CURRENT_TIMESTAMP, '一期业务全局管理角色'), + (4, '培训教师', 'train_teacher', 4, '2', 1, 1, '0', '0', 'admin', CURRENT_TIMESTAMP, '教师专属教学工作台'), + (5, '培训学员', 'train_student', 5, '2', 1, 1, '0', '0', 'admin', CURRENT_TIMESTAMP, '学员专属个人培训中心') + ON CONFLICT (role_id) DO NOTHING; + + -- 2. 补齐基础管理模块按钮权限 (示例:教师管理按钮) + -- 采用 WHERE NOT EXISTS 确保多次执行安全幂等 + -- ... (录入 train:teacher:list/add/edit/query/export 等节点) + + -- 3. 统一规范电子班牌命名空间 + UPDATE sys_menu SET perms = REPLACE(perms, 'system:board:', 'train:board:') + WHERE perms LIKE 'system:board:%'; + + -- 4. 建立角色与菜单权限关联 (sys_role_menu) + -- train_admin: 分配全部培训业务管理菜单及按钮 + -- train_teacher: 分配教师工作台、我的课表、我的班级学员菜单 + -- train_student: 分配学员中心、我的课表、我的报到、我的考勤菜单 + + COMMIT; + ``` + +--- + +## 3. 生产兼容性与无损回滚设计 (Rollback Safety) + +1. **绝对向前兼容 (Zero Breaking Change)**: + - 现有超级管理员 `admin (role_id=1)` 依靠 `*:*:*` 拥有全量通配权限,新增角色与菜单对已有超管管理流程无任何不良影响。 + - 所有已有历史数据中 `train_teacher.user_id` 和 `train_student.user_id` 为 NULL 的记录完全保留,部分唯一索引不校验 NULL,历史数据不会产生约束冲突。 +2. **零 DDL 锁表风险**: + - KingbaseES / PostgreSQL 架构中,`ADD COLUMN IF NOT EXISTS` 与 `CREATE INDEX ... WHERE ...` 操作毫秒级完成,不锁死数据读写。 +3. **独立可执行与验证**: + - 脚本必须具备单向独立执行性,并在脚本末尾集成 `DO $$ ... RAISE NOTICE ... $$;` 自检核验块。 diff --git a/docs/development/rbac-r2/07_e2e_r2_acceptance_plan.md b/docs/development/rbac-r2/07_e2e_r2_acceptance_plan.md new file mode 100644 index 000000000..ca4940e74 --- /dev/null +++ b/docs/development/rbac-r2/07_e2e_r2_acceptance_plan.md @@ -0,0 +1,85 @@ +# RailwaySystem 三角色端到端验收方案设计 (E2E-R2 Acceptance Plan) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 全面规划未来在 R.2B 实现落地后,用于终审验收的三角色(Admin, Teacher, Student)全链路端到端自动化与人工验收方案。 + +--- + +## 1. 验收环境与前置准备 + +- **部署环境**: 生产环境或预发布环境(KingbaseES V009R003C018 数据库已执行 `004_...sql` 与 `005_...sql`)。 +- **统一访问入口**: `http://192.168.139.230:8082/login`。 +- **命名空间约定**: 验收专用账号统一带有前缀 `R2_`(如 `R2_admin`, `R2_teacher_wang`, `R2_student_zhang`)。 + +--- + +## 2. 五阶段端到端全流程验收矩阵 (Stages A ~ E) + +### 2.1 Stage A: 管理员建档与账号分配 (Admin Operations) +- **用例编号**: `E2E-R2-A01` ~ `A04` +- **执行角色**: `train_admin` (培训管理员) 或超级管理员 +- **操作步骤**: + 1. 登录系统,进入【师资队伍管理】,新增教师 `R2_王老师`,勾选【同时开通登录账号】,设置账号 `r2_teacher`,初始密码 `Railway@2026`。 + 2. 进入【学员档案管理】,新增学员 `R2_张三` 与 `R2_李四`,分别开通账号 `r2_student_01` 与 `r2_student_02`。 + 3. 创建培训计划《R2_高速铁路综合运维实训班》,完成课程绑定与批量分班入班。 + 4. 编排课次并分配 `R2_王老师` 担任授课教师。 +- **验收断言**: + - `train_teacher.user_id` 与 `train_student.user_id` 成功写入,非 NULL。 + - `sys_user_role` 正确关联 `train_teacher` (role_id=4) 与 `train_student` (role_id=5)。 + +### 2.2 Stage B: 教师独立工作台与数据隔离 (Teacher Experience) +- **用例编号**: `E2E-R2-B01` ~ `B05` +- **执行角色**: `r2_teacher` (教师身份) +- **操作步骤**: + 1. 使用 `r2_teacher` 登录统一登录页。 + 2. 验证动态 Landing:自动重定向至教师工作台 `/train/teacher/dashboard`。 + 3. 查验侧边栏:仅包含【教师工作台】、【我的授课日程】、【任教班级学员】,**绝无**任何系统管理、全量计划维护、场地配置等管理菜单。 + 4. 查验日历看板:仅显示本人被排课的课次,不显示其他教师课次。 + 5. 查验学员花名册:仅显示自己所带班级的学员名单,且身份证号自动脱敏(后四位)。 + 6. **安全渗透测试 (IDOR 越权拦截)**: + - 尝试直接访问管理端接口 `GET /train/student/list` -> 预期返回 `403 Forbidden`。 + - 尝试访问他人课次详情 `GET /train/timetable/lesson/999` -> 预期返回 `403/404` 拒止。 +- **验收判定**: 教师功能闭环完整,菜单受限严格,横向越权拦截率 100%。 + +### 2.3 Stage C: 学员个人中心与隐私隔离 (Student Experience) +- **用例编号**: `E2E-R2-C01` ~ `C06` +- **执行角色**: `r2_student_01` (学员张三) +- **操作步骤**: + 1. 使用 `r2_student_01` 登录系统。 + 2. 验证动态 Landing:自动重定向至学员个人培训中心 `/train/student/portal`。 + 3. 查验侧边栏:仅出现【我的培训】、【我的课表】、【我的报到】、【我的考勤打卡】。 + 4. 查验个人信息:显示张三本人档案,状态为 `待报到 (0)`。 + 5. 查验课表:仅显示张三所在班级的课程日程。 + 6. **安全渗透测试 (IDOR 越权拦截)**: + - 尝试在 DevTools 中将查询参数篡改为李四的 ID:`GET /train/student/{lisi_id}` -> 预期返回 `403 Forbidden`。 + - 尝试提交带有他人 ID 的打卡请求 -> 预期后端强制校验登录人身份,返回 `403/400` 拒止。 + - 查验个人画像与学时:完全只加载张三本人的成长数据。 +- **验收判定**: 学员视角清晰,个人隐私高度受保护,无法读取或篡改其他学员信息。 + +### 2.4 Stage D: 注册入口安全受控策略核验 (Registration Policy) +- **用例编号**: `E2E-R2-D01` ~ `D02` +- **执行角色**: 匿名外部访客 +- **操作步骤**: + 1. 浏览器打开 `/login` 登录页,肉眼核验页面**无任何“立即注册”链接或按钮**。 + 2. 核验登录卡片下方是否清晰呈现受控提示:“*账号由培训管理部门统一创建与授权维护*”。 + 3. 自动化发起网络探测请求 `POST /register`: + - 预期返回状态码 `200`,业务响应 `{"code": 500, "msg": "当前系统没有开启注册功能!"}`。 +- **验收判定**: 注册入口彻底隐藏,受控拦截坚固有效。 + +### 2.5 Stage E: 管理端全流程业务无损回归 (Admin Regression) +- **用例编号**: `E2E-R2-E01` ~ `E30` +- **执行角色**: `admin` / `train_admin` +- **操作步骤**: + - 全量重新执行 E2E-R1 的 30 个标准业务用例(师资、学员、课程、场地、计划、双课次 Soft Warning 确认保存、硬阻断、报到、打卡、签退时间冻结、人工修正迟到、状态联动归档、操作日志)。 +- **验收判定**: 30 个核心管理端用例全部 100% 通过,无任何功能损伤或回归缺陷。 + +--- + +## 3. 验收通过标准 (Exit Criteria) + +1. `TOTAL_R2_CASES = 48` +2. `PASSED_R2_CASES = 48 (100%)` +3. `FAILED_CASES = 0` +4. `IDOR_LEAK_COUNT = 0 (全站零越权数据泄露)` +5. `ADMIN_REGRESSION_PASS_RATE = 100%` diff --git a/docs/development/rbac-r2/08_implementation_task_breakdown.md b/docs/development/rbac-r2/08_implementation_task_breakdown.md new file mode 100644 index 000000000..fba3645f7 --- /dev/null +++ b/docs/development/rbac-r2/08_implementation_task_breakdown.md @@ -0,0 +1,136 @@ +# RailwaySystem R.2B 落地实现任务拆分指南 (Implementation Task Breakdown) + +- **设计版本**: R.2A Design Freeze +- **设计日期**: 2026-09-15 +- **核心目标**: 将 R.2B 实现阶段拆解为 10 个粒度适中、依赖严密、支持多工程师并行作业的高效子任务。 + +--- + +## 1. 任务依赖关系与并行推进路线图 + +```mermaid +flowchart TD + subgraph Phase1["阶段一: 数据库与权限基础设施 (前置依赖)"] + B1["RBAC-B1: 身份绑定与约束脚本 (004.sql)"] + B2["RBAC-B2: 角色菜单与权限补全脚本 (005.sql)"] + end + + subgraph Phase2["阶段二: 独立并行开发 (无直接阻塞)"] + B3["RBAC-B3: 教师自服务后端 API (/train/me/teacher)"] + B4["RBAC-B4: 学员自服务后端 API (/train/me/student)"] + B5["RBAC-B5: 教师工作台前端视图 (Teacher Portal)"] + B6["RBAC-B6: 学员个人中心前端视图 (Student Portal)"] + B7["RBAC-B7: 登录页注册策略改造 (隐藏入口)"] + B8["RBAC-B8: 班牌权限命名空间对齐 (system->train)"] + end + + subgraph Phase3["阶段三: 安全联调与全量验收"] + B9["RBAC-B9: 数据范围与 IDOR 安全渗透测试"] + B10["RBAC-B10: 管理端业务无损全量回归 (E2E-R2)"] + end + + B1 --> B3 + B1 --> B4 + B2 --> B3 + B2 --> B4 + B2 --> B5 + B2 --> B6 + + B3 --> B5 + B4 --> B6 + + B5 --> B9 + B6 --> B9 + B7 --> B10 + B8 --> B10 + B9 --> B10 +``` + +--- + +## 2. 详细任务卡片清单 (RBAC-B1 ~ RBAC-B10) + +### 任务 1: RBAC-B1 - 身份绑定与物理约束脚本 (Identity Binding & DDL) +- **依赖关系**: 无前置依赖,首发任务。 +- **任务目标**: + 1. 编写 `RuoYi-Vue/sql/system/kingbase/004_rbac_identity_binding.sql`。 + 2. 针对 `train_teacher(user_id)` 与 `train_student(user_id)` 建立非空部分唯一索引。 +- **完成标志**: 脚本在 Kingbase 生产库显式事务执行通过,唯一性拦截生效。 + +### 任务 2: RBAC-B2 - 角色预置与权限补齐脚本 (Role & Permission Migration) +- **依赖关系**: 依赖 B1 完成。 +- **任务目标**: + 1. 编写 `RuoYi-Vue/sql/system/kingbase/005_training_roles_permissions.sql`。 + 2. 预置 `train_admin` (3), `train_teacher` (4), `train_student` (5) 角色。 + 3. 补齐 `sys_menu` 中基础管理模块缺失的全部按钮级 `perms` 行。 + 4. 建立角色的初始菜单与功能权限绑定映射。 +- **完成标志**: 数据库执行无报错,管理员界面角色管理中可见 3 个新角色。 + +### 任务 3: RBAC-B3 - 教师后端自服务体系实现 (Teacher Backend) +- **依赖关系**: 依赖 B1, B2。与 B4、B7、B8 可完全并行。 +- **任务目标**: + 1. 新增控制器 `TrainTeacherMeController.java` (`/train/me/...`)。 + 2. 实现获取教师本人画像、任教班级、授课课表、班级学员名册(脱敏)与出勤统计。 + 3. 强制从 `SecurityUtils.getUserId()` 提取当前教师 ID。 +- **完成标志**: Swagger 联调成功,篡改身份直接抛出 403。 + +### 任务 4: RBAC-B4 - 学员后端自服务体系实现 (Student Backend) +- **依赖关系**: 依赖 B1, B2。与 B3、B7、B8 可完全并行。 +- **任务目标**: + 1. 新增控制器 `TrainStudentMeController.java` (`/train/me/...`)。 + 2. 实现获取学员本人档案、参训班级状态、个人课表、报到凭据、个人考勤、累计学时与多维画像。 + 3. 强制校验本人归属,杜绝任何外部主键传入。 +- **完成标志**: 接口单元测试通过率 100%。 + +### 任务 5: RBAC-B5 - 教师工作台前端视图开发 (Teacher Frontend) +- **依赖关系**: 依赖 B2 菜单路由,可与 B3 并行(采用 Mock 或接口契约联调)。 +- **任务目标**: + 1. 在 `build_ui/src/views/train/teacher/` 下开发工作台看板与我的日程日历组件。 + 2. 开发任教班级花名册抽屉视图。 +- **完成标志**: 教师登录后正常展现日历课表与花名册。 + +### 任务 6: RBAC-B6 - 学员个人培训中心前端开发 (Student Frontend) +- **依赖关系**: 依赖 B2 菜单路由,可与 B4 并行开发。 +- **任务目标**: + 1. 在 `build_ui/src/views/train/student/` 下开发学员培训个人中心。 + 2. 整合培训流程步骤条、今日课表卡片、个人考勤打卡流水与雷达画像组件。 +- **完成标志**: 学员登录后清晰直观呈现个人培训全流程状态。 + +### 任务 7: RBAC-B7 - 登录界面注册入口安全改造 (Login UI Policy) +- **依赖关系**: 独立任务,可立即开工。 +- **任务目标**: + 1. 修改 `build_ui/src/views/login.vue`,彻底关闭 `register` 开关,移除“立即注册”链接。 + 2. 增加居中提示:“*账号由培训管理部门统一创建与授权维护*”。 +- **完成标志**: 登录页面无注册链接,提示文案清晰优雅。 + +### 任务 8: RBAC-B8 - 班牌权限命名空间统一对齐 (Namespace Alignment) +- **依赖关系**: 独立任务,可立即开工。 +- **任务目标**: + 1. 将后端 `TrainElectronicBoardController.java` 中的 `@PreAuthorize("@ss.hasPermi('system:board:...')")` 统一对齐为 `train:board:...`。 + 2. 将前端 `views/train/board/index.vue` 中的 `v-hasPermi="['system:board:...']"` 统一对齐为 `train:board:...`。 +- **完成标志**: 班牌功能按钮与后端权限完全基于 `train:board:*` 正常鉴权。 + +### 任务 9: RBAC-B9 - 数据范围与 IDOR 安全渗透自动化测试 (IDOR Gate) +- **依赖关系**: 依赖 B3、B4、B5、B6 联调完成。 +- **任务目标**: + 1. 编写跨用户越权自动化探测脚本,模拟 Teacher A 访问 Teacher B 课表,模拟 Student A 访问 Student B 档案与考勤。 + 2. 验证所有直接对象引用均被有效拒止。 +- **完成标志**: `IDOR_LEAK_COUNT = 0`。 + +### 任务 10: RBAC-B10 - 管理端业务全流程无损回归测试 (Admin Regression) +- **依赖关系**: 汇总任务,依赖全部前置任务。 +- **任务目标**: + 1. 执行 E2E-R2 验收方案全流程。 + 2. 执行管理端原有 30 个生产核心用例回归测试。 +- **完成标志**: 管理端核心业务 100% PASS,实现三角色完整 RBAC 闭环。 + +--- + +## 3. 并行施工调度建议 + +- **第 1 梯队 (基础组)**: 执行 `RBAC-B1` 与 `RBAC-B2` (预计 0.5 天),完成数据库准备;同时执行 `RBAC-B7` 与 `RBAC-B8` (预计 0.5 天)。 +- **第 2 梯队 (业务组)**: + - 后端工程师 A 攻坚 `RBAC-B3` (教师后端),前端工程师 A 攻坚 `RBAC-B5` (教师前端); + - 后端工程师 B 攻坚 `RBAC-B4` (学员后端),前端工程师 B 攻坚 `RBAC-B6` (学员前端); + - 两组完全解耦并行。 +- **第 3 梯队 (质量保障组)**: 执行 `RBAC-B9` (越权审计) 与 `RBAC-B10` (全量回归与演示交付)。