From 81ea44fbabf735cc039d41cf5d898dce57b3568f Mon Sep 17 00:00:00 2001 From: canglan Date: Fri, 10 Apr 2026 15:01:21 +0800 Subject: [PATCH] =?UTF-8?q?v0.2=E6=B5=8B=E8=AF=95=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/middleware/permission.py | 54 ++++++++++++- backend/routes/admin.py | 127 +++++++------------------------ 2 files changed, 78 insertions(+), 103 deletions(-) diff --git a/backend/middleware/permission.py b/backend/middleware/permission.py index f69cb4a..04eae38 100644 --- a/backend/middleware/permission.py +++ b/backend/middleware/permission.py @@ -12,10 +12,16 @@ from fastapi import Request from typing import List, Optional, Callable, Dict, Any from functools import wraps + from utils.response import forbidden_response from utils.database import execute_one +from utils.logger import get_logger + +logger = get_logger(__name__) + async def get_current_user(request: Request) -> Dict[str, Any]: + """获取当前登录用户信息""" return { "user_id": getattr(request.state, 'user_id', None), "username": getattr(request.state, 'username', None), @@ -24,48 +30,66 @@ async def get_current_user(request: Request) -> Dict[str, Any]: "role": getattr(request.state, 'role', None) } + async def get_current_user_id(request: Request) -> int: + """获取当前用户ID""" return getattr(request.state, 'user_id', None) + class PermissionChecker: + """权限检查器""" + @staticmethod async def get_user_role(user_id: int) -> Optional[str]: + """获取用户的管理员角色""" sql = "SELECT role_type FROM admin_roles WHERE user_id = %s LIMIT 1" result = await execute_one(sql, (user_id,)) return result["role_type"] if result else None @staticmethod async def check_is_teacher(user_id: int) -> bool: + """检查是否为班主任""" role = await PermissionChecker.get_user_role(user_id) return role == "班主任" @staticmethod async def check_is_monitor(user_id: int) -> bool: + """检查是否为班长""" role = await PermissionChecker.get_user_role(user_id) return role == "班长" @staticmethod async def check_is_study_commissioner(user_id: int) -> bool: + """检查是否为学习委员""" role = await PermissionChecker.get_user_role(user_id) return role == "学习委员" @staticmethod async def check_is_attendance_rep(user_id: int) -> bool: + """检查是否为考勤委员""" role = await PermissionChecker.get_user_role(user_id) return role == "考勤委员" @staticmethod async def check_is_labor_rep(user_id: int) -> bool: + """检查是否为劳动委员""" role = await PermissionChecker.get_user_role(user_id) return role == "劳动委员" @staticmethod async def check_can_manage_subjects(user_id: int) -> bool: + """检查是否可以管理科目(班主任或学习委员)""" role = await PermissionChecker.get_user_role(user_id) return role in ["班主任", "学习委员"] @staticmethod async def check_can_revoke(user_id: int, record_id: int) -> bool: + """ + 检查是否可以撤销扣分记录 + 班主任:可以撤销任何记录 + 班长:可以撤销任何记录 + 其他:只能撤销自己的记录 + """ sql = "SELECT recorder_id FROM conduct_records WHERE record_id = %s" record = await execute_one(sql, (record_id,)) if not record: @@ -75,7 +99,9 @@ class PermissionChecker: return True return record["recorder_id"] == user_id + def require_auth(func: Callable): + """需要认证的装饰器""" @wraps(func) async def wrapper(*args, **kwargs): request = kwargs.get('request') @@ -84,7 +110,9 @@ def require_auth(func: Callable): return await func(*args, **kwargs) return wrapper + def require_role(roles: List[str]): + """需要特定角色的装饰器""" def decorator(func: Callable): @wraps(func) async def wrapper(*args, **kwargs): @@ -99,24 +127,44 @@ def require_role(roles: List[str]): return wrapper return decorator + def require_teacher(func: Callable): + """需要班主任权限的装饰器""" @wraps(func) async def wrapper(*args, **kwargs): request = kwargs.get('request') if not request or not hasattr(request.state, 'user_id'): return forbidden_response("请先登录") - if not await PermissionChecker.check_is_teacher(request.state.user_id): + is_teacher = await PermissionChecker.check_is_teacher(request.state.user_id) + if not is_teacher: return forbidden_response("需要班主任权限") return await func(*args, **kwargs) return wrapper -def require_study_commissioner(func: Callable): + +def require_monitor(func: Callable): + """需要班长权限的装饰器""" @wraps(func) async def wrapper(*args, **kwargs): request = kwargs.get('request') if not request or not hasattr(request.state, 'user_id'): return forbidden_response("请先登录") - if not await PermissionChecker.check_is_study_commissioner(request.state.user_id): + is_monitor = await PermissionChecker.check_is_monitor(request.state.user_id) + if not is_monitor: + return forbidden_response("需要班长权限") + return await func(*args, **kwargs) + return wrapper + + +def require_study_commissioner(func: Callable): + """需要学习委员权限的装饰器""" + @wraps(func) + async def wrapper(*args, **kwargs): + request = kwargs.get('request') + if not request or not hasattr(request.state, 'user_id'): + return forbidden_response("请先登录") + is_study = await PermissionChecker.check_is_study_commissioner(request.state.user_id) + if not is_study: return forbidden_response("需要学习委员权限") return await func(*args, **kwargs) return wrapper \ No newline at end of file diff --git a/backend/routes/admin.py b/backend/routes/admin.py index 28ab106..c9c2e20 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -14,9 +14,8 @@ from typing import Optional, List import json from middleware.permission import ( - get_current_user, - require_teacher, - require_monitor, + get_current_user, + require_teacher, PermissionChecker ) from services.admin_service import AdminService @@ -27,7 +26,7 @@ from schemas.admin import ( AddPointsRequest, RevokeRequest, AddAdminRequest, UpdateHomeworkStatusRequest, AddAttendanceRequest ) -from utils.response import success_response, error_response, paginated_response +from utils.response import success_response, error_response from utils.logger import get_logger from config import settings @@ -44,50 +43,30 @@ async def get_students( page_size: int = Query(20, ge=1, le=100), search: Optional[str] = None ): - """ - 获取所有学生列表(单班级) - """ + """获取所有学生列表(单班级)""" user = await get_current_user(request) - - result = await AdminService.get_students( - page=page, - page_size=page_size, - search=search - ) - + result = await AdminService.get_students(page=page, page_size=page_size, search=search) return success_response(data=result) @router.post("/students/import") -async def import_students( - request: Request, - file: UploadFile = File(...) -): - """ - 批量导入学生(JSON格式) - 初始操行分默认为60分 - """ +async def import_students(request: Request, file: UploadFile = File(...)): + """批量导入学生(JSON格式),初始操行分默认为60分""" user = await get_current_user(request) - - # 检查权限(仅班主任) is_teacher = await PermissionChecker.check_is_teacher(user["user_id"]) if not is_teacher: return error_response(message="仅班主任可导入学生", code=403) - # 检查文件大小 content = await file.read() file_size = len(content) - if file_size > settings.MAX_UPLOAD_SIZE: return error_response(message=f"文件大小不能超过{settings.MAX_UPLOAD_SIZE // 1024 // 1024}MB") - # 检查文件扩展名 filename = file.filename or "" extension = filename.split('.')[-1].lower() if '.' in filename else '' if extension not in settings.ALLOWED_EXTENSIONS: return error_response(message=f"不支持的文件类型,仅支持 {', '.join(settings.ALLOWED_EXTENSIONS)}") - # 解析JSON try: data = json.loads(content.decode('utf-8')) students = data.get("students", []) @@ -99,13 +78,11 @@ async def import_students( if not students: return error_response(message="文件中没有学生数据") - # 导入学生(初始操行分60分) result = await AdminService.import_students( students=students, operator_id=user["user_id"], initial_points=60 ) - return success_response(data=result, message=f"导入完成: 成功{result['success_count']}人,失败{result['failed_count']}人") @@ -116,11 +93,8 @@ async def add_student( name: str, parent_phone: Optional[str] = None ): - """ - 新增学生 - """ + """新增学生""" user = await get_current_user(request) - is_teacher = await PermissionChecker.check_is_teacher(user["user_id"]) if not is_teacher: return error_response(message="仅班主任可新增学生", code=403) @@ -132,7 +106,6 @@ async def add_student( operator_id=user["user_id"], initial_points=60 ) - if result["success"]: return success_response(data=result, message="学生添加成功") else: @@ -143,11 +116,8 @@ async def add_student( @router.post("/conduct/add") async def add_conduct_points(request: Request, req: AddPointsRequest): - """ - 批量加减分 - """ + """批量加减分""" user = await get_current_user(request) - result = await ConductService.add_points( student_ids=req.student_ids, points_change=req.points_change, @@ -155,7 +125,6 @@ async def add_conduct_points(request: Request, req: AddPointsRequest): recorder_id=user["user_id"], recorder_name=user["username"] ) - if result["success"]: return success_response(data=result, message="操作成功") else: @@ -164,16 +133,12 @@ async def add_conduct_points(request: Request, req: AddPointsRequest): @router.post("/conduct/revoke") async def revoke_conduct_record(request: Request, req: RevokeRequest): - """ - 撤销扣分记录 - """ + """撤销扣分记录""" user = await get_current_user(request) - result = await ConductService.revoke_record( record_id=req.record_id, revoker_id=user["user_id"] ) - if result["success"]: return success_response(message="撤销成功") else: @@ -189,11 +154,8 @@ async def get_conduct_history( start_date: Optional[str] = None, end_date: Optional[str] = None ): - """ - 获取操行分历史记录 - """ + """获取操行分历史记录""" user = await get_current_user(request) - result = await ConductService.get_history( user_id=user["user_id"], student_id=student_id, @@ -202,7 +164,6 @@ async def get_conduct_history( start_date=start_date, end_date=end_date ) - return success_response(data=result) @@ -210,31 +171,26 @@ async def get_conduct_history( @router.get("/homework/assignments") async def get_assignments(request: Request): - """ - 获取作业列表 - """ + """获取作业列表""" user = await get_current_user(request) role = await PermissionChecker.get_user_role(user["user_id"]) if role not in ["班主任", "学习委员"]: return error_response(message="无权限", code=403) - result = await HomeworkService.get_assignments(user["user_id"]) - return success_response(data=result) @router.get("/homework/submissions/{assignment_id}") async def get_submissions(request: Request, assignment_id: int): - """ - 获取作业提交记录 - """ + """获取作业提交记录""" user = await get_current_user(request) - + role = await PermissionChecker.get_user_role(user["user_id"]) + if role not in ["班主任", "学习委员"]: + return error_response(message="无权限", code=403) result = await HomeworkService.get_submissions( assignment_id=assignment_id, user_id=user["user_id"] ) - return success_response(data=result) @@ -246,15 +202,11 @@ async def create_assignment( description: Optional[str] = None, deadline: str = None ): - """ - 发布作业(班主任) - """ + """发布作业(班主任)""" user = await get_current_user(request) - is_teacher = await PermissionChecker.check_is_teacher(user["user_id"]) if not is_teacher: return error_response(message="仅班主任可发布作业", code=403) - result = await HomeworkService.create_assignment( subject_id=subject_id, title=title, @@ -262,7 +214,6 @@ async def create_assignment( deadline=deadline, created_by=user["user_id"] ) - if result["success"]: return success_response(data=result, message="作业发布成功") else: @@ -271,11 +222,11 @@ async def create_assignment( @router.put("/homework/submission") async def update_submission_status(request: Request, req: UpdateHomeworkStatusRequest): + """更新作业提交状态(班主任或学习委员)""" user = await get_current_user(request) role = await PermissionChecker.get_user_role(user["user_id"]) if role not in ["班主任", "学习委员"]: return error_response(message="无权进行此操作", code=403) - result = await HomeworkService.update_submission_status( submission_id=req.submission_id, status=req.status, @@ -283,7 +234,6 @@ async def update_submission_status(request: Request, req: UpdateHomeworkStatusRe apply_deduction=req.apply_deduction, operator_id=user["user_id"] ) - if result["success"]: return success_response(message="状态更新成功") else: @@ -294,11 +244,11 @@ async def update_submission_status(request: Request, req: UpdateHomeworkStatusRe @router.post("/attendance") async def add_attendance(request: Request, req: AddAttendanceRequest): - """ - 添加考勤记录(考勤委员) - """ + """添加考勤记录(考勤委员)""" user = await get_current_user(request) - + role = await PermissionChecker.get_user_role(user["user_id"]) + if role not in ["班主任", "考勤委员"]: + return error_response(message="无权进行此操作", code=403) result = await AttendanceService.add_attendance( student_id=req.student_id, date=str(req.date), @@ -307,7 +257,6 @@ async def add_attendance(request: Request, req: AddAttendanceRequest): apply_deduction=req.apply_deduction, recorder_id=user["user_id"] ) - if result["success"]: return success_response(message="考勤记录添加成功") else: @@ -320,17 +269,13 @@ async def get_attendance_records( date: Optional[str] = None, student_id: Optional[int] = None ): - """ - 获取考勤记录 - """ + """获取考勤记录""" user = await get_current_user(request) - result = await AttendanceService.get_records( user_id=user["user_id"], date=date, student_id=student_id ) - return success_response(data=result) @@ -338,15 +283,11 @@ async def get_attendance_records( @router.post("/admin/add") async def add_admin(request: Request, req: AddAdminRequest): - """ - 添加管理员(班主任) - """ + """添加管理员(班主任)""" user = await get_current_user(request) - - if not await PermissionChecker.check_is_teacher(user["user_id"]): + is_teacher = await PermissionChecker.check_is_teacher(user["user_id"]) + if not is_teacher: return error_response(message="仅班主任可添加管理员", code=403) - - # 验证角色类型是否合法 if req.role_type not in ["班长", "学习委员", "考勤委员", "劳动委员"]: return error_response(message="无效的角色类型", code=400) result = await AdminService.add_admin( @@ -356,15 +297,6 @@ async def add_admin(request: Request, req: AddAdminRequest): role_type=req.role_type, operator_id=user["user_id"] ) - - result = await AdminService.add_admin( - username=req.username, - real_name=req.real_name, - password=req.password, - role_type=req.role_type, - operator_id=user["user_id"] - ) - if result["success"]: return success_response(data=result, message="管理员添加成功") else: @@ -373,15 +305,10 @@ async def add_admin(request: Request, req: AddAdminRequest): @router.get("/admin/list") async def get_admins(request: Request): - """ - 获取管理员列表(班主任) - """ + """获取管理员列表(班主任)""" user = await get_current_user(request) - is_teacher = await PermissionChecker.check_is_teacher(user["user_id"]) if not is_teacher: return error_response(message="仅班主任可查看管理员列表", code=403) - result = await AdminService.get_admins() - return success_response(data=result) \ No newline at end of file