Files
LoveACE-EndF/router/jwc/__init__.py
2025-08-03 16:50:56 +08:00

606 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import Depends
from fastapi.routing import APIRouter
from provider.aufe.jwc import JWCClient
from provider.aufe.jwc.depends import get_jwc_client
from provider.loveac.authme import AuthmeResponse
from router.jwc.model import (
AcademicInfoResponse,
TrainingPlanInfoResponse,
CourseListResponse,
ExamInfoAPIResponse,
AllTermsResponse,
TermScoreAPIResponse,
FetchTermScoreRequest,
ScheduleResponse,
FetchScheduleRequest,
)
from router.common_model import ErrorResponse
from .evaluate_model import (
EvaluationStatsResponse,
CurrentCourseInfoResponse,
TaskOperationResponse,
InitializeResponse,
CourseInfo,
TaskStatusEnum,
EvaluationStatsData,
CurrentCourseInfoData,
TaskOperationData,
InitializeData,
)
from .evaluate import (
get_task_manager,
remove_task_manager,
)
from datetime import datetime
from loguru import logger
jwc_router = APIRouter(prefix="/api/v1/jwc")
invite_tokens = []
@jwc_router.post(
"/fetch_academic_info",
summary="获取学业信息",
response_model=AcademicInfoResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_academic_info(client: JWCClient = Depends(get_jwc_client)):
"""获取学术信息(课程数量、绩点等)"""
try:
result = await client.fetch_academic_info()
# 检查是否是AuthmeResponse认证错误
if isinstance(result, AuthmeResponse):
return result
# 使用新的错误检测机制
response = AcademicInfoResponse.from_data(
data=result,
success_message="学业信息获取成功",
error_message="获取学业信息失败,网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
)
return response
except Exception as e:
return ErrorResponse(message=f"获取学业信息时发生系统错误:{str(e)}", code=500)
@jwc_router.post(
"/fetch_education_plan_info",
summary="获取培养方案信息",
response_model=TrainingPlanInfoResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_education_plan_info(client: JWCClient = Depends(get_jwc_client)):
"""获取培养方案信息"""
try:
result = await client.fetch_training_plan_info()
# 检查是否是AuthmeResponse认证错误
if isinstance(result, AuthmeResponse):
return result
# 使用新的错误检测机制
response = TrainingPlanInfoResponse.from_data(
data=result,
success_message="培养方案信息获取成功",
error_message="获取培养方案信息失败,网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
)
return response
except Exception as e:
return ErrorResponse(
message=f"获取培养方案信息时发生系统错误:{str(e)}", code=500
)
@jwc_router.post(
"/fetch_evaluation_course_list",
summary="获取评教课程列表",
response_model=CourseListResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_evaluation_course_list(client: JWCClient = Depends(get_jwc_client)):
"""获取评教课程列表"""
try:
result = await client.fetch_evaluation_course_list()
# 检查是否是AuthmeResponse认证错误
if isinstance(result, AuthmeResponse):
return result
# 对于列表类型,使用特殊的检查逻辑
if result and len(result) > 0:
# 检查第一个元素是否是错误数据
first_course = result[0]
if (
hasattr(first_course, "evaluated_people")
and first_course.evaluated_people == "请求失败"
):
return CourseListResponse.error(
message="获取评教课程列表失败,网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
code=500,
data=[],
)
else:
return CourseListResponse.success(
data=result, message="评教课程列表获取成功"
)
else:
return CourseListResponse.success(data=[], message="暂无需要评教的课程")
except Exception as e:
return ErrorResponse(
message=f"获取评教课程列表时发生系统错误:{str(e)}", code=500
)
@jwc_router.post(
"/fetch_exam_info",
summary="获取考试信息",
response_model=ExamInfoAPIResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_exam_info(client: JWCClient = Depends(get_jwc_client)):
"""获取考试信息,包括校统考和其他考试"""
try:
train_plan_info = await client.fetch_training_plan_info()
# 检查培养方案信息是否获取失败
if not train_plan_info or (
hasattr(train_plan_info, "plan_name")
and train_plan_info.plan_name == "请求失败,请稍后重试"
):
return ErrorResponse(
message="无法获取培养方案信息,导致考试信息获取失败。网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
code=500,
)
# 检查是否是AuthmeResponse
if isinstance(train_plan_info, AuthmeResponse):
return train_plan_info
_term_code = train_plan_info.current_term
# _term_code -> term_code: "2024-2025春季学期" 转换为 "2024-2025-2-1" "2024-2025秋季学期" 转换为 "2024-2025-1-1"
# 进行转换
term_code = f"{_term_code[:4]}-{_term_code[5:9]}-{"1" if _term_code[10] == "" else "2"}-1"
print(f"当前学期代码: {term_code}")
start_date = datetime.now()
# termcode 结尾为 1 为秋季学期考试应在3月之前2为春季学期考试应在9月之前
end_date = datetime(
year=start_date.year + (1 if term_code.endswith("1") else 0),
month=3 if term_code.endswith("1") else 9,
day=30,
)
result = await client.fetch_unified_exam_info(
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
term_code=term_code,
)
# 检查是否是AuthmeResponse认证错误
if isinstance(result, AuthmeResponse):
return result
# 使用新的错误检测机制
response = ExamInfoAPIResponse.from_data(
data=result,
success_message="考试信息获取成功",
error_message="获取考试信息失败,网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
)
return response
except Exception as e:
return ErrorResponse(message=f"获取考试信息时发生系统错误:{str(e)}", code=500)
# ==================== 评价系统API ====================
@jwc_router.post(
"/evaluation/initialize",
summary="初始化评价任务",
response_model=InitializeResponse | AuthmeResponse,
)
async def initialize_evaluation_task(client: JWCClient = Depends(get_jwc_client)):
"""初始化评价任务,获取课程列表"""
try:
# 获取用户ID (从JWC客户端获取)
user_id = getattr(client, "user_id", "unknown")
# 检查是否已有活跃的任务管理器
existing_manager = get_task_manager(user_id)
if existing_manager:
current_status = existing_manager.get_task_status().status
if current_status in [
TaskStatusEnum.RUNNING,
TaskStatusEnum.PAUSED,
TaskStatusEnum.INITIALIZING,
]:
return InitializeResponse(
code=400,
message="您已有一个评价任务在进行中,请先完成或终止当前任务",
data=None,
)
# 如果任务已完成、失败或终止,移除旧的任务管理器
elif current_status in [
TaskStatusEnum.COMPLETED,
TaskStatusEnum.FAILED,
TaskStatusEnum.TERMINATED,
]:
remove_task_manager(user_id)
# 获取或创建任务管理器
task_manager = get_task_manager(user_id, client)
if not task_manager:
return InitializeResponse(code=400, message="创建任务管理器失败", data=None)
# 执行初始化
success = await task_manager.initialize()
stats = task_manager.get_task_status()
# 转换课程列表格式
course_list = []
for course in stats.course_list:
course_info = CourseInfo(
course_id=(
getattr(course.id, "coure_sequence_number", "") if course.id else ""
),
course_name=course.evaluation_content,
teacher_name=course.evaluated_people,
is_evaluated=course.is_evaluated,
evaluation_content=course.evaluation_content,
)
course_list.append(course_info)
initialize_data = InitializeData(
total_courses=stats.total_courses,
pending_courses=stats.pending_courses,
course_list=course_list,
)
return InitializeResponse(
code=200 if success else 400, message=stats.message, data=initialize_data
)
except Exception as e:
return InitializeResponse(code=500, message=f"初始化失败: {str(e)}", data=None)
@jwc_router.post(
"/evaluation/start",
summary="开始评价任务",
response_model=TaskOperationResponse | AuthmeResponse,
)
async def start_evaluation_task(client: JWCClient = Depends(get_jwc_client)):
"""开始评价任务"""
try:
user_id = getattr(client, "user_id", "unknown")
# 检查是否已有运行中的任务
existing_manager = get_task_manager(user_id)
if existing_manager:
current_status = existing_manager.get_task_status().status
if current_status.value in [
TaskStatusEnum.RUNNING.value,
TaskStatusEnum.PAUSED.value,
]:
task_data = TaskOperationData(
task_status=TaskStatusEnum(current_status.value)
)
return TaskOperationResponse(
code=400,
message="您已有一个评价任务在运行中,请先完成或终止当前任务",
data=task_data,
)
task_manager = get_task_manager(user_id, client)
if not task_manager:
task_data = TaskOperationData(task_status=TaskStatusEnum.FAILED)
return TaskOperationResponse(
code=400, message="任务管理器不存在,请先初始化", data=task_data
)
success = await task_manager.start_evaluation_task()
stats = task_manager.get_task_status()
task_data = TaskOperationData(task_status=TaskStatusEnum(stats.status.value))
return TaskOperationResponse(
code=200 if success else 400,
message="任务已启动" if success else "任务启动失败,可能已有任务在运行",
data=task_data,
)
except Exception as e:
task_data = TaskOperationData(task_status=TaskStatusEnum.FAILED)
return TaskOperationResponse(
code=500, message=f"启动任务失败: {str(e)}", data=task_data
)
@jwc_router.post(
"/evaluation/terminate",
summary="终止评价任务",
response_model=TaskOperationResponse | AuthmeResponse,
)
async def terminate_evaluation_task(client: JWCClient = Depends(get_jwc_client)):
"""终止评价任务"""
try:
user_id = getattr(client, "user_id", "unknown")
task_manager = get_task_manager(user_id)
if not task_manager:
task_data = TaskOperationData(task_status=TaskStatusEnum.IDLE)
return TaskOperationResponse(
code=400, message="任务管理器不存在", data=task_data
)
success = await task_manager.terminate_task()
stats = task_manager.get_task_status()
# 移除任务管理器
remove_task_manager(user_id)
task_data = TaskOperationData(task_status=TaskStatusEnum(stats.status.value))
return TaskOperationResponse(
code=200 if success else 400,
message="任务已终止" if success else "终止失败",
data=task_data,
)
except Exception as e:
task_data = TaskOperationData(task_status=TaskStatusEnum.FAILED)
return TaskOperationResponse(
code=500, message=f"终止任务失败: {str(e)}", data=task_data
)
@jwc_router.post(
"/evaluation/status",
summary="获取评价任务状态",
response_model=EvaluationStatsResponse | AuthmeResponse,
)
async def get_evaluation_task_status(client: JWCClient = Depends(get_jwc_client)):
"""获取评价任务状态"""
try:
user_id = getattr(client, "user_id", "unknown")
task_manager = get_task_manager(user_id)
if not task_manager:
return EvaluationStatsResponse(code=200, message="无活跃任务", data=None)
stats = task_manager.get_task_status()
# 转换课程列表格式
course_list = []
for course in stats.course_list:
course_info = CourseInfo(
course_id=(
getattr(course.id, "coure_sequence_number", "") if course.id else ""
),
course_name=course.evaluation_content,
teacher_name=course.evaluated_people,
is_evaluated=course.is_evaluated,
evaluation_content=course.evaluation_content,
)
course_list.append(course_info)
stats_data = EvaluationStatsData(
total_courses=stats.total_courses,
pending_courses=stats.pending_courses,
success_count=stats.success_count,
fail_count=stats.fail_count,
current_index=stats.current_index,
status=TaskStatusEnum(stats.status.value),
current_countdown=stats.current_countdown,
start_time=stats.start_time,
end_time=stats.end_time,
error_message=stats.error_message,
course_list=course_list,
)
return EvaluationStatsResponse(code=200, message=stats.message, data=stats_data)
except Exception as e:
return EvaluationStatsResponse(
code=500, message=f"获取状态失败: {str(e)}", data=None
)
@jwc_router.post(
"/evaluation/current",
summary="获取当前评价课程信息",
response_model=CurrentCourseInfoResponse | AuthmeResponse,
)
async def get_current_course_info(client: JWCClient = Depends(get_jwc_client)):
"""获取当前评价课程信息"""
try:
user_id = getattr(client, "user_id", "unknown")
task_manager = get_task_manager(user_id)
if not task_manager:
return CurrentCourseInfoResponse(code=200, message="无活跃任务", data=None)
current_info = task_manager.get_current_course_info()
course_info_data = CurrentCourseInfoData(
is_evaluating=current_info.is_evaluating,
course_name=current_info.course_name,
teacher_name=current_info.teacher_name,
progress_text=current_info.progress_text,
countdown_seconds=current_info.countdown_seconds,
current_index=current_info.current_index,
total_pending=current_info.total_pending,
)
return CurrentCourseInfoResponse(
code=200, message="获取成功", data=course_info_data
)
except Exception as e:
return CurrentCourseInfoResponse(
code=500, message=f"获取信息失败: {str(e)}", data=None
)
# ==================== 学期和成绩相关API ====================
@jwc_router.post(
"/fetch_all_terms",
summary="获取所有学期信息",
response_model=AllTermsResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_all_terms(client: JWCClient = Depends(get_jwc_client)):
"""获取所有可查询的学期信息"""
try:
result = await client.fetch_all_terms()
# 检查结果
if result and len(result) > 0:
return AllTermsResponse.success(data=result, message="学期信息获取成功")
else:
return AllTermsResponse.error(
message="获取学期信息失败,网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
code=500,
data={},
)
except Exception as e:
return ErrorResponse(message=f"获取学期信息时发生系统错误:{str(e)}", code=500)
@jwc_router.post(
"/fetch_term_score",
summary="获取指定学期成绩",
response_model=TermScoreAPIResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_term_score(
request: FetchTermScoreRequest,
client: JWCClient = Depends(get_jwc_client),
):
"""
获取指定学期的成绩信息
"""
try:
raw_result = await client.fetch_term_score(
term_id=request.term_id,
course_code=request.course_code,
course_name=request.course_name,
page_num=request.page_num,
page_size=request.page_size,
)
if not raw_result:
return TermScoreAPIResponse.error(
message="获取成绩信息失败,网络请求多次重试后仍无法连接教务系统,请稍后重试或联系管理员",
code=500,
data=None,
)
try:
# 解析原始数据为结构化数据
from provider.aufe.jwc.model import TermScoreResponse, ScoreRecord
list_data = raw_result.get("list", {})
page_context = list_data.get("pageContext", {})
records_raw = list_data.get("records", [])
# 转换记录格式
score_records = []
for record in records_raw:
if len(record) >= 13: # 确保数据完整
score_record = ScoreRecord(
sequence=record[0] if record[0] else 0,
term_id=record[1] if record[1] else "",
course_code=record[2] if record[2] else "",
course_class=record[3] if record[3] else "",
course_name_cn=record[4] if record[4] else "",
course_name_en=record[5] if record[5] else "",
credits=record[6] if record[6] else "",
hours=record[7] if record[7] else 0,
course_type=record[8] if record[8] else "",
exam_type=record[9] if record[9] else "",
score=record[10] if record[10] else "",
retake_score=(
record[11] if len(record) > 11 and record[11] else None
),
makeup_score=(
record[12] if len(record) > 12 and record[12] else None
),
)
score_records.append(score_record)
result = TermScoreResponse(
page_size=list_data.get("pageSize", 50),
page_num=list_data.get("pageNum", 1),
total_count=page_context.get("totalCount", 0),
records=score_records,
)
return TermScoreAPIResponse(
code=200,
message="success",
data=result,
)
except Exception as parse_error:
return TermScoreAPIResponse.error(
message=f"解析成绩数据失败:{str(parse_error)}", code=500, data=None
)
except Exception as e:
logger.error(f"获取学期成绩失败: {str(e)}")
return ErrorResponse(code=1, message=f"获取学期成绩失败: {str(e)}")
@jwc_router.post(
"/fetch_course_schedule",
summary="获取课表信息",
response_model=ScheduleResponse | AuthmeResponse | ErrorResponse,
)
async def fetch_course_schedule(
request: FetchScheduleRequest,
client: JWCClient = Depends(get_jwc_client)
):
"""
获取聚合的课表信息,包含:
- 课程基本信息(课程名、教师、学分等)
- 上课时间和地点信息
- 时间段详情
- 学期信息
特殊处理:
- 自动过滤无用字段
- 标记没有具体时间安排的课程
- 清理教师姓名中的特殊字符
"""
try:
logger.info(f"获取课表请求: plan_code={request.plan_code}")
# 检查环境和Cookie有效性
is_valid = await client.validate_environment_and_cookie()
if not is_valid:
return AuthmeResponse(
code=401,
message="Cookie已失效或不在VPN/校园网环境,请重新登录",
)
# 获取处理后的课表数据
schedule_data = await client.get_processed_schedule(request.plan_code)
if not schedule_data:
return ErrorResponse(
code=1,
message="获取课表信息失败,请稍后重试"
)
return ScheduleResponse(
code=0,
message="success",
data=schedule_data,
)
except Exception as e:
logger.error(f"获取课表信息失败: {str(e)}")
return ErrorResponse(code=1, message=f"获取课表信息失败: {str(e)}")