3.1 KiB
3.1 KiB
Student 角色分离设计
日期:2026-07-09 | 状态:待审批
背景
钉钉同步 (syncOneUser) 目前对每个用户都自动创建 Student 实体。实际上钉钉用户分为:
- 学员 — 需要 Student 实体(考勤、班级、费用)
- 教职工 — 只需要 User(登录+角色),不需要 Student
钉钉标准 API 不提供用户身份标记。采用 批量建 + 手动摘 策略。
设计
1. Student 状态新增 'staff'
Student.status 现有值:active、graduated、withdrawn、archived
新增:'staff' — 标记为教职工,不作为学员管理。
| status | 含义 | 学员列表显示 | 同步时覆盖? |
|---|---|---|---|
| active | 在读学员 | ✅ | ✅ |
| graduated | 已毕业 | ❌(筛选可见) | ❌ |
| withdrawn | 已退训 | ❌(筛选可见) | ❌ |
| archived | 已归档 | ❌(归档开关) | ❌ |
| staff | 教职工 | ❌ 默认隐藏 | ❌ 不覆盖 |
2. syncOneUser 防护
// 已有 Student 且 status 为非 active → 跳过更新
const existingStudent = await this.studentRepo.findOne({ where: { userId: user.id } });
if (existingStudent && existingStudent.status !== 'active') {
// 用户已被标记为教职工/毕业/退训,不覆盖
return; // 跳过 Student 操作
}
3. API 端点
| 端点 | 权限 | 效果 |
|---|---|---|
PUT /rbac/users/:id/mark-staff |
user:edit |
Student.status → 'staff' |
PUT /rbac/users/:id/mark-student |
user:edit |
Student.status → 'active' |
4. 前端改动
账号管理页面:操作列新增按钮
用户有 Student 且 status='active' → 显示 [标记为教职工]
用户有 Student 且 status='staff' → 显示 [恢复为学员]
用户无 Student → 不显示
学员管理页面:默认筛选 status != 'staff',可通过状态筛选查看。
5. 用户操作流程
同步后 → 所有人在学员列表可见
↓
管理员到「账号管理」→ 找到李老师 → 点「标记为教职工」
↓
Student.status → 'staff'
↓
「学员管理」→ 李老师不再显示
「考勤管理」→ 李老师的考勤数据不参与学生统计
下次同步 → 不会恢复李老师为学员
边际情况
变更文件
| 文件 | 变更 |
|---|---|
entities/student.entity.ts |
status 注释更新(staff 已是有效值,无需改 schema) |
integration/dingtalk.service.ts |
syncOneUser: 已有 Student 且 status!='active' 时跳过 |
rbac/rbac.service.ts |
+markAsStaff、+markAsStudent |
rbac/rbac.controller.ts |
+两个端点 |
students/students.service.ts |
findAll 默认排除 status='staff' |
pages/Users/index.tsx |
+标记/恢复按钮 |
pages/Students/index.tsx |
+staff 状态筛选 |
风险点
- Student 表
status是 varchar,不需要迁移('staff'是新增有效值) - 同步不覆盖的原则:只保护
status != 'active'的 Student,active的依然正常更新 - 如果用户之前没有 Student(纯手工创建的教职工),标记操作报友好错误