Files
iot-device-management-service/app/routers/wechat_callback.py

309 lines
11 KiB
Python
Raw Normal View History

"""
企微回调路由
处理安保人员在企微卡片上的操作前往处理/误报忽略
接收企微用户消息并路由到交互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")
# ---- 图片消息 → Agent 图片分析 ----
if msg_type == "image":
user_id = msg.get("FromUserName", "")
media_id = msg.get("MediaId", "")
logger.info(f"收到企微图片: user={user_id}, media_id={media_id}")
asyncio.create_task(_process_agent_image(user_id, media_id))
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_agent_image(user_id: str, media_id: str):
"""异步处理图片消息:先回复"正在分析",再调 VLM 分析"""
try:
from app.services.agent_dispatcher import get_agent_dispatcher
from app.services.wechat_service import get_wechat_service
wechat = get_wechat_service()
# 先回复提示,避免用户等太久
await wechat.send_text_message(user_id, "正在分析图片,请稍候...")
dispatcher = get_agent_dispatcher()
reply = await dispatcher.handle_image(user_id, media_id)
await wechat.send_text_message(user_id, reply)
except Exception as e:
logger.error(f"Agent图片处理失败: user={user_id}, error={e}", exc_info=True)
try:
from app.services.wechat_service import get_wechat_service
wechat = get_wechat_service()
await wechat.send_text_message(user_id, "图片处理失败,请稍后重试。")
except Exception:
pass
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})