2026-09-15 13:30:07 +08:00
|
|
|
|
# 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)
|
|
|
|
|
|
|
2026-09-15 14:26:40 +08:00
|
|
|
|
### 2.1 一期多身份决策:业务主体身份互斥 (Teacher XOR Student)
|
|
|
|
|
|
- 在铁路职工技能培训体系中,明确区分角色权限(Role)与业务档案身份(Identity):
|
|
|
|
|
|
- **教务管理员 (Admin)**: 是系统权限角色(Role),负责全局资源调配、排课、分班与监控。
|
|
|
|
|
|
- **任课教师 (Teacher)**: 是教学业务身份(Identity),负责授课、课次考勤与教学答疑。
|
|
|
|
|
|
- **参训职工 (Student)**: 是参训业务身份(Identity),专职参与培训、打卡签到与考核画像。
|
|
|
|
|
|
- **冻结决策**:
|
|
|
|
|
|
- **Teacher 与 Student 业务身份严格互斥**:严禁同一个 `sys_user` 既绑定教师又绑定学员。
|
|
|
|
|
|
- **Admin 是角色而非业务身份**:拥有 Admin 角色或管理权限的账号,若同时承担教学任务,允许绑定 Teacher 业务档案(Admin Role + Teacher Identity),并在自服务工作台中以当前教师身份推导数据范围。
|
|
|
|
|
|
- **超级管理员账号环境纪律**:对默认超级管理员测试账号(如 `admin / userId=1`)的保护应通过运维环境数据纪律落实,业务服务层严禁硬编码 `userId=1` 永久禁止绑定。
|
2026-09-15 13:30:07 +08:00
|
|
|
|
|
|
|
|
|
|
### 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';
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-15 14:26:40 +08:00
|
|
|
|
### 2.3 业务服务层互斥前置校验与跨表并发锁
|
|
|
|
|
|
在新建或绑定账号时,在 Java Service 事务中实施悲观锁与互斥断言:
|
2026-09-15 13:30:07 +08:00
|
|
|
|
```java
|
2026-09-15 14:26:40 +08:00
|
|
|
|
public void validateUserIdentityExclusive(Long userId, String targetType, String targetBusinessId) {
|
2026-09-15 13:30:07 +08:00
|
|
|
|
if (userId == null) return;
|
2026-09-15 14:26:40 +08:00
|
|
|
|
if ("TEACHER".equalsIgnoreCase(targetType)) {
|
|
|
|
|
|
if (studentMapper.selectTrainStudentByUserId(userId) != null) {
|
|
|
|
|
|
throw new ServiceException("该系统登录账号已绑定为学员身份,不可重复绑定为教师");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if ("STUDENT".equalsIgnoreCase(targetType)) {
|
|
|
|
|
|
if (teacherMapper.selectTrainTeacherByUserId(userId) != null) {
|
|
|
|
|
|
throw new ServiceException("该系统登录账号已绑定为教师身份,不可重复绑定为学员");
|
|
|
|
|
|
}
|
2026-09-15 13:30:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 用户创建与账号生命周期联动机制
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 账号创建推荐模式:表单内可选“同时开通系统账号”
|
|
|
|
|
|
- **业务流程**:
|
|
|
|
|
|
1. 管理员在录入教师或学员档案时,表单底部勾选【开通系统登录账号】开关(默认勾选)。
|
|
|
|
|
|
2. 系统自动推荐登录账号格式:
|
|
|
|
|
|
- **教师账号**: 优先采用工号(如 `T2026001`),若未填工号则采用手机号。
|
|
|
|
|
|
- **学员账号**: 优先采用学员编号/工号(如 `GH001`),若无则采用手机号。
|
|
|
|
|
|
3. **初始密码策略**:
|
|
|
|
|
|
- 统一设置初始受控密码:`Railway@2026`。
|
|
|
|
|
|
- 采用 BCrypt 强哈希加密存储入库。
|
|
|
|
|
|
- 强制将 `sys_user.pwd_update_date` 设为 `NULL`,触发前端首次登录时强制要求用户修改密码的安全拦截。
|
|
|
|
|
|
4. **角色自动赋予**:
|
|
|
|
|
|
- 创建教师账号时,自动关联向 `sys_user_role` 插入 `role_id = <train_teacher>`。
|
|
|
|
|
|
- 创建学员账号时,自动关联向 `sys_user_role` 插入 `role_id = <train_student>`。
|
|
|
|
|
|
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` 在任何时候均可完整追溯责任人。
|