重构 sync-status 接口:按状态分流处理,删除 send-card 接口
- dispatched: 发企微群聊+私发卡片(不更新告警) - confirmed: 仅更新卡片到第二步(不更新告警) - 终态(completed/false_alarm/auto_resolved): 更新告警+卡片 - 删除 send-card 接口和 SendCardRequest 类(企微由 dispatched 触发)
This commit is contained in:
@@ -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)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user