2026-03-06 13:35:40 +08:00
|
|
|
|
"""
|
|
|
|
|
|
企微回调路由
|
|
|
|
|
|
|
2026-03-11 17:43:58 +08:00
|
|
|
|
处理安保人员在企微卡片上的操作(前往处理/误报忽略)。
|
2026-03-09 10:42:32 +08:00
|
|
|
|
接收企微用户消息并路由到交互Agent。
|
2026-03-06 13:35:40 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-03-09 10:42:32 +08:00
|
|
|
|
import asyncio
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Query, Request
|
|
|
|
|
|
from fastapi.responses import PlainTextResponse
|
2026-03-06 13:35:40 +08:00
|
|
|
|
|
|
|
|
|
|
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=["企微回调"])
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-09 10:42:32 +08:00
|
|
|
|
# ==================== 交互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")
|
|
|
|
|
|
|
2026-03-11 14:36:58 +08:00
|
|
|
|
msg_type = msg.get("MsgType", "")
|
|
|
|
|
|
event = msg.get("Event", "")
|
2026-03-09 10:42:32 +08:00
|
|
|
|
|
2026-03-11 14:36:58 +08:00
|
|
|
|
# ---- 模板卡片按钮点击事件 ----
|
|
|
|
|
|
if msg_type == "event" and event == "template_card_event":
|
|
|
|
|
|
asyncio.create_task(_process_card_button_click(msg))
|
|
|
|
|
|
return PlainTextResponse(content="success")
|
2026-03-09 10:42:32 +08:00
|
|
|
|
|
2026-03-11 14:36:58 +08:00
|
|
|
|
# ---- 文本消息 → 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")
|
2026-03-09 10:42:32 +08:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-11 14:36:58 +08:00
|
|
|
|
async def _process_card_button_click(msg: dict):
|
|
|
|
|
|
"""
|
|
|
|
|
|
处理模板卡片按钮点击事件
|
|
|
|
|
|
|
|
|
|
|
|
企微回调 XML 解密后包含:
|
|
|
|
|
|
- FromUserName: 点击者 userid
|
|
|
|
|
|
- EventKey: 按钮 key (handle_{alarm_id} / ignore_{alarm_id})
|
|
|
|
|
|
- TaskId: 卡片的 task_id (alarm_id)
|
|
|
|
|
|
- ResponseCode: 用于更新卡片状态(一次性)
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from app.services.wechat_service import get_wechat_service
|
|
|
|
|
|
|
|
|
|
|
|
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}, "
|
|
|
|
|
|
f"task={task_id}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 解析 action 和 alarm_id
|
|
|
|
|
|
if event_key.startswith("handle_"):
|
|
|
|
|
|
action = "handle"
|
|
|
|
|
|
alarm_id = event_key[len("handle_"):]
|
|
|
|
|
|
elif event_key.startswith("ignore_"):
|
|
|
|
|
|
action = "ignore"
|
|
|
|
|
|
alarm_id = event_key[len("ignore_"):]
|
|
|
|
|
|
elif event_key.startswith("done_"):
|
|
|
|
|
|
# 已处理状态的按钮,忽略
|
|
|
|
|
|
return
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.warning(f"未知的按钮 key: {event_key}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 更新告警状态
|
|
|
|
|
|
action_map = {
|
|
|
|
|
|
"handle": {
|
|
|
|
|
|
"alarm_status": "CONFIRMED",
|
|
|
|
|
|
"handle_status": "HANDLING",
|
|
|
|
|
|
"remark": "企微卡片-前往处理",
|
|
|
|
|
|
},
|
|
|
|
|
|
"ignore": {
|
|
|
|
|
|
"alarm_status": "FALSE",
|
|
|
|
|
|
"handle_status": "IGNORED",
|
|
|
|
|
|
"remark": "企微卡片-标记误报",
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
action_cfg = action_map.get(action)
|
|
|
|
|
|
if action_cfg:
|
|
|
|
|
|
service = get_alarm_event_service()
|
|
|
|
|
|
service.handle_alarm(
|
|
|
|
|
|
alarm_id=alarm_id,
|
|
|
|
|
|
alarm_status=action_cfg["alarm_status"],
|
|
|
|
|
|
handle_status=action_cfg["handle_status"],
|
|
|
|
|
|
handler=user_id,
|
|
|
|
|
|
remark=action_cfg["remark"],
|
|
|
|
|
|
)
|
|
|
|
|
|
logger.info(f"告警状态已更新: alarm={alarm_id}, action={action}, by={user_id}")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新卡片按钮状态(变灰 + 显示处理结果)
|
|
|
|
|
|
if response_code:
|
|
|
|
|
|
wechat = get_wechat_service()
|
|
|
|
|
|
await wechat.update_alarm_card(
|
|
|
|
|
|
response_code=response_code,
|
|
|
|
|
|
user_ids=[user_id],
|
|
|
|
|
|
alarm_id=alarm_id,
|
|
|
|
|
|
action=action,
|
|
|
|
|
|
operator_name=user_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"处理卡片按钮点击失败: {e}", exc_info=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-09 10:42:32 +08:00
|
|
|
|
# ==================== 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})
|