# api/students.py # 学生模块接口:增、查(多条件分页)、改、逻辑删除 from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from core.deps import get_current_user, require_roles, get_teacher_class_ids from dao.students_dao import StudentDAO from database import get_db from model.user import User from scheme.students import StudentAdd, StudentUpdate, StudentResponse, StudentListResponse router = APIRouter() def _can_read_student(user: User, student) -> bool: """学生角色只能读取自己的信息""" if user.role == "student": return user.stu_id == student.stu_id return True def _can_manage_student(db: Session, user: User, student) -> bool: """教师角色只能管理自己所带班级的学生;管理员不受限""" if user.role == "admin": return True if user.role == "teacher": return student.class_id in get_teacher_class_ids(db, user.teacher_id) return False # ---------- 创建新学生 ---------- @router.post("/add", response_model=StudentResponse, summary="创建学生(学号可自动生成)") async def add_student( student: StudentAdd, db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): # 外键校验:班级 / 顾问 if not StudentDAO.inspect_class_id_unq(db, student.class_id): raise HTTPException(status_code=404, detail="班级ID不存在,或已被软删除") if not StudentDAO.inspect_advisor_id_unq(db, student.advisor_id): raise HTTPException(status_code=404, detail="顾问ID不存在,或已被软删除") # 教师只能给自己班上的学生建档 if current_user.role == "teacher" and student.class_id not in get_teacher_class_ids( db, current_user.teacher_id ): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级的学生") # 主键校验(显式传入学号时) if student.stu_id is not None: existing = StudentDAO.inspect_student_id_unq(db, student.stu_id) if existing: raise HTTPException(status_code=409, detail="学号已存在(包括软删除记录)") return StudentDAO.add_student(db, student) # ---------- 查询学生列表(多条件 + 分页) ---------- @router.get("/total_query", response_model=StudentListResponse, summary="查询学生列表(多条件筛选+分页)") async def get_students( skip: int = Query(0, ge=0, description="跳过的记录数"), limit: int = Query(100, ge=1, le=200, description="返回的最大记录数"), stu_id: Optional[int] = Query(None, description="按学号精确查询"), stu_name: Optional[str] = Query(None, description="按姓名模糊查询"), class_id: Optional[int] = Query(None, description="按班级ID筛选"), gender: Optional[str] = Query(None, description="按性别筛选:男/女"), status: Optional[str] = Query(None, description="按状态筛选:在读/进入就业/已就业"), education: Optional[str] = Query(None, description="按学历筛选"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): total, items = StudentDAO.get_all( db, skip=skip, limit=limit, stu_id=stu_id, stu_name=stu_name, class_id=class_id, gender=gender, status=status, education=education, ) # 学生角色只能看到自己 if current_user.role == "student": items = [s for s in items if s.stu_id == current_user.stu_id] total = len(items) return StudentListResponse( total=total, items=[StudentResponse.model_validate(s) for s in items], ) # ---------- 根据 ID 查询单个学生 ---------- @router.get("/single_query", response_model=StudentResponse, summary="根据学号查询学生") async def get_student( stu_id: int = Query(..., ge=1, description="学号"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): student = StudentDAO.get_active(db, stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_read_student(current_user, student): raise HTTPException(status_code=403, detail="学生角色只能查看自己的信息") resp = StudentResponse.model_validate(student) # 联表补充班级名与顾问名 if student.classes: resp.class_name = student.classes.class_name if student.advisor: resp.advisor_name = student.advisor.advisor_name return resp # ---------- 更新学生信息 ---------- @router.put("/update", response_model=StudentResponse, summary="更新学生信息") async def update_student( student_data: StudentUpdate, stu_id: int = Query(..., ge=1, description="要更新的学号"), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): student = StudentDAO.get_active(db, stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_manage_student(db, current_user, student): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级的学生") updated = StudentDAO.update(db, stu_id, student_data) return StudentResponse.model_validate(updated) # ---------- 逻辑删除学生 ---------- @router.delete("/delete", status_code=204, summary="逻辑删除学生") async def delete_student( stu_id: int = Query(..., ge=1, description="要删除的学号"), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): student = StudentDAO.get_active(db, stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_manage_student(db, current_user, student): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级的学生") success = StudentDAO.delete_light(db, stu_id) if not success: raise HTTPException(status_code=404, detail="学生不存在") return None