Files
gongxue-base/docs/superpowers/specs/2026-07-09-student-role-separation-design.md

3.1 KiB
Raw Blame History

Student 角色分离设计

日期2026-07-09 | 状态:待审批

背景

钉钉同步 (syncOneUser) 目前对每个用户都自动创建 Student 实体。实际上钉钉用户分为:

  • 学员 — 需要 Student 实体(考勤、班级、费用)
  • 教职工 — 只需要 User登录+角色),不需要 Student

钉钉标准 API 不提供用户身份标记。采用 批量建 + 手动摘 策略。

设计

1. Student 状态新增 'staff'

Student.status 现有值:activegraduatedwithdrawnarchived

新增:'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' 的 Studentactive 的依然正常更新
  • 如果用户之前没有 Student纯手工创建的教职工标记操作报友好错误