Files
iot-device-management-service/app/routers/wechat_notify_api.py
16337 3a9595de7c 切换到 IoT 工单驱动模式:所有状态变更由 IoT 回调驱动
1. notify_dispatch: 工单优先于卡片发送,创建成功则跳过直发卡片(等IoT回调send-card)
2. wechat_callback: IoT API 成功后直接返回,等 sync-status 回调更新告警+卡片
3. edge_compat: 启用工单自动结单,成功后等 sync-status 回调
4. yudao_aiot_alarm: 前端操作优先调 IoT 工单 API,降级直接更新卡片
5. wechat_notify_api: 修复 confirmed 的 card_action 为 None 导致卡片不更新的 bug

所有路径均保留降级逻辑:IoT 失败或工单未启用时直接处理告警+更新卡片
2026-03-25 15:38:52 +08:00

158 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
企微通知 API — 供 IoT 平台调用
IoT 平台通过这两个接口驱动企微消息:
- /send-card: 工单派单后发送企微卡片
- /sync-status: 工单状态变更后同步告警状态+更新卡片
"""
from fastapi import APIRouter, Request
from pydantic import BaseModel
from typing import List, Optional
from app.utils.logger import logger
router = APIRouter(prefix="/api/wechat/notify", tags=["企微通知-IoT回调"])
class SendCardRequest(BaseModel):
"""IoT 派单后发送企微卡片"""
alarmId: str
orderId: str
userIds: List[str]
title: str
areaName: Optional[str] = ""
cameraName: Optional[str] = ""
eventTime: Optional[str] = ""
level: Optional[int] = 2
snapshotUrl: Optional[str] = ""
class SyncStatusRequest(BaseModel):
"""IoT 工单状态变更后同步"""
alarmId: str
orderId: str
status: str # confirmed / completed / false_alarm / auto_resolved
operator: Optional[str] = ""
remark: Optional[str] = ""
@router.post("/send-card")
async def send_card(req: SendCardRequest):
"""IoT 派单后调用,发送企微工单卡片给指定保安"""
try:
from app.services.wechat_service import get_wechat_service, ALARM_LEVEL_NAMES
wechat = get_wechat_service()
if not wechat.enabled:
return {"code": -1, "msg": "企微未启用"}
level_name = ALARM_LEVEL_NAMES.get(req.level, "普通")
sent = await wechat.send_alarm_card(
user_ids=req.userIds,
alarm_id=req.alarmId,
alarm_type=req.title,
area_name=req.areaName or "",
camera_name=req.cameraName or "",
description=f"工单编号:{req.orderId}",
event_time=req.eventTime or "",
alarm_level=req.level or 2,
)
if sent:
logger.info(f"IoT回调发卡片成功: alarm={req.alarmId}, order={req.orderId}, users={req.userIds}")
return {"code": 0, "msg": "success"}
else:
return {"code": -1, "msg": "发送失败"}
except Exception as e:
logger.error(f"IoT回调发卡片异常: {e}", exc_info=True)
return {"code": -1, "msg": str(e)}
@router.post("/sync-status")
async def sync_status(req: SyncStatusRequest):
"""IoT 工单状态变更后调用,同步更新告警状态+企微卡片"""
try:
from app.services.alarm_event_service import get_alarm_event_service
from app.services.wechat_service import get_wechat_service
service = get_alarm_event_service()
wechat = get_wechat_service()
# 状态映射
status_map = {
"confirmed": {"alarm_status": "CONFIRMED", "handle_status": "HANDLING", "card_action": "confirm"},
"completed": {"alarm_status": "CLOSED", "handle_status": "DONE", "card_action": "complete"},
"false_alarm": {"alarm_status": "FALSE", "handle_status": "IGNORED", "card_action": "false"},
"auto_resolved": {"alarm_status": "CLOSED", "handle_status": "DONE", "card_action": "auto_resolve"},
}
mapping = status_map.get(req.status)
if not mapping:
return {"code": 400, "msg": f"未知状态: {req.status}"}
# 1. 更新告警状态
service.handle_alarm(
alarm_id=req.alarmId,
alarm_status=mapping["alarm_status"],
handle_status=mapping["handle_status"],
handler=req.operator or "",
remark=req.remark or f"IoT工单同步: {req.status}",
)
logger.info(f"告警状态已同步: alarm={req.alarmId}, status={req.status}")
# 2. 更新企微卡片(区分 confirm→step2 和其他→terminal
if mapping["card_action"] and wechat.enabled:
response_code = wechat.get_response_code(req.alarmId)
if response_code:
if mapping["card_action"] == "confirm":
# 确认接单重绘卡片提示去H5处理
await wechat.update_alarm_card_step2(
response_code=response_code,
user_ids=[req.operator] if req.operator else [],
alarm_id=req.alarmId,
operator_name=req.operator,
)
else:
# 终态:按钮变灰
await wechat.update_alarm_card_terminal(
response_code=response_code,
user_ids=[req.operator] if req.operator else [],
alarm_id=req.alarmId,
action=mapping["card_action"],
operator_name=req.operator,
)
logger.info(f"卡片已更新: alarm={req.alarmId}, action={mapping['card_action']}")
else:
logger.warning(f"未找到 response_code跳过卡片更新: alarm={req.alarmId}")
return {"code": 0, "msg": "success"}
except Exception as e:
logger.error(f"IoT回调同步状态异常: {e}", exc_info=True)
return {"code": -1, "msg": str(e)}
@router.post("/daily-report")
async def trigger_daily_report(preview: bool = False):
"""手动触发每日告警日报
- preview=false默认生成并发送到企微群聊
- preview=true仅生成内容预览不发送
"""
try:
from app.services.daily_report_service import generate_daily_report, _send_daily_report
if preview:
content = await generate_daily_report()
return {"code": 0, "data": {"content": content}, "msg": "预览生成成功(未发送)"}
await _send_daily_report()
return {"code": 0, "msg": "日报已发送"}
except Exception as e:
logger.error(f"手动触发日报异常: {e}", exc_info=True)
return {"code": -1, "msg": str(e)}