Skip to content

08_作业权限系统设计

概述

作业权限系统基于用户角色和学校归属关系,实现了细粒度的权限控制。系统支持四种用户角色:学生、老师、学校管理员和平台管理员。

权限矩阵

操作学生老师学校管理员平台管理员
查看作业详情✅ 仅本班级✅ 仅本班级✅ 仅本学校✅ 所有
提交作业✅ 仅本班级
创建作业✅ 仅本班级✅ 仅本学校✅ 所有
更新作业✅ 仅本班级✅ 仅本学校✅ 所有
删除作业✅ 仅本班级✅ 仅本学校✅ 所有
查看提交列表✅ 仅本班级✅ 仅本学校✅ 所有
评分作业✅ 仅本班级✅ 仅本学校✅ 所有
设置发布状态✅ 仅本班级✅ 仅本学校✅ 所有
生成AI建议✅ 仅本班级✅ 仅本学校✅ 所有

权限检查逻辑

1. 平台管理员 (PLATFORM_ADMIN)

  • 权限范围: 所有学校的作业
  • 检查逻辑: 无需额外检查,直接通过
  • 使用场景: 系统维护、全局监控、跨学校管理

2. 学校管理员 (SCHOOL_ADMIN)

  • 权限范围: 本学校的所有作业
  • 检查逻辑: 验证班级是否属于该管理员所在的学校
  • 使用场景: 学校内部管理、跨班级作业管理

3. 老师 (TEACHER)

  • 权限范围: 自己担任教师的班级作业
  • 检查逻辑: 验证用户是否在 class_teachers 表中
  • 使用场景: 日常教学、作业管理

4. 学生 (STUDENT)

  • 权限范围: 自己所在班级的作业
  • 检查逻辑: 验证用户是否在 class_members 表中
  • 使用场景: 查看作业、提交作业

核心权限检查方法

AssignmentService.ensure_user_can_manage_assignment()

python
async def ensure_user_can_manage_assignment(self, user: User, assignment_id: int) -> bool:
    """确保用户有权限管理该作业"""
    # 平台管理员可以管理所有作业
    if user.role == UserRole.PLATFORM_ADMIN:
        return True
    
    # 获取作业信息
    assignment = await self.get_assignment(assignment_id)
    if not assignment:
        return False
    
    # 学校管理员可以管理本学校的作业
    if user.role == UserRole.SCHOOL_ADMIN:
        class_result = await self.db.execute(
            select(Class.school_id).where(Class.id == assignment.class_id)
        )
        class_school_id = class_result.scalar_one()
        return class_school_id == user.school_id
    
    # 老师只能管理自己班级的作业
    if user.role == UserRole.TEACHER:
        return await self.ensure_teacher_can_manage_class(user.id, assignment.class_id)
    
    return False

AssignmentService.ensure_user_can_view_assignment()

python
async def ensure_user_can_view_assignment(self, user: User, assignment_id: int) -> bool:
    """确保用户有权限查看该作业"""
    # 平台管理员可以查看所有作业
    if user.role == UserRole.PLATFORM_ADMIN:
        return True
    
    # 获取作业信息
    assignment = await self.get_assignment(assignment_id)
    if not assignment:
        return False
    
    # 学校管理员可以查看本学校的作业
    if user.role == UserRole.SCHOOL_ADMIN:
        class_result = await self.db.execute(
            select(Class.school_id).where(Class.id == assignment.class_id)
        )
        class_school_id = class_result.scalar_one()
        return class_school_id == user.school_id
    
    # 老师只能查看自己班级的作业
    if user.role == UserRole.TEACHER:
        return await self.ensure_teacher_can_manage_class(user.id, assignment.class_id)
    
    # 学生只能查看自己班级的作业
    if user.role == UserRole.STUDENT:
        return await self.ensure_student_in_class(user.id, assignment.class_id)
    
    return False

API 端点权限控制

创建作业权限检查

python
@router.post("/assignments", response_model=AssignmentOut, status_code=201)
async def create_assignment(
    payload: AssignmentCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_teacher_or_admin),
):
    """创建作业"""
    svc = AssignmentService(db)
    
    # 检查用户是否有权限管理该班级的作业
    if not await svc.ensure_user_can_manage_assignment(current_user, 0):
        # 对于新作业,检查用户是否有权限管理该班级
        if current_user.role.value == "teacher":
            if not await svc.ensure_teacher_can_manage_class(current_user.id, payload.class_id):
                raise HTTPException(status_code=403, detail="No permission to manage this class")
        elif current_user.role.value == "school_admin":
            # 检查班级是否属于该学校
            class_result = await db.execute(
                select(Class.school_id).where(Class.id == payload.class_id)
            )
            class_school_id = class_result.scalar_one()
            if class_school_id != current_user.school_id:
                raise HTTPException(status_code=403, detail="No permission to manage this class")
        # platform_admin 可以管理所有班级
    
    assignment = await svc.create_assignment(payload)
    return assignment

测试用例

学校管理员权限测试

python
@pytest.mark.asyncio
async def test_create_assignment_as_school_admin():
    """测试学校管理员创建作业"""
    # 确保班级属于该学校
    assert test_class.school_id == test_school.id
    
    response = await client.post(
        "/assignments",
        json=assignment_data,
        headers={"Authorization": f"Bearer {school_admin_user.id}"}
    )
    
    assert response.status_code == 201

跨学校权限限制测试

python
@pytest.mark.asyncio
async def test_school_admin_cannot_manage_other_school_class():
    """测试学校管理员无法管理其他学校的班级作业"""
    # 确保班级属于其他学校
    assert other_class.school_id == other_school.id
    assert other_class.school_id != school_admin_user.school_id
    
    response = await client.post(
        "/assignments",
        json=assignment_data,
        headers={"Authorization": f"Bearer {school_admin_user.id}"}
    )
    
    assert response.status_code == 403
    assert "No permission to manage this class" in response.json()["detail"]

安全考虑

  1. 角色验证: 所有权限检查都基于用户角色和学校归属关系
  2. 数据隔离: 学校管理员只能访问本学校的数据
  3. 最小权限原则: 用户只能访问必要的功能和数据
  4. 审计日志: 所有权限相关的操作都应该记录日志

扩展性

权限系统设计考虑了未来的扩展需求:

  1. 新角色支持: 可以轻松添加新的用户角色
  2. 权限组: 可以基于权限组实现更细粒度的控制
  3. 动态权限: 可以基于业务规则动态调整权限
  4. 多租户: 支持多租户架构的权限隔离

总结

新的权限系统实现了:

  • ✅ 学校管理员可以管理本学校的所有作业
  • ✅ 平台管理员可以管理所有学校的作业
  • ✅ 老师只能管理自己班级的作业
  • ✅ 学生只能访问自己班级的作业
  • ✅ 严格的权限边界和数据隔离
  • ✅ 完整的测试覆盖
  • ✅ 清晰的权限检查逻辑