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)}")