671 lines
23 KiB
Python
671 lines
23 KiB
Python
from provider.aufe.jwc import JWCClient
|
||
from provider.aufe.jwc.model import Course, EvaluationRequestParam
|
||
|
||
import asyncio
|
||
import random
|
||
from datetime import datetime
|
||
from typing import Dict, List, Optional, Callable
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum
|
||
from loguru import logger
|
||
|
||
|
||
class TaskStatus(Enum):
|
||
"""任务状态枚举"""
|
||
|
||
IDLE = "idle" # 空闲
|
||
INITIALIZING = "initializing" # 初始化中
|
||
RUNNING = "running" # 运行中
|
||
PAUSED = "paused" # 暂停
|
||
COMPLETED = "completed" # 完成
|
||
FAILED = "failed" # 失败
|
||
TERMINATED = "terminated" # 已终止
|
||
|
||
|
||
@dataclass
|
||
class EvaluationStats:
|
||
"""评价统计信息"""
|
||
|
||
total_courses: int = 0
|
||
pending_courses: int = 0
|
||
success_count: int = 0
|
||
fail_count: int = 0
|
||
current_index: int = 0
|
||
status: TaskStatus = TaskStatus.IDLE
|
||
message: str = ""
|
||
course_list: List[Course] = field(default_factory=list)
|
||
current_countdown: int = 0
|
||
current_course: Optional[Course] = None
|
||
start_time: Optional[datetime] = None
|
||
end_time: Optional[datetime] = None
|
||
error_message: str = ""
|
||
|
||
|
||
@dataclass
|
||
class CurrentCourseInfo:
|
||
"""当前评价课程信息"""
|
||
|
||
is_evaluating: bool = False
|
||
course_name: str = ""
|
||
teacher_name: str = ""
|
||
progress_text: str = ""
|
||
countdown_seconds: int = 0
|
||
current_index: int = -1
|
||
total_pending: int = 0
|
||
|
||
|
||
class Constants:
|
||
"""常量定义"""
|
||
|
||
# 等待评价的冷却时间(秒)
|
||
COUNTDOWN_SECONDS = 140 # 2分20秒
|
||
|
||
# 随机评价文案 - 总体评价文案
|
||
ZGPGS = [
|
||
"老师授课生动形象,课堂氛围活跃。",
|
||
"教学方法新颖,能够激发学习兴趣。",
|
||
"讲解耐心细致,知识点清晰易懂。",
|
||
"对待学生公平公正,很有亲和力。",
|
||
"课堂管理有序,效率高。",
|
||
"能理论联系实际,深入浅出。",
|
||
"作业布置合理,有助于巩固知识。",
|
||
"教学经验丰富,讲解深入浅出。",
|
||
"关注学生反馈,及时调整教学。",
|
||
"教学资源丰富,便于学习。",
|
||
"课堂互动性强,能充分调动积极性。",
|
||
"教学重点突出,难点突破到位。",
|
||
"性格开朗,课堂充满活力。",
|
||
"批改作业认真,评语有指导性。",
|
||
"教学目标明确,条理清晰。",
|
||
]
|
||
|
||
# 额外描述性文案
|
||
NICE_0000000200 = [
|
||
"常把晦涩理论生活化,知识瞬间亲近起来。",
|
||
"总用类比解难点,复杂概念秒懂。",
|
||
"引入行业前沿案例,打开视野新窗口。",
|
||
"设问巧妙引深思,激发自主探寻答案。",
|
||
"常分享学科冷知识,拓宽知识边界。",
|
||
"用跨学科视角解题,思维更灵动。",
|
||
"鼓励尝试多元解法,创新思维被激活。",
|
||
"常分享科研趣事,点燃学术热情。",
|
||
"用思维导图梳理知识,结构一目了然。",
|
||
"常把学习方法倾囊相授,效率直线提升。",
|
||
"用历史事件类比,知识记忆更深刻。",
|
||
"常鼓励跨学科学习,综合素养渐涨。",
|
||
"分享行业大咖故事,奋斗动力满满。",
|
||
"总能挖掘知识背后的趣味,学习味十足。",
|
||
"常组织知识竞赛,学习热情被点燃。",
|
||
]
|
||
|
||
# 建议文案
|
||
NICE_0000000201 = [
|
||
"无",
|
||
"没有",
|
||
"没有什么建议,老师很好",
|
||
"继续保持这么好的教学风格",
|
||
"希望老师继续分享更多精彩案例",
|
||
"感谢老师的悉心指导",
|
||
]
|
||
|
||
|
||
class EvaluationTaskManager:
|
||
"""评价任务管理器 - 基于学号管理"""
|
||
|
||
def __init__(self, jwc_client: JWCClient, user_id: str):
|
||
"""
|
||
初始化评价任务管理器
|
||
|
||
Args:
|
||
jwc_client: JWC客户端实例
|
||
user_id: 用户学号
|
||
"""
|
||
self.jwc_client = jwc_client
|
||
self.user_id = user_id
|
||
self.stats = EvaluationStats()
|
||
self._task: Optional[asyncio.Task] = None
|
||
self._stop_event = asyncio.Event()
|
||
self._progress_callbacks: List[Callable[[EvaluationStats], None]] = []
|
||
|
||
logger.info(f"初始化评价任务管理器,用户ID: {user_id}")
|
||
|
||
def add_progress_callback(self, callback: Callable[[EvaluationStats], None]):
|
||
"""添加进度回调函数"""
|
||
self._progress_callbacks.append(callback)
|
||
|
||
def _notify_progress(self):
|
||
"""通知所有进度回调"""
|
||
for callback in self._progress_callbacks:
|
||
try:
|
||
callback(self.stats)
|
||
except Exception as e:
|
||
logger.error(f"进度回调执行失败: {str(e)}")
|
||
|
||
async def initialize(self) -> bool:
|
||
"""
|
||
初始化评价环境
|
||
|
||
Returns:
|
||
bool: 初始化是否成功
|
||
"""
|
||
try:
|
||
self.stats.status = TaskStatus.INITIALIZING
|
||
self.stats.message = "正在检查网络..."
|
||
self._notify_progress()
|
||
|
||
# 检查网络连接
|
||
if not await self.jwc_client.check_network_connection():
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.message = "网络连接失败,请确保连接到校园网或VPN"
|
||
self.stats.error_message = "网络连接失败"
|
||
self._notify_progress()
|
||
return False
|
||
|
||
# 验证环境和Cookie
|
||
self.stats.message = "正在验证登录状态..."
|
||
self._notify_progress()
|
||
|
||
if not await self.jwc_client.validate_environment_and_cookie():
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.message = "登录状态失效,请重新登录"
|
||
self.stats.error_message = "Cookie验证失败"
|
||
self._notify_progress()
|
||
return False
|
||
|
||
# 获取Token
|
||
self.stats.message = "正在获取Token..."
|
||
self._notify_progress()
|
||
|
||
token = await self.jwc_client.get_token()
|
||
if not token:
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.message = "获取Token失败,可能是评教系统未开放"
|
||
self.stats.error_message = "Token获取失败"
|
||
self._notify_progress()
|
||
return False
|
||
|
||
# 获取课程列表
|
||
self.stats.message = "正在获取课程列表..."
|
||
self._notify_progress()
|
||
|
||
courses = await self.jwc_client.fetch_evaluation_course_list()
|
||
if not courses:
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.message = "未获取到课程列表,请稍后再试"
|
||
self.stats.error_message = "课程列表获取失败"
|
||
self._notify_progress()
|
||
return False
|
||
|
||
# 更新统计信息
|
||
pending_courses = [
|
||
course
|
||
for course in courses
|
||
if getattr(course, "is_evaluated", "否") != "是"
|
||
]
|
||
self.stats.course_list = courses
|
||
self.stats.total_courses = len(courses)
|
||
self.stats.pending_courses = len(pending_courses)
|
||
self.stats.status = TaskStatus.IDLE
|
||
self.stats.message = (
|
||
f"初始化完成,找到 {self.stats.pending_courses} 门待评价课程"
|
||
)
|
||
self.stats.current_course = None
|
||
|
||
logger.info(
|
||
f"用户 {self.user_id} 初始化完成,待评价课程: {self.stats.pending_courses}"
|
||
)
|
||
self._notify_progress()
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.message = f"初始化异常: {str(e)}"
|
||
self.stats.error_message = str(e)
|
||
logger.error(f"用户 {self.user_id} 初始化失败: {str(e)}")
|
||
self._notify_progress()
|
||
return False
|
||
|
||
async def evaluate_course(self, course: Course, token: str) -> bool:
|
||
"""
|
||
评价单门课程
|
||
|
||
Args:
|
||
course: 课程信息
|
||
token: CSRF Token
|
||
|
||
Returns:
|
||
bool: 评价是否成功
|
||
"""
|
||
try:
|
||
# 设置当前课程
|
||
self.stats.current_course = course
|
||
|
||
# 如果课程已评价,则跳过
|
||
if getattr(course, "is_evaluated", "否") == "是":
|
||
logger.info(f"课程已评价,跳过: {course.evaluation_content}")
|
||
return True
|
||
|
||
# 第一步:访问评价页面
|
||
if not await self.jwc_client.access_evaluation_page(token, course):
|
||
return False
|
||
|
||
course_name = course.evaluation_content
|
||
logger.info(f"正在准备评价: {course_name}")
|
||
|
||
self.stats.message = "已访问评价页面,等待服务器倒计时完成后提交评价..."
|
||
self._notify_progress()
|
||
|
||
# 等待服务器倒计时
|
||
server_wait_time = Constants.COUNTDOWN_SECONDS
|
||
|
||
# 显示倒计时
|
||
for second in range(server_wait_time, 0, -1):
|
||
# 检查是否被终止
|
||
if self._stop_event.is_set():
|
||
self.stats.status = TaskStatus.TERMINATED
|
||
self.stats.message = "任务已被终止"
|
||
self._notify_progress()
|
||
return False
|
||
|
||
self.stats.current_countdown = second
|
||
self.stats.message = f"服务器倒计时: {second} 秒,然后提交评价..."
|
||
self._notify_progress()
|
||
|
||
await asyncio.sleep(1)
|
||
|
||
self.stats.current_countdown = 0
|
||
self.stats.message = "倒计时结束,正在提交评价..."
|
||
self._notify_progress()
|
||
|
||
# 生成评价数据
|
||
evaluation_ratings = {}
|
||
for i in range(180, 202):
|
||
key = f"0000000{i}"
|
||
if i == 200:
|
||
evaluation_ratings[key] = random.choice(Constants.NICE_0000000200)
|
||
elif i == 201:
|
||
evaluation_ratings[key] = random.choice(Constants.NICE_0000000201)
|
||
else:
|
||
evaluation_ratings[key] = f"5_{random.choice(['0.8', '1'])}"
|
||
|
||
# 创建评价请求参数
|
||
evaluation_param = EvaluationRequestParam(
|
||
token_value=token,
|
||
questionnaire_code=(
|
||
course.questionnaire.questionnaire_number
|
||
if course.questionnaire
|
||
else ""
|
||
),
|
||
evaluation_content=(
|
||
course.id.evaluation_content_number if course.id else ""
|
||
),
|
||
evaluated_people_number=course.id.evaluated_people if course.id else "",
|
||
zgpj=random.choice(Constants.ZGPGS),
|
||
rating_items=evaluation_ratings,
|
||
)
|
||
|
||
# 提交评价
|
||
response = await self.jwc_client.submit_evaluation(evaluation_param)
|
||
success = response.result == "success"
|
||
|
||
if success:
|
||
logger.info(f"课程评价成功: {course_name}")
|
||
else:
|
||
logger.error(f"课程评价失败: {course_name}, 错误: {response.msg}")
|
||
|
||
# 清除当前课程信息
|
||
self.stats.current_course = None
|
||
self.stats.current_countdown = 0
|
||
|
||
return success
|
||
|
||
except Exception as e:
|
||
logger.error(f"评价课程异常: {str(e)}")
|
||
return False
|
||
|
||
async def start_evaluation_task(self) -> bool:
|
||
"""
|
||
开始评价任务
|
||
确保一个用户只能有一个运行中的任务
|
||
|
||
Returns:
|
||
bool: 任务是否成功启动
|
||
"""
|
||
# 检查当前状态
|
||
if self.stats.status == TaskStatus.RUNNING:
|
||
logger.warning(f"用户 {self.user_id} 的评价任务已在运行中")
|
||
return False
|
||
|
||
if self.stats.status == TaskStatus.INITIALIZING:
|
||
logger.warning(f"用户 {self.user_id} 的评价任务正在初始化中")
|
||
return False
|
||
|
||
# 检查是否有未完成的异步任务
|
||
if self._task and not self._task.done():
|
||
logger.warning(f"用户 {self.user_id} 已有任务在执行")
|
||
return False
|
||
|
||
# 确保任务已经初始化
|
||
if self.stats.status == TaskStatus.IDLE and len(self.stats.course_list) == 0:
|
||
logger.warning(f"用户 {self.user_id} 任务未初始化,请先调用initialize")
|
||
return False
|
||
|
||
# 重置停止事件
|
||
self._stop_event.clear()
|
||
|
||
# 创建新任务
|
||
self._task = asyncio.create_task(self._evaluate_all_courses())
|
||
|
||
logger.info(f"用户 {self.user_id} 开始评价任务")
|
||
return True
|
||
|
||
async def _evaluate_all_courses(self):
|
||
"""批量评价所有课程(内部方法)"""
|
||
try:
|
||
# 获取Token
|
||
token = await self.jwc_client.get_token()
|
||
if not token:
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.message = "获取Token失败"
|
||
self._notify_progress()
|
||
return
|
||
|
||
# 获取待评价课程
|
||
pending_courses = [
|
||
course
|
||
for course in self.stats.course_list
|
||
if getattr(course, "is_evaluated", "否") != "是"
|
||
]
|
||
|
||
if not pending_courses:
|
||
self.stats.status = TaskStatus.COMPLETED
|
||
self.stats.message = "所有课程已评价完成!"
|
||
self._notify_progress()
|
||
return
|
||
|
||
# 开始评价流程
|
||
self.stats.status = TaskStatus.RUNNING
|
||
self.stats.success_count = 0
|
||
self.stats.fail_count = 0
|
||
self.stats.current_course = None
|
||
self.stats.start_time = datetime.now()
|
||
|
||
index = 0
|
||
while index < len(pending_courses):
|
||
# 检查是否被终止
|
||
if self._stop_event.is_set():
|
||
self.stats.status = TaskStatus.TERMINATED
|
||
self.stats.message = "任务已被终止"
|
||
self.stats.end_time = datetime.now()
|
||
self._notify_progress()
|
||
return
|
||
|
||
course = pending_courses[index]
|
||
self.stats.current_index = index
|
||
self.stats.current_course = course
|
||
|
||
course_name = getattr(
|
||
course.questionnaire,
|
||
"questionnaire_name",
|
||
course.evaluation_content,
|
||
)
|
||
self.stats.message = f"正在处理第 {index + 1}/{len(pending_courses)} 门课程: {course_name}"
|
||
self._notify_progress()
|
||
|
||
# 评价当前课程
|
||
success = await self.evaluate_course(course, token)
|
||
|
||
if success:
|
||
self.stats.success_count += 1
|
||
self.stats.message = f"课程评价成功: {course_name}"
|
||
else:
|
||
self.stats.fail_count += 1
|
||
self.stats.message = f"课程评价失败: {course_name}"
|
||
|
||
self._notify_progress()
|
||
|
||
# 评价完一门课程后,重新获取课程列表
|
||
self.stats.message = "正在更新课程列表..."
|
||
self._notify_progress()
|
||
|
||
# 重新获取课程列表
|
||
updated_courses = await self.jwc_client.fetch_evaluation_course_list()
|
||
if updated_courses:
|
||
self.stats.course_list = updated_courses
|
||
pending_courses = [
|
||
course
|
||
for course in updated_courses
|
||
if getattr(course, "is_evaluated", "否") != "是"
|
||
]
|
||
self.stats.total_courses = len(updated_courses)
|
||
self.stats.pending_courses = len(pending_courses)
|
||
self.stats.message = (
|
||
f"课程列表已更新,剩余待评价课程: {self.stats.pending_courses}"
|
||
)
|
||
self._notify_progress()
|
||
|
||
# 给服务器一些处理时间
|
||
if pending_courses and index < len(pending_courses) - 1:
|
||
self.stats.message = "准备处理下一门课程..."
|
||
self._notify_progress()
|
||
await asyncio.sleep(3)
|
||
|
||
index += 1
|
||
|
||
# 评价完成
|
||
self.stats.status = TaskStatus.COMPLETED
|
||
self.stats.current_course = None
|
||
self.stats.end_time = datetime.now()
|
||
self.stats.message = f"评价完成!成功: {self.stats.success_count},失败: {self.stats.fail_count}"
|
||
|
||
logger.info(
|
||
f"用户 {self.user_id} 评价任务完成,成功: {self.stats.success_count},失败: {self.stats.fail_count}"
|
||
)
|
||
self._notify_progress()
|
||
|
||
except Exception as e:
|
||
self.stats.status = TaskStatus.FAILED
|
||
self.stats.error_message = str(e)
|
||
self.stats.message = f"评价任务异常: {str(e)}"
|
||
self.stats.end_time = datetime.now()
|
||
logger.error(f"用户 {self.user_id} 评价任务异常: {str(e)}")
|
||
self._notify_progress()
|
||
|
||
async def pause_task(self) -> bool:
|
||
"""
|
||
暂停任务
|
||
|
||
Returns:
|
||
bool: 是否成功暂停
|
||
"""
|
||
if self.stats.status != TaskStatus.RUNNING:
|
||
return False
|
||
|
||
self.stats.status = TaskStatus.PAUSED
|
||
self.stats.message = "任务已暂停"
|
||
logger.info(f"用户 {self.user_id} 任务已暂停")
|
||
self._notify_progress()
|
||
return True
|
||
|
||
async def resume_task(self) -> bool:
|
||
"""
|
||
恢复任务
|
||
|
||
Returns:
|
||
bool: 是否成功恢复
|
||
"""
|
||
if self.stats.status != TaskStatus.PAUSED:
|
||
return False
|
||
|
||
self.stats.status = TaskStatus.RUNNING
|
||
self.stats.message = "任务已恢复"
|
||
logger.info(f"用户 {self.user_id} 任务已恢复")
|
||
self._notify_progress()
|
||
return True
|
||
|
||
async def terminate_task(self) -> bool:
|
||
"""
|
||
终止任务
|
||
|
||
Returns:
|
||
bool: 是否成功终止
|
||
"""
|
||
if self.stats.status not in [TaskStatus.RUNNING, TaskStatus.PAUSED]:
|
||
return False
|
||
|
||
# 设置停止事件
|
||
self._stop_event.set()
|
||
|
||
# 如果有运行中的任务,等待其完成
|
||
if self._task and not self._task.done():
|
||
try:
|
||
await asyncio.wait_for(self._task, timeout=5.0)
|
||
except asyncio.TimeoutError:
|
||
self._task.cancel()
|
||
try:
|
||
await self._task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
|
||
self.stats.status = TaskStatus.TERMINATED
|
||
self.stats.message = "任务已终止"
|
||
self.stats.end_time = datetime.now()
|
||
logger.info(f"用户 {self.user_id} 任务已终止")
|
||
self._notify_progress()
|
||
return True
|
||
|
||
def get_current_course_info(self) -> CurrentCourseInfo:
|
||
"""
|
||
获取当前评价课程信息
|
||
|
||
Returns:
|
||
CurrentCourseInfo: 当前课程信息
|
||
"""
|
||
# 如果没有运行评价任务
|
||
if self.stats.status != TaskStatus.RUNNING:
|
||
return CurrentCourseInfo(
|
||
is_evaluating=False, progress_text="当前无评价任务"
|
||
)
|
||
|
||
# 正在评价但还没有确定是哪门课程
|
||
if (
|
||
self.stats.current_index < 0
|
||
or self.stats.current_index >= len(self.stats.course_list)
|
||
or self.stats.current_course is None
|
||
):
|
||
return CurrentCourseInfo(
|
||
is_evaluating=True,
|
||
progress_text="准备中...",
|
||
total_pending=self.stats.pending_courses,
|
||
)
|
||
|
||
# 正在评价特定课程
|
||
course = self.stats.current_course
|
||
pending_courses = [
|
||
c
|
||
for c in self.stats.course_list
|
||
if getattr(c, "is_evaluated", "否") != "是"
|
||
]
|
||
index = self.stats.current_index + 1
|
||
total = len(pending_courses)
|
||
|
||
countdown_text = (
|
||
f" (倒计时: {self.stats.current_countdown}秒)"
|
||
if self.stats.current_countdown > 0
|
||
else ""
|
||
)
|
||
|
||
course_name = course.evaluation_content[:20]
|
||
if len(course.evaluation_content) > 20:
|
||
course_name += "..."
|
||
|
||
return CurrentCourseInfo(
|
||
is_evaluating=True,
|
||
course_name=course_name,
|
||
teacher_name=course.evaluated_people,
|
||
progress_text=f"正在评价({index}/{total}): {course_name} - {course.evaluated_people}{countdown_text}",
|
||
countdown_seconds=self.stats.current_countdown,
|
||
current_index=self.stats.current_index,
|
||
total_pending=total,
|
||
)
|
||
|
||
def get_task_status(self) -> EvaluationStats:
|
||
"""
|
||
获取任务状态
|
||
|
||
Returns:
|
||
EvaluationStats: 任务统计信息
|
||
"""
|
||
return self.stats
|
||
|
||
def get_user_id(self) -> str:
|
||
"""获取用户ID"""
|
||
return self.user_id
|
||
|
||
|
||
# 全局任务管理器字典,以学号为键
|
||
_task_managers: Dict[str, EvaluationTaskManager] = {}
|
||
|
||
|
||
def get_task_manager(
|
||
user_id: str, jwc_client: Optional[JWCClient] = None
|
||
) -> Optional[EvaluationTaskManager]:
|
||
"""
|
||
获取或创建任务管理器
|
||
一个用户只能有一个活跃的任务管理器
|
||
|
||
Args:
|
||
user_id: 用户学号
|
||
jwc_client: JWC客户端(创建新管理器时需要)
|
||
|
||
Returns:
|
||
Optional[EvaluationTaskManager]: 任务管理器实例
|
||
"""
|
||
if user_id in _task_managers:
|
||
existing_manager = _task_managers[user_id]
|
||
# 检查现有任务的状态
|
||
current_status = existing_manager.get_task_status().status
|
||
|
||
# 如果任务已完成、失败或终止,自动清理
|
||
if current_status in [
|
||
TaskStatus.COMPLETED,
|
||
TaskStatus.FAILED,
|
||
TaskStatus.TERMINATED,
|
||
]:
|
||
logger.info(f"自动清理用户 {user_id} 的已完成任务")
|
||
del _task_managers[user_id]
|
||
else:
|
||
# 返回现有的管理器
|
||
return existing_manager
|
||
|
||
# 创建新的管理器
|
||
if jwc_client is None:
|
||
return None
|
||
|
||
manager = EvaluationTaskManager(jwc_client, user_id)
|
||
_task_managers[user_id] = manager
|
||
logger.info(f"为用户 {user_id} 创建新的任务管理器")
|
||
return manager
|
||
|
||
|
||
def remove_task_manager(user_id: str) -> bool:
|
||
"""
|
||
移除任务管理器
|
||
|
||
Args:
|
||
user_id: 用户学号
|
||
|
||
Returns:
|
||
bool: 是否成功移除
|
||
"""
|
||
if user_id in _task_managers:
|
||
del _task_managers[user_id]
|
||
return True
|
||
return False
|
||
|
||
|
||
def get_all_task_managers() -> Dict[str, EvaluationTaskManager]:
|
||
"""获取所有任务管理器"""
|
||
return _task_managers.copy()
|