""" 企微回调路由 处理安保人员在企微卡片上的操作(前往处理/误报忽略)。 接收企微用户消息并路由到交互Agent。 """ import asyncio from fastapi import APIRouter, Depends, Query, Request from fastapi.responses import PlainTextResponse from app.yudao_compat import YudaoResponse from app.services.alarm_event_service import get_alarm_event_service, AlarmEventService from app.utils.logger import logger router = APIRouter(prefix="/api/wechat", tags=["企微回调"]) # ==================== 交互Agent消息回调 ==================== @router.get("/agent/callback") async def wechat_agent_verify( msg_signature: str = Query(...), timestamp: str = Query(...), nonce: str = Query(...), echostr: str = Query(...), ): """企微回调URL验证(首次配置时调用)""" from app.services.wechat_crypto import WeChatCrypto try: crypto = WeChatCrypto() echo = crypto.verify_url(msg_signature, timestamp, nonce, echostr) return PlainTextResponse(content=echo) except Exception as e: logger.error(f"企微URL验证失败: {e}") return PlainTextResponse(content="", status_code=403) @router.post("/agent/callback") async def wechat_agent_message( request: Request, msg_signature: str = Query(...), timestamp: str = Query(...), nonce: str = Query(...), ): """接收企微用户消息,路由到交互Agent""" body = await request.body() from app.services.wechat_crypto import WeChatCrypto try: crypto = WeChatCrypto() msg = crypto.decrypt_message(body, msg_signature, timestamp, nonce) except Exception as e: logger.error(f"企微消息解密失败: {e}") return PlainTextResponse(content="success") msg_type = msg.get("MsgType", "") event = msg.get("Event", "") # ---- 模板卡片按钮点击事件 ---- if msg_type == "event" and event == "template_card_event": asyncio.create_task(_process_card_button_click(msg)) return PlainTextResponse(content="success") # ---- 文本消息 → Agent 对话 ---- if msg_type == "text": user_id = msg.get("FromUserName", "") content = msg.get("Content", "") logger.info(f"收到企微消息: user={user_id}, content={content[:50]}") asyncio.create_task(_process_agent_message(user_id, content)) return PlainTextResponse(content="success") return PlainTextResponse(content="success") async def _process_agent_message(user_id: str, content: str): """异步处理消息并主动回复""" try: from app.services.agent_dispatcher import get_agent_dispatcher from app.services.wechat_service import get_wechat_service dispatcher = get_agent_dispatcher() reply = await dispatcher.handle_message(user_id, content) wechat = get_wechat_service() await wechat.send_text_message(user_id, reply) except Exception as e: logger.error(f"Agent消息处理失败: user={user_id}, error={e}", exc_info=True) async def _process_card_button_click(msg: dict): """ 处理模板卡片按钮点击事件(两步状态机) 第一步按钮: - confirm_{alarm_id}: 确认接单 → 更新状态为处理中,创建工单,卡片更新到第二步 - ignore_{alarm_id}: 误报忽略 → 终态,自动结单 第二步按钮: - complete_{alarm_id}: 已处理完成 → 终态,自动结单 - false_{alarm_id}: 标记误报 → 终态,自动结单 终态按钮: - done_{alarm_id}: 已完成,忽略 """ try: from app.services.wechat_service import get_wechat_service from app.services.work_order_client import get_work_order_client user_id = msg.get("FromUserName", "") event_key = msg.get("EventKey", "") task_id = msg.get("TaskId", "") response_code = msg.get("ResponseCode", "") logger.info( f"卡片按钮点击: user={user_id}, key={event_key}, task={task_id}" ) # 解析 action 和 alarm_id if event_key.startswith("confirm_"): action = "confirm" alarm_id = event_key[len("confirm_"):] elif event_key.startswith("ignore_"): action = "ignore" alarm_id = event_key[len("ignore_"):] elif event_key.startswith("complete_"): action = "complete" alarm_id = event_key[len("complete_"):] elif event_key.startswith("false_"): action = "false" alarm_id = event_key[len("false_"):] elif event_key.startswith("done_"): return # 终态按钮,忽略 else: logger.warning(f"未知的按钮 key: {event_key}") return wechat = get_wechat_service() wo_client = get_work_order_client() service = get_alarm_event_service() # ---- 第一步:确认接单 ---- if action == "confirm": # 更新告警状态为处理中 service.handle_alarm( alarm_id=alarm_id, alarm_status="CONFIRMED", handle_status="HANDLING", handler=user_id, remark="企微卡片-确认接单", ) logger.info(f"告警已确认接单: alarm={alarm_id}, by={user_id}") # 更新卡片到第二步(新交互按钮) if response_code: await wechat.update_alarm_card_step2( response_code=response_code, user_ids=[user_id], alarm_id=alarm_id, operator_name=user_id, ) # ---- 第一步:误报忽略 ---- elif action == "ignore": service.handle_alarm( alarm_id=alarm_id, alarm_status="FALSE", handle_status="IGNORED", handler=user_id, remark="企微卡片-误报忽略", ) logger.info(f"告警已标记忽略: alarm={alarm_id}, by={user_id}") # 终态卡片 if response_code: await wechat.update_alarm_card_terminal( response_code=response_code, user_ids=[user_id], alarm_id=alarm_id, action="ignore", operator_name=user_id, ) # 自动结单 order_id = _get_order_id_for_alarm(alarm_id) if order_id: await wo_client.auto_complete_order(order_id, f"误报忽略 by {user_id}") # ---- 第二步:已处理完成 ---- elif action == "complete": service.handle_alarm( alarm_id=alarm_id, alarm_status="CLOSED", handle_status="DONE", handler=user_id, remark="企微卡片-已处理完成", ) logger.info(f"告警已处理完成: alarm={alarm_id}, by={user_id}") # 终态卡片 if response_code: await wechat.update_alarm_card_terminal( response_code=response_code, user_ids=[user_id], alarm_id=alarm_id, action="complete", operator_name=user_id, ) # 自动结单 order_id = _get_order_id_for_alarm(alarm_id) if order_id: await wo_client.auto_complete_order(order_id, f"已处理 by {user_id}") # ---- 第二步:标记误报 ---- elif action == "false": service.handle_alarm( alarm_id=alarm_id, alarm_status="FALSE", handle_status="IGNORED", handler=user_id, remark="企微卡片-标记误报", ) logger.info(f"告警已标记误报: alarm={alarm_id}, by={user_id}") # 终态卡片 if response_code: await wechat.update_alarm_card_terminal( response_code=response_code, user_ids=[user_id], alarm_id=alarm_id, action="false", operator_name=user_id, ) # 自动结单 order_id = _get_order_id_for_alarm(alarm_id) if order_id: await wo_client.auto_complete_order(order_id, f"标记误报 by {user_id}") except Exception as e: logger.error(f"处理卡片按钮点击失败: {e}", exc_info=True) def _get_order_id_for_alarm(alarm_id: str) -> str: """从 alarm_event_ext 中获取关联的工单ID""" from app.models import get_session, AlarmEventExt db = get_session() try: ext = db.query(AlarmEventExt).filter( AlarmEventExt.alarm_id == alarm_id, AlarmEventExt.ext_type == "WORK_ORDER", ).first() if ext and ext.ext_data: return ext.ext_data.get("order_id", "") return "" except Exception as e: logger.error(f"查询工单ID失败: alarm={alarm_id}, error={e}") return "" finally: db.close() # ==================== Agent测试接口(开发用) ==================== @router.post("/agent/test") async def test_agent_message( user_id: str = Query(default="test_user"), content: str = Query(..., description="测试消息内容"), ): """测试Agent对话(开发用,无加密,直接返回回复)""" from app.services.agent_dispatcher import get_agent_dispatcher dispatcher = get_agent_dispatcher() reply = await dispatcher.handle_message(user_id, content) return YudaoResponse.success({"user_id": user_id, "content": content, "reply": reply})