v2.2更新

This commit is contained in:
2026-05-28 15:38:32 +08:00
parent f84c9d3efb
commit ca53fdc349
38 changed files with 688 additions and 686 deletions

View File

@@ -21,7 +21,7 @@ from utils.logger import setup_logger, log_access
from utils.database import init_db_pool, close_db_pool
from utils.redis_client import init_redis_pool, close_redis_pool
from middleware.auth_middleware import AuthMiddleware
from routes import auth, student, parent, admin, subject, semester, debug
from routes import auth, student, parent, admin, subject, semester, debug, upgrade
from routes.config import router as config_router
@@ -119,6 +119,7 @@ app.include_router(admin.router, prefix="/api/admin", tags=["管理端"])
app.include_router(subject.router, prefix="/api/subject", tags=["科目管理"])
app.include_router(semester.router, prefix="/api/semester", tags=["学期管理"])
app.include_router(config_router, prefix="/api/config", tags=["配置"])
app.include_router(upgrade.router, prefix="/api/upgrade", tags=["升级管理"])
app.include_router(debug.router, tags=["调试"])

View File

@@ -209,6 +209,7 @@ class ConductModel:
student_id: int = None,
start_date: str = None,
end_date: str = None,
related_type: str = None,
page: int = 1,
page_size: int = 20
) -> Dict[str, Any]:
@@ -217,6 +218,8 @@ class ConductModel:
start_date = None
if end_date == "":
end_date = None
if related_type == "":
related_type = None
conditions = ["cr.is_revoked = 0"]
params = []
@@ -230,6 +233,9 @@ class ConductModel:
if end_date:
conditions.append("cr.created_at <= %s")
params.append(end_date + ' 23:59:59')
if related_type:
conditions.append("cr.related_type = %s")
params.append(related_type)
where_clause = " AND ".join(conditions)

View File

@@ -1,117 +0,0 @@
# ===========================================
# 班级操行分管理系统 - 后端服务
#
# 开发者: Canglan
# 联系方式: admin@sea-studio.top
# 版权归属: Sea Network Technology Studio
# 许可证: MIT License
#
# 版权所有 © Sea Network Technology Studio
# ===========================================
from typing import Optional, Dict, Any, List
from utils.database import execute_one, execute_query, execute_insert, execute_update
class HomeworkModel:
"""作业数据模型"""
@staticmethod
async def get_all_assignments() -> List[Dict[str, Any]]:
sql = """
SELECT a.*, s.subject_name, u.real_name as created_by_name
FROM assignments a
JOIN subjects s ON a.subject_id = s.subject_id
JOIN users u ON a.created_by = u.user_id
ORDER BY a.deadline ASC, a.created_at DESC
"""
return await execute_query(sql)
@staticmethod
async def get_assignments_by_subjects(subject_ids: List[int]) -> List[Dict[str, Any]]:
if not subject_ids:
return []
placeholders = ','.join(['%s'] * len(subject_ids))
sql = f"""
SELECT a.*, s.subject_name, u.real_name as created_by_name
FROM assignments a
JOIN subjects s ON a.subject_id = s.subject_id
JOIN users u ON a.created_by = u.user_id
WHERE a.subject_id IN ({placeholders})
ORDER BY a.deadline ASC, a.created_at DESC
"""
return await execute_query(sql, tuple(subject_ids))
@staticmethod
async def get_student_homework(student_id: int) -> List[Dict[str, Any]]:
sql = """
SELECT a.assignment_id, a.title, a.description, a.deadline, a.created_at,
s.subject_name, hs.status, hs.submit_time, hs.comments, hs.deduction_applied,
cr.points_change AS points
FROM assignments a
JOIN subjects s ON a.subject_id = s.subject_id
LEFT JOIN homework_submissions hs ON a.assignment_id = hs.assignment_id AND hs.student_id = %s
LEFT JOIN conduct_records cr ON cr.related_type = 'homework'
AND cr.related_id = a.assignment_id AND cr.student_id = %s AND cr.is_revoked = 0
GROUP BY a.assignment_id
ORDER BY a.deadline ASC, a.created_at DESC
"""
return await execute_query(sql, (student_id, student_id))
@staticmethod
async def get_submission(submission_id: int) -> Optional[Dict[str, Any]]:
sql = """
SELECT hs.*, a.title, a.subject_id, a.assignment_id, s.name as student_name
FROM homework_submissions hs
JOIN assignments a ON hs.assignment_id = a.assignment_id
JOIN students s ON hs.student_id = s.student_id
WHERE hs.submission_id = %s
"""
return await execute_one(sql, (submission_id,))
@staticmethod
async def create_assignment(
subject_id: int,
title: str,
description: str,
deadline: str,
created_by: int
) -> int:
sql = """
INSERT INTO assignments (subject_id, title, description, deadline, created_by)
VALUES (%s, %s, %s, %s, %s)
"""
assignment_id = await execute_insert(sql, (subject_id, title, description, deadline, created_by))
# 为所有学生创建提交记录
from models.student import StudentModel
students = await StudentModel.get_all(include_disabled=False)
for student in students:
sql_sub = """
INSERT INTO homework_submissions (assignment_id, student_id, status)
VALUES (%s, %s, 'not_submitted')
"""
await execute_insert(sql_sub, (assignment_id, student["student_id"]))
return assignment_id
@staticmethod
async def update_submission(
submission_id: int,
status: str,
comments: str = None,
updated_by: int = None
) -> bool:
sql = """
UPDATE homework_submissions
SET status = %s, comments = %s, updated_by = %s, updated_at = NOW()
WHERE submission_id = %s
"""
result = await execute_update(sql, (status, comments, updated_by, submission_id))
return result > 0
@staticmethod
async def mark_deduction_applied(submission_id: int) -> bool:
sql = "UPDATE homework_submissions SET deduction_applied = 1 WHERE submission_id = %s"
result = await execute_update(sql, (submission_id,))
return result > 0

View File

@@ -53,6 +53,18 @@ class StudentModel:
sql += " ORDER BY student_no"
return await execute_query(sql)
@staticmethod
async def get_dormitory_list() -> List[str]:
"""获取所有不重复的宿舍号列表"""
sql = """
SELECT DISTINCT dormitory_number
FROM students
WHERE status = 1 AND dormitory_number IS NOT NULL AND dormitory_number != ''
ORDER BY dormitory_number
"""
rows = await execute_query(sql)
return [row["dormitory_number"] for row in rows]
@staticmethod
async def create(
student_no: str,

View File

@@ -20,16 +20,15 @@ from middleware.permission import (
)
from services.admin_service import AdminService
from services.conduct_service import ConductService
from services.homework_service import HomeworkService
from services.attendance_service import AttendanceService
from services.log_service import LogService
from utils.redis_client import RedisClient
from schemas.admin import (
AddPointsRequest, RevokeRequest, AddAdminRequest,
AddStudentRequest, UpdateStudentRequest,
UpdateHomeworkStatusRequest, AddAttendanceRequest,
AddAttendanceRequest,
UpdateAdminRequest, DeleteAdminRequest, ResetPasswordRequest,
CreateAssignmentRequest, UnlockUserRequest
UnlockUserRequest
)
from utils.response import success_response, error_response
from utils.logger import get_logger
@@ -41,18 +40,31 @@ logger = get_logger(__name__)
# ========== 学生管理 ==========
@router.get("/students/dormitories")
async def get_dormitory_list(request: Request):
"""获取宿舍号列表"""
user = await get_current_user(request)
if user["user_type"] != "admin":
return error_response(message="仅管理员可查看", code=403)
from models.student import StudentModel
dormitories = await StudentModel.get_dormitory_list()
return success_response(data={"dormitories": dormitories})
@router.get("/students")
async def get_students(
request: Request,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=1000),
search: Optional[str] = None
search: Optional[str] = None,
dormitory_number: Optional[str] = None
):
"""获取所有学生列表(单班级)"""
user = await get_current_user(request)
if user["user_type"] != "admin":
return error_response(message="仅管理员可查看学生列表", code=403)
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, dormitory_number=dormitory_number)
return success_response(data=result)
@@ -139,7 +151,8 @@ async def update_student(request: Request, student_id: int, req: UpdateStudentRe
result = await AdminService.update_student(
student_id=student_id,
name=req.name,
parent_phone=req.parent_phone
parent_phone=req.parent_phone,
dormitory_number=req.dormitory_number
)
if result["success"]:
await LogService.write_operation_log(
@@ -207,6 +220,9 @@ async def reset_student_password(request: Request, student_id: int, req: ResetPa
async def add_conduct_points(request: Request, req: AddPointsRequest):
"""批量加减分"""
user = await get_current_user(request)
# 仅管理员(班主任/班干部)可操作
if user["user_type"] != "admin":
return error_response(message="无权进行此操作", code=403)
result = await ConductService.add_points(
student_ids=req.student_ids,
points_change=req.points_change,
@@ -236,6 +252,9 @@ async def add_conduct_points(request: Request, req: AddPointsRequest):
async def revoke_conduct_record(request: Request, req: RevokeRequest):
"""撤销扣分记录"""
user = await get_current_user(request)
# 仅管理员(班主任/班干部)可操作
if user["user_type"] != "admin":
return error_response(message="无权进行此操作", code=403)
result = await ConductService.revoke_record(
record_id=req.record_id,
revoker_id=user["user_id"]
@@ -264,6 +283,9 @@ async def revoke_conduct_record(request: Request, req: RevokeRequest):
async def restore_conduct_record(request: Request, req: RevokeRequest):
"""反撤销(恢复)已撤销的记录"""
user = await get_current_user(request)
# 仅管理员(班主任/班干部)可操作
if user["user_type"] != "admin":
return error_response(message="无权进行此操作", code=403)
result = await ConductService.restore_record(
record_id=req.record_id,
restorer_id=user["user_id"]
@@ -319,86 +341,6 @@ async def get_conduct_history(
return error_response(message=f"获取历史记录失败: {str(e)}")
# ========== 作业管理 ==========
@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)
@router.post("/homework/assignment")
async def create_assignment(request: Request, req: CreateAssignmentRequest):
"""发布作业(班主任)"""
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=req.subject_id,
title=req.title,
description=req.description,
deadline=req.deadline,
created_by=user["user_id"]
)
if result["success"]:
await LogService.write_operation_log(
operator_id=user["user_id"], operator_name=user["real_name"],
operator_role="班主任", operation_type="create_assignment",
target_type="homework",
details=f"发布作业: {title}",
ip=request.client.host
)
return success_response(data=result, message="作业发布成功")
else:
return error_response(message=result["message"])
@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,
comments=req.comments,
apply_deduction=req.apply_deduction,
operator_id=user["user_id"]
)
if result["success"]:
await LogService.write_operation_log(
operator_id=user["user_id"], operator_name=user["real_name"],
operator_role=role, operation_type="update_submission",
target_type="homework", target_id=req.submission_id,
details=f"状态: {req.status}",
ip=request.client.host
)
return success_response(message="状态更新成功")
else:
return error_response(message=result["message"])
# ========== 考勤管理 ==========

View File

@@ -36,21 +36,6 @@ async def get_child_conduct(request: Request):
return success_response(data=result)
@router.get("/child/homework")
async def get_child_homework(request: Request):
"""
获取子女作业情况
"""
user = await get_current_user(request)
if user["user_type"] != "parent":
return error_response(message="仅限家长访问", code=403)
result = await ParentService.get_child_homework(user["user_id"])
return success_response(data=result)
@router.get("/child/attendance")
async def get_child_attendance(request: Request):
"""

231
backend/routes/upgrade.py Normal file
View File

@@ -0,0 +1,231 @@
# ===========================================
# 班级操行分管理系统 - 升级管理路由
#
# 开发者: Canglan
# 版权归属: Sea Network Technology Studio
#
# 版权所有 © Sea Network Technology Studio
# ===========================================
from fastapi import APIRouter, Request
from utils.database import execute_query, execute_update, get_pool
from utils.response import success_response, error_response
from utils.logger import setup_logger
from middleware.permission import PermissionChecker
import os
import re
logger = setup_logger()
router = APIRouter()
# 版本列表(按顺序)
ALL_VERSIONS = {
'1.7': 'v1.7.sql',
'1.8': 'v1.8.sql',
'2.0': 'v2.0.sql',
'2.0.1': 'v2.0.1.sql',
'2.1': 'v2.1.sql',
'2.2': 'v2.2.sql',
}
@router.get("/check")
async def check_upgrade(request: Request):
"""检查数据库版本是否需要升级"""
# 权限检查:仅班主任可执行升级操作
user_type = getattr(request.state, 'user_type', None)
if user_type != 'admin':
return error_response(message="仅管理员可执行升级操作", code=403)
is_teacher = await PermissionChecker.check_is_teacher(
getattr(request.state, 'user_id', 0)
)
if not is_teacher:
return error_response(message="仅班主任可执行升级操作", code=403)
user_id = request.state.user.get('user_id') if hasattr(request.state, 'user') else getattr(request.state, 'user_id', None)
# 检测当前数据库版本
current_version = '0.0.0'
try:
row = await execute_query(
"SELECT setting_value FROM system_settings WHERE setting_key = 'db_version'"
)
if row:
current_version = row[0]['setting_value']
except Exception:
pass # 表不存在时使用默认值
# 读取目标版本(从 VERSION 文件)
version_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'VERSION')
version_file = os.path.normpath(version_file)
target_version = '0.0.0'
try:
if os.path.exists(version_file):
with open(version_file, 'r') as f:
target_version = f.read().strip()
except Exception:
pass
# 计算需要升级的步骤
needs_upgrade = _compare_versions(target_version, current_version) > 0
steps = []
for version, file_name in sorted(ALL_VERSIONS.items(), key=lambda x: _version_tuple(x[0])):
if _compare_versions(version, current_version) > 0 and _compare_versions(version, target_version) <= 0:
steps.append({'version': version, 'file': file_name})
return success_response(data={
'needs_upgrade': needs_upgrade,
'current': current_version,
'target': target_version,
'steps': steps
})
@router.post("/step")
async def execute_upgrade_step(request: Request):
"""执行单个升级步骤"""
# 权限检查:仅班主任可执行升级操作
user_type = getattr(request.state, 'user_type', None)
if user_type != 'admin':
return error_response(message="仅管理员可执行升级操作", code=403)
is_teacher = await PermissionChecker.check_is_teacher(
getattr(request.state, 'user_id', 0)
)
if not is_teacher:
return error_response(message="仅班主任可执行升级操作", code=403)
user_id = request.state.user.get('user_id') if hasattr(request.state, 'user') else getattr(request.state, 'user_id', None)
body = await request.json()
version = body.get('version', '')
if not version:
return error_response(message='缺少版本号参数', code=400)
if version not in ALL_VERSIONS:
return error_response(message=f'未知版本: {version}', code=400)
# SQL 文件路径
sql_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'sql', 'upgrades')
sql_file = os.path.normpath(os.path.join(sql_dir, ALL_VERSIONS[version]))
if not os.path.exists(sql_file):
return error_response(message=f'SQL 文件不存在: {ALL_VERSIONS[version]}', code=500)
try:
# 读取并执行 SQL
with open(sql_file, 'r', encoding='utf-8') as f:
sql_content = f.read().strip()
if sql_content and sql_content != '--':
# 使用 aiomysql 直接执行多条 SQL
pool = get_pool()
async with pool.acquire() as conn:
async with conn.cursor() as cursor:
# 分割 SQL 语句(按 DELIMITER 处理存储过程)
await _execute_sql_content(cursor, sql_content)
await conn.commit()
# 更新版本号
await execute_update(
"INSERT INTO system_settings (setting_key, setting_value) VALUES ('db_version', %s) "
"ON DUPLICATE KEY UPDATE setting_value = %s",
(version, version)
)
# 重新检测版本
new_version = '0.0.0'
try:
row = await execute_query(
"SELECT setting_value FROM system_settings WHERE setting_key = 'db_version'"
)
if row:
new_version = row[0]['setting_value']
except Exception:
pass
logger.info(f"数据库升级成功: v{version} ({ALL_VERSIONS[version]})")
return success_response(data={
'success': True,
'version': version,
'message': f"升级至 v{version} 成功 ({ALL_VERSIONS[version]})",
'current': new_version
})
except Exception as e:
logger.error(f"数据库升级失败: v{version} - {str(e)}")
return error_response(message=f"升级至 v{version} 失败: {str(e)}", code=500)
def _compare_versions(v1: str, v2: str) -> int:
"""比较两个版本号,返回 1/0/-1"""
t1 = _version_tuple(v1)
t2 = _version_tuple(v2)
if t1 > t2:
return 1
elif t1 < t2:
return -1
return 0
def _version_tuple(v: str) -> tuple:
"""将版本字符串转为可比较的元组"""
parts = []
for p in v.split('.'):
try:
parts.append(int(p))
except ValueError:
parts.append(0)
return tuple(parts)
async def _execute_sql_content(cursor, sql_content: str):
"""执行 SQL 内容,处理存储过程中的 DELIMITER"""
# 如果包含 DELIMITER需要特殊处理
if 'DELIMITER' in sql_content:
# 移除 DELIMITER 行,按 $$ 分割存储过程
lines = sql_content.split('\n')
current_block = []
in_procedure = False
for line in lines:
stripped = line.strip()
if stripped.upper().startswith('DELIMITER $$'):
in_procedure = True
current_block = []
continue
elif stripped.upper() == 'DELIMITER ;':
# 执行累积的存储过程块
if current_block:
proc_sql = '\n'.join(current_block).strip()
if proc_sql:
await cursor.execute(proc_sql)
in_procedure = False
current_block = []
continue
elif stripped.upper().startswith('DELIMITER'):
continue
if in_procedure:
current_block.append(line)
else:
# 普通SQL按分号分割执行
if stripped and not stripped.startswith('--'):
# 简单的按分号分割
for stmt in stripped.split(';'):
stmt = stmt.strip()
if stmt:
await cursor.execute(stmt)
else:
# 无 DELIMITER简单执行
# 按 CREATE 分割以支持多语句
# 分割 SQL 语句
statements = re.split(r';\s*\n', sql_content)
for stmt in statements:
stmt = stmt.strip()
if stmt and stmt != '--':
await cursor.execute(stmt)

View File

@@ -64,14 +64,6 @@ class AddAdminResponse(BaseModel):
message: str
class UpdateHomeworkStatusRequest(BaseModel):
"""更新作业状态请求"""
submission_id: int = Field(..., gt=0, description="提交记录ID")
status: str = Field(..., pattern=r'^(submitted|not_submitted|late|excused)$', description="状态")
comments: Optional[str] = Field(None, max_length=500, description="评语")
apply_deduction: bool = False
class AddStudentRequest(BaseModel):
"""新增学生请求"""
student_no: str = Field(..., min_length=1, max_length=20, pattern=r'^[a-zA-Z0-9]+$', description="学号")
@@ -114,14 +106,6 @@ class UpdateStudentRequest(BaseModel):
dormitory_number: Optional[str] = Field(None, max_length=20, description="宿舍号")
class CreateAssignmentRequest(BaseModel):
"""创建作业请求"""
subject_id: int = Field(..., gt=0, description="科目ID")
title: str = Field(..., min_length=1, max_length=200, description="作业标题")
description: Optional[str] = Field(None, max_length=1000, description="作业描述")
deadline: str = Field(..., min_length=1, max_length=20, description="截止日期")
class UnlockUserRequest(BaseModel):
"""解除用户登录锁定请求"""
username: str = Field(..., min_length=1, max_length=50, description="用户名")

View File

@@ -47,18 +47,6 @@ class ConductHistoryResponse(BaseModel):
records: List[ConductRecord]
class HomeworkSubmission(BaseModel):
"""作业提交情况"""
assignment_id: int
title: str
subject: str
deadline: date
status: str
submit_time: Optional[datetime] = None
comments: Optional[str] = None
deduction_applied: bool
class AttendanceRecord(BaseModel):
"""考勤记录"""
attendance_id: int

View File

@@ -27,14 +27,15 @@ class AdminService:
async def get_students(
page: int = 1,
page_size: int = 20,
search: str = None
search: str = None,
dormitory_number: str = None
) -> Dict[str, Any]:
"""获取所有学生列表"""
offset = (page - 1) * page_size
sql = """
SELECT student_id, student_no, name, total_points, parent_phone, status
FROM students
SELECT student_id, student_no, name, total_points, parent_phone, dormitory_number, status
FROM students
WHERE status = 1
"""
params = []
@@ -43,6 +44,10 @@ class AdminService:
sql += " AND (student_no LIKE %s OR name LIKE %s)"
params.extend([f"%{search}%", f"%{search}%"])
if dormitory_number:
sql += " AND dormitory_number = %s"
params.append(dormitory_number)
sql += " ORDER BY student_no LIMIT %s OFFSET %s"
params.extend([page_size, offset])
@@ -50,12 +55,17 @@ class AdminService:
# 获取总数
count_sql = "SELECT COUNT(*) as total FROM students WHERE status = 1"
count_params = []
if search:
count_sql += " AND (student_no LIKE %s OR name LIKE %s)"
total_result = await execute_one(count_sql, (f"%{search}%", f"%{search}%"))
count_params.extend([f"%{search}%", f"%{search}%"])
if dormitory_number:
count_sql += " AND dormitory_number = %s"
count_params.append(dormitory_number)
if count_params:
total_result = await execute_one(count_sql, tuple(count_params))
else:
total_result = await execute_one(count_sql)
total = total_result["total"] if total_result else 0
return {

View File

@@ -235,6 +235,7 @@ class ConductService:
student_id=student_id,
start_date=start_date,
end_date=end_date,
related_type=related_type,
page=page,
page_size=page_size
)

View File

@@ -1,135 +0,0 @@
# ===========================================
# 班级操行分管理系统 - 后端服务
#
# 开发者: Canglan
# 联系方式: admin@sea-studio.top
# 版权归属: Sea Network Technology Studio
# 许可证: MIT License
#
# 版权所有 © Sea Network Technology Studio
# ===========================================
from typing import Dict, Any, List, Optional
from datetime import datetime
from models.homework import HomeworkModel
from models.student import StudentModel
from models.conduct import ConductModel
from middleware.permission import PermissionChecker
from config import settings
from utils.logger import get_logger
logger = get_logger(__name__)
class HomeworkService:
"""作业服务"""
@staticmethod
async def get_assignments(user_id: int) -> Dict[str, Any]:
"""获取作业列表"""
role = await PermissionChecker.get_user_role(user_id)
if role == "班主任":
assignments = await HomeworkModel.get_all_assignments()
elif role == "学习委员":
subject_ids = await PermissionChecker.get_user_subject_ids(user_id)
assignments = await HomeworkModel.get_assignments_by_subjects(subject_ids)
else:
assignments = []
return {"assignments": assignments}
@staticmethod
async def create_assignment(
subject_id: int,
title: str,
description: Optional[str],
deadline: str,
created_by: int
) -> Dict[str, Any]:
"""创建作业"""
assignment_id = await HomeworkModel.create_assignment(
subject_id=subject_id,
title=title,
description=description,
deadline=deadline,
created_by=created_by
)
if assignment_id:
logger.info(f"用户[{created_by}] 创建作业[{assignment_id}]: {title}")
return {"success": True, "assignment_id": assignment_id}
else:
return {"success": False, "message": "创建作业失败"}
@staticmethod
async def update_submission_status(
submission_id: int,
status: str,
comments: Optional[str],
apply_deduction: bool,
operator_id: int
) -> Dict[str, Any]:
"""更新作业提交状态"""
# 获取提交记录信息
submission = await HomeworkModel.get_submission(submission_id)
if not submission:
return {"success": False, "message": "提交记录不存在"}
# 检查权限
role = await PermissionChecker.get_user_role(operator_id)
if role == "学习委员":
# 检查是否管理该科目
subject_ids = await PermissionChecker.get_user_subject_ids(operator_id)
if submission["subject_id"] not in subject_ids:
return {"success": False, "message": "无权操作此作业"}
elif role != "班主任":
return {"success": False, "message": "无权进行此操作"}
# 更新状态
result = await HomeworkModel.update_submission(
submission_id=submission_id,
status=status,
comments=comments,
updated_by=operator_id
)
if not result:
return {"success": False, "message": "更新失败"}
# 应用扣分
if apply_deduction and status in ["not_submitted", "late"]:
# 确定扣分数值
if status == "not_submitted":
points_change = -settings.DEDUCTION_HOMEWORK_NOT_SUBMIT
else:
points_change = -settings.DEDUCTION_HOMEWORK_LATE
# 创建扣分记录
student = await StudentModel.get_by_id(submission["student_id"])
if student:
# 获取操作人姓名
from models.user import UserModel
user = await UserModel.get_by_user_id(operator_id)
recorder_name = user.get("real_name", "班主任") if user else "班主任"
await ConductModel.create_record(
student_id=submission["student_id"],
points_change=points_change,
reason=f"作业未提交/迟交: {submission['title']}",
recorder_id=operator_id,
recorder_name=recorder_name,
related_type="homework",
related_id=submission["assignment_id"]
)
# 更新学生总分
await StudentModel.update_total_points(submission["student_id"], points_change)
# 标记已应用扣分
await HomeworkModel.mark_deduction_applied(submission_id)
logger.info(f"用户[{operator_id}] 更新作业提交状态[{submission_id}] -> {status}")
return {"success": True, "message": "状态更新成功"}

View File

@@ -15,7 +15,6 @@ from typing import Dict, Any, Optional, List
from models.user import UserModel
from models.student import StudentModel
from models.conduct import ConductModel
from models.homework import HomeworkModel
from models.attendance import AttendanceModel
from utils.logger import get_logger
@@ -46,24 +45,6 @@ class ParentService:
}
@staticmethod
async def get_child_homework(parent_id: int) -> Dict[str, Any]:
"""获取子女作业情况"""
user = await UserModel.get_by_user_id(parent_id)
if not user or not user["student_id"]:
return {"error": "未关联学生"}
student = await StudentModel.get_by_id(user["student_id"])
if not student:
return {"error": "学生不存在"}
homework = await HomeworkModel.get_student_homework(user["student_id"])
return {
"student_id": student["student_id"],
"student_name": student["name"],
"homework": homework
}
@staticmethod
async def get_child_attendance(parent_id: int) -> Dict[str, Any]:
"""获取子女考勤记录"""
user = await UserModel.get_by_user_id(parent_id)

View File

@@ -14,9 +14,9 @@ from datetime import datetime, timedelta
from models.student import StudentModel
from models.conduct import ConductModel
from models.homework import HomeworkModel
from models.attendance import AttendanceModel
from middleware.permission import PermissionChecker
from utils.database import execute_query
from utils.logger import get_logger
logger = get_logger(__name__)
@@ -57,29 +57,33 @@ class StudentService:
@staticmethod
async def get_homework_status(student_id: int) -> Dict[str, Any]:
"""获取学生作业情况"""
"""获取学生作业扣分记录"""
student = await StudentModel.get_by_id(student_id)
if not student:
return {"error": "学生不存在"}
homework = await HomeworkModel.get_student_homework(student_id)
# 查询作业相关的操行分记录
sql = """
SELECT cr.record_id, cr.points_change, cr.reason, cr.created_at,
cr.related_type, cr.recorder_name
FROM conduct_records cr
WHERE cr.student_id = %s AND cr.related_type = 'homework' AND cr.is_revoked = 0
ORDER BY cr.created_at DESC
"""
records = await execute_query(sql, (student_id,))
# 统计
total = len(homework)
submitted = sum(1 for h in homework if h["status"] == "submitted")
not_submitted = sum(1 for h in homework if h["status"] == "not_submitted")
late = sum(1 for h in homework if h["status"] == "late")
total = len(records)
deductions = sum(1 for r in records if r["points_change"] < 0)
return {
"student_id": student_id,
"student_name": student["name"],
"statistics": {
"total": total,
"submitted": submitted,
"not_submitted": not_submitted,
"late": late
"deductions": deductions
},
"homework": homework
"homework": records
}
@staticmethod