# ============================================================================= # 企微IT智能服务台 — 超时提醒定时任务 # ============================================================================= # 说明:定时检查超时未回复的会话,发送企微提醒消息 # 运行频率:每 30 秒执行一次 # 超时逻辑(改造后,决策 G4): # 1. 坐席回复后 3 分钟员工未回复 -> 发送企微提醒(只发 1 次) # 2. 坐席回复后 10 分钟员工未回复 -> 标记会话为 pending_close # 3. pending_close 后 5 分钟员工未响应 -> 自动 resolved(新增) # 4. ai_handling 30 分钟无互动 -> 发送提醒 -> 再 10 分钟 -> 自动 resolved(新增) # ============================================================================= import logging from datetime import datetime, timedelta from sqlalchemy import select from app.models.conversation import Conversation from app.services.reminder_service import send_reminder_message logger = logging.getLogger(__name__) # 超时配置(分钟) REMINDER_TIMEOUT_MINUTES = 3 # 未回复超时时间 CLOSE_TIMEOUT_MINUTES = 10 # 自动待关闭时间 PENDING_CLOSE_RESOLVE_MINUTES = 5 # pending_close → resolved 超时 # AI处理阶段超时配置 AI_HANDLING_TIMEOUT_MINUTES = 30 # AI处理超时(无互动) AI_HANDLING_REMINDER_TO_CLOSE_MINUTES = 10 # 提醒后仍未响应的关闭时间 async def check_unreplied_sessions(): """检查超时未回复的会话,发送提醒并标记待关闭/自动关闭。 此函数由 APScheduler 定时调用(每 30 秒)。 执行流程: 1. serving 状态:发送提醒 + 标记 pending_close(原有逻辑) 2. pending_close 状态:超时自动 resolved(新增) 3. ai_handling 状态:超时提醒 + 自动 resolved(新增) """ from app.database import _get_session_factory from app.services.closing_service import ClosingService async_session_factory = _get_session_factory() async with async_session_factory() as db: try: closing_service = ClosingService(db) # ================================================================== # 1. serving 状态:发送提醒 + 标记 pending_close(原有逻辑) # ================================================================== # 1a. 查找需要发送提醒的会话 # 条件:serving + 有坐席回复 + 超过3分钟未回复 + 未发送过提醒 reminder_threshold = datetime.now() - timedelta(minutes=REMINDER_TIMEOUT_MINUTES) reminder_stmt = select(Conversation).where( Conversation.status == "serving", Conversation.last_agent_reply_at.isnot(None), Conversation.last_agent_reply_at < reminder_threshold, Conversation.reminder_sent == False, ) result = await db.execute(reminder_stmt) sessions_to_remind = result.scalars().all() logger.info(f"发现 {len(sessions_to_remind)} 个需要发送提醒的会话") # 1b. 发送企微提醒消息 for session in sessions_to_remind: try: success = await send_reminder_message(session.employee_id) if success: session.reminder_sent = True session.reminder_sent_at = datetime.now() logger.info(f"会话 {session.id} 已发送提醒: employee_id={session.employee_id}") else: logger.warning(f"会话 {session.id} 提醒发送失败,跳过") except Exception as e: logger.error(f"会话 {session.id} 发送提醒异常: {e}") continue # 1c. 查找需要标记待关闭的会话 # 条件:serving + 有坐席回复 + 超过10分钟未回复 close_threshold = datetime.now() - timedelta(minutes=CLOSE_TIMEOUT_MINUTES) close_stmt = select(Conversation).where( Conversation.status == "serving", Conversation.last_agent_reply_at.isnot(None), Conversation.last_agent_reply_at < close_threshold, ) result = await db.execute(close_stmt) sessions_to_close = result.scalars().all() logger.info(f"发现 {len(sessions_to_close)} 个需要标记待关闭的会话") # 1d. 更新会话状态为 pending_close for session in sessions_to_close: session.status = "pending_close" session.pending_close_at = datetime.now() logger.info(f"会话 {session.id} 已标记为待关闭: employee_id={session.employee_id}") # ================================================================== # 2. pending_close 状态:超时自动 resolved(新增,决策 G4) # ================================================================== # 坐席发起结单后,员工5分钟内未确认 → 系统自动关闭 pending_close_sessions = await closing_service.get_pending_close_timeout_sessions() logger.info(f"发现 {len(pending_close_sessions)} 个 pending_close 超时会话") for session in pending_close_sessions: try: await closing_service.auto_timeout_close( str(session.id), timeout_type="pending_close" ) logger.info(f"会话 {session.id} pending_close 超时已自动关闭") except Exception as e: logger.error(f"会话 {session.id} pending_close 自动关闭失败: {e}") continue # ================================================================== # 3. ai_handling 状态:超时提醒 + 自动关闭(新增,决策 G4) # ================================================================== # AI处理阶段30分钟无互动 → 发送提醒 # 提醒后10分钟仍无响应 → 自动关闭 # 3a. 发送 ai_handling 超时提醒 ai_reminder_sessions = await closing_service.get_ai_handling_reminder_sessions() logger.info(f"发现 {len(ai_reminder_sessions)} 个 ai_handling 需要提醒的会话") for session in ai_reminder_sessions: try: # 推送 WS 超时警告 from app.services.ws_manager import manager as ws_manager await ws_manager.send_to_employee( session.employee_id, { "type": "auto_close_warning", "data": { "conversation_id": str(session.id), "minutes_remaining": AI_HANDLING_REMINDER_TO_CLOSE_MINUTES, "message": f"您的会话已空闲超过{AI_HANDLING_TIMEOUT_MINUTES}分钟," f"将在{AI_HANDLING_REMINDER_TO_CLOSE_MINUTES}分钟后自动关闭。" f"如需继续请发送消息。", "timestamp": datetime.now().isoformat(), }, } ) session.reminder_sent = True session.reminder_sent_at = datetime.now() logger.info(f"会话 {session.id} ai_handling 超时提醒已发送") except Exception as e: logger.error(f"会话 {session.id} ai_handling 提醒发送失败: {e}") continue # 3b. ai_handling 超时自动关闭 ai_timeout_sessions = await closing_service.get_ai_handling_timeout_sessions() logger.info(f"发现 {len(ai_timeout_sessions)} 个 ai_handling 超时关闭会话") for session in ai_timeout_sessions: try: await closing_service.auto_timeout_close( str(session.id), timeout_type="ai_handling" ) logger.info(f"会话 {session.id} ai_handling 超时已自动关闭") except Exception as e: logger.error(f"会话 {session.id} ai_handling 自动关闭失败: {e}") continue # 提交数据库变更 await db.commit() logger.info("超时检查任务执行完成") except Exception as e: await db.rollback() logger.error(f"超时检查任务执行异常: {e}") raise