From e6fd316036be5eb8ffc0fbd954a548ab53a36063 Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Fri, 27 Mar 2026 13:12:29 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=20sync-status=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=9A=E6=8C=89=E7=8A=B6=E6=80=81=E5=88=86=E6=B5=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E5=88=A0=E9=99=A4=20send-card=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dispatched: 发企微群聊+私发卡片(不更新告警) - confirmed: 仅更新卡片到第二步(不更新告警) - 终态(completed/false_alarm/auto_resolved): 更新告警+卡片 - 删除 send-card 接口和 SendCardRequest 类(企微由 dispatched 触发) --- app/routers/wechat_notify_api.py | 197 ++++++++++++++++++------------- 1 file changed, 115 insertions(+), 82 deletions(-) diff --git a/app/routers/wechat_notify_api.py b/app/routers/wechat_notify_api.py index 1d34bf7..77f69b2 100644 --- a/app/routers/wechat_notify_api.py +++ b/app/routers/wechat_notify_api.py @@ -1,34 +1,20 @@ """ 企微通知 API — 供 IoT 平台调用 -IoT 平台通过这两个接口驱动企微消息: -- /send-card: 工单派单后发送企微卡片 -- /sync-status: 工单状态变更后同步告警状态+更新卡片 +IoT 平台通过这些接口驱动企微消息: +- /sync-status: 工单状态变更后同步(dispatched发企微、confirmed更新卡片、终态更新告警+卡片) """ import json from fastapi import APIRouter, Request from pydantic import BaseModel -from typing import List, Optional +from typing import 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 @@ -74,48 +60,93 @@ async def _parse_body(request: Request) -> dict: return {} -@router.post("/send-card") -async def send_card(request: Request): - """IoT 派单后调用,发送企微工单卡片给指定保安""" +async def _send_wechat_on_dispatch(req, wechat): + """dispatched 时发企微群聊+私发卡片""" try: - data = await _parse_body(request) - logger.info(f"IoT send-card 收到数据: {data}") + from app.models import get_session, AlarmEvent + from app.services.camera_name_service import get_camera_name_service + from app.services.wechat_service import ALARM_TYPE_NAMES + from app.config import settings - req = SendCardRequest(**data) + # 查告警信息 + db = get_session() + try: + alarm = db.query(AlarmEvent).filter(AlarmEvent.alarm_id == req.alarmId).first() + if not alarm: + logger.warning(f"dispatched 发企微: 告警不存在 {req.alarmId}") + return + alarm_type = alarm.alarm_type or "" + device_id = alarm.device_id or "" + alarm_level = alarm.alarm_level or 2 + event_time = alarm.event_time + snapshot_url = alarm.snapshot_url or "" + area_id = alarm.area_id + finally: + db.close() - from app.services.wechat_service import get_wechat_service, ALARM_LEVEL_NAMES + # 摄像头名称 + camera_service = get_camera_name_service() + camera_info = await camera_service.get_camera_info(device_id) + camera_name = camera_service.format_display_name(device_id, camera_info) - wechat = get_wechat_service() - if not wechat.enabled: - return {"code": -1, "msg": "企微未启用"} + # 区域名称 + area_name = "" + if area_id: + from app.services.notify_dispatch import _get_area_name_from_iot + area_name = await _get_area_name_from_iot(area_id) + if not area_name: + area_name = "未知区域" - 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"} + # event_time 格式化 + if hasattr(event_time, 'strftime'): + event_time_str = event_time.strftime("%m-%d %H:%M") else: - return {"code": -1, "msg": "发送失败"} + s = str(event_time or "") + event_time_str = s[5:16] if len(s) >= 16 else s + + # 截图预签名URL + from app.services.notify_dispatch import _get_presigned_url + presigned_url = _get_presigned_url(snapshot_url) + + # 群聊通知 + group_chat_id = settings.wechat.group_chat_id + if group_chat_id: + await wechat.send_group_alarm_combo( + chat_id=group_chat_id, + alarm_id=req.alarmId, + alarm_type=alarm_type, + area_name=area_name, + camera_name=camera_name, + description=f"工单编号:{req.orderId}", + event_time=event_time_str, + alarm_level=alarm_level, + snapshot_url=presigned_url, + mention_user_ids=[req.operator] if req.operator else [], + ) + + # 私发卡片 + user_ids = [req.operator] if req.operator else [] + if user_ids: + await wechat.send_alarm_card( + user_ids=user_ids, + alarm_id=req.alarmId, + alarm_type=alarm_type, + area_name=area_name, + camera_name=camera_name, + description=f"工单编号:{req.orderId}", + event_time=event_time_str, + alarm_level=alarm_level, + ) + + logger.info(f"dispatched 企微通知已发送: alarm={req.alarmId}, order={req.orderId}") except Exception as e: - logger.error(f"IoT回调发卡片异常: {e}", exc_info=True) - return {"code": -1, "msg": str(e)} + logger.error(f"dispatched 发企微失败: alarm={req.alarmId}, error={e}", exc_info=True) @router.post("/sync-status") async def sync_status(request: Request): - """IoT 工单状态变更后调用,同步更新告警状态+企微卡片""" + """IoT 工单状态变更后调用""" try: data = await _parse_body(request) logger.info(f"IoT sync-status 收到数据: {data}") @@ -128,43 +159,46 @@ async def sync_status(request: Request): service = get_alarm_event_service() wechat = get_wechat_service() - # 状态映射 - status_map = { - "dispatched": {"alarm_status": "CONFIRMED", "handle_status": "HANDLING", "card_action": None}, - "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"}, - } + if req.status == "dispatched": + # 派单:发企微群聊+私发卡片(不更新告警) + if wechat.enabled: + await _send_wechat_on_dispatch(req, wechat) + return {"code": 0, "msg": "success"} - 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处理 + elif req.status == "confirmed": + # 确认接单:更新卡片到第二步(不更新告警) + if wechat.enabled: + response_code = wechat.get_response_code(req.alarmId) + if response_code: 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: - # 终态:按钮变灰 + logger.info(f"卡片已更新到步骤2: alarm={req.alarmId}") + return {"code": 0, "msg": "success"} + + elif req.status in ("completed", "false_alarm", "auto_resolved"): + # 终态:更新告警状态 + 更新卡片 + terminal_map = { + "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 = terminal_map[req.status] + 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}") + + if wechat.enabled: + response_code = wechat.get_response_code(req.alarmId) + if response_code: await wechat.update_alarm_card_terminal( response_code=response_code, user_ids=[req.operator] if req.operator else [], @@ -172,11 +206,11 @@ async def sync_status(request: Request): 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"} - return {"code": 0, "msg": "success"} + else: + logger.warning(f"未知状态,忽略: {req.status}") + return {"code": 0, "msg": "ignored"} except Exception as e: logger.error(f"IoT回调同步状态异常: {e}", exc_info=True) @@ -203,4 +237,3 @@ async def trigger_daily_report(preview: bool = False): except Exception as e: logger.error(f"手动触发日报异常: {e}", exc_info=True) return {"code": -1, "msg": str(e)} -