""" 企微通知 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": None}, "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. 更新企微卡片 if mapping["card_action"] and wechat.enabled: response_code = wechat.get_response_code(req.alarmId) if response_code: if req.status == "confirmed": # 确认接单:重绘卡片提示去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)}