切换到 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 失败或工单未启用时直接处理告警+更新卡片
This commit is contained in:
@@ -176,34 +176,37 @@ async def edge_device_heartbeat(request: Request, _auth=Depends(_verify_edge_tok
|
||||
|
||||
|
||||
async def _resolve_card_update(alarm_id: str, resolve_type: str):
|
||||
"""边缘端 resolve 后异步处理:更新卡片(工单暂未上线)"""
|
||||
"""边缘端 resolve 后异步处理:工单自动结单 + 更新卡片"""
|
||||
try:
|
||||
from app.services.work_order_client import get_work_order_client
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
from app.models import get_session, AlarmEventExt
|
||||
|
||||
# 工单自动结单(暂未上线)
|
||||
# from app.services.work_order_client import get_work_order_client
|
||||
# from app.models import get_session, AlarmEventExt
|
||||
# wo_client = get_work_order_client()
|
||||
# if wo_client.enabled:
|
||||
# db = get_session()
|
||||
# try:
|
||||
# ext = db.query(AlarmEventExt).filter(
|
||||
# AlarmEventExt.alarm_id == alarm_id,
|
||||
# AlarmEventExt.ext_type == "WORK_ORDER",
|
||||
# ).first()
|
||||
# order_id = ext.ext_data.get("order_id", "") if ext and ext.ext_data else ""
|
||||
# finally:
|
||||
# db.close()
|
||||
# if order_id:
|
||||
# remark_map = {
|
||||
# "person_returned": "人员回岗自动关闭",
|
||||
# "non_work_time": "非工作时间自动关闭",
|
||||
# "intrusion_cleared": "入侵消失自动关闭",
|
||||
# }
|
||||
# remark = remark_map.get(resolve_type, f"边缘端自动结单: {resolve_type}")
|
||||
# await wo_client.auto_complete_order(order_id, remark)
|
||||
# 工单自动结单
|
||||
wo_client = get_work_order_client()
|
||||
if wo_client.enabled:
|
||||
db = get_session()
|
||||
try:
|
||||
ext = db.query(AlarmEventExt).filter(
|
||||
AlarmEventExt.alarm_id == alarm_id,
|
||||
AlarmEventExt.ext_type == "WORK_ORDER",
|
||||
).first()
|
||||
order_id = ext.ext_data.get("order_id", "") if ext and ext.ext_data else ""
|
||||
finally:
|
||||
db.close()
|
||||
if order_id:
|
||||
remark_map = {
|
||||
"person_returned": "人员回岗自动关闭",
|
||||
"non_work_time": "非工作时间自动关闭",
|
||||
"intrusion_cleared": "入侵消失自动关闭",
|
||||
}
|
||||
remark = remark_map.get(resolve_type, f"边缘端自动结单: {resolve_type}")
|
||||
success = await wo_client.auto_complete_order(order_id, remark)
|
||||
if success:
|
||||
logger.info(f"IoT工单已自动结单,等待sync-status回调: alarm={alarm_id}, order={order_id}")
|
||||
return # IoT 回调 sync-status 会更新告警+卡片
|
||||
|
||||
# 更新企微卡片到终态(如果有 response_code)
|
||||
# 降级:直接更新企微卡片到终态
|
||||
wechat = get_wechat_service()
|
||||
if wechat.enabled:
|
||||
response_code = wechat.get_response_code(alarm_id)
|
||||
|
||||
@@ -181,20 +181,20 @@ async def _process_card_button_click(msg: dict):
|
||||
if order_id and wo_client.enabled:
|
||||
success = await wo_client.confirm_order(order_id)
|
||||
if success:
|
||||
logger.info(f"IoT工单已确认: alarm={alarm_id}, order={order_id}, by={user_id}")
|
||||
else:
|
||||
logger.warning(f"IoT工单确认失败: alarm={alarm_id}, order={order_id}")
|
||||
# IoT 调用失败时降级直接更新
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="CONFIRMED",
|
||||
handle_status="HANDLING", handler=user_id, remark="企微确认(IoT降级)")
|
||||
logger.info(f"IoT工单已确认,等待sync-status回调: alarm={alarm_id}, order={order_id}, by={user_id}")
|
||||
return # IoT 回调 sync-status 会更新告警+卡片
|
||||
# IoT 调用失败时降级直接更新
|
||||
logger.warning(f"IoT工单确认失败,降级处理: alarm={alarm_id}, order={order_id}")
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="CONFIRMED",
|
||||
handle_status="HANDLING", handler=user_id, remark="企微确认(IoT降级)")
|
||||
else:
|
||||
# 工单未启用时直接更新告警状态
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="CONFIRMED",
|
||||
handle_status="HANDLING", handler=user_id, remark="企微确认接单")
|
||||
|
||||
# 更新卡片提示去H5处理
|
||||
# 降级时才更新卡片
|
||||
if response_code:
|
||||
await wechat.update_alarm_card_step2(
|
||||
response_code=response_code,
|
||||
@@ -208,17 +208,18 @@ async def _process_card_button_click(msg: dict):
|
||||
if order_id and wo_client.enabled:
|
||||
success = await wo_client.false_alarm(order_id)
|
||||
if success:
|
||||
logger.info(f"IoT工单已标记误报: alarm={alarm_id}, order={order_id}, by={user_id}")
|
||||
else:
|
||||
logger.warning(f"IoT误报标记失败: alarm={alarm_id}, order={order_id}")
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="FALSE",
|
||||
handle_status="IGNORED", handler=user_id, remark="企微误报(IoT降级)")
|
||||
logger.info(f"IoT工单已标记误报,等待sync-status回调: alarm={alarm_id}, order={order_id}, by={user_id}")
|
||||
return # IoT 回调 sync-status 会更新告警+卡片
|
||||
logger.warning(f"IoT误报标记失败,降级处理: alarm={alarm_id}, order={order_id}")
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="FALSE",
|
||||
handle_status="IGNORED", handler=user_id, remark="企微误报(IoT降级)")
|
||||
else:
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="FALSE",
|
||||
handle_status="IGNORED", handler=user_id, remark="企微误报忽略")
|
||||
|
||||
# 降级时才更新卡片
|
||||
if response_code:
|
||||
await wechat.update_alarm_card_terminal(
|
||||
response_code=response_code, user_ids=[user_id],
|
||||
@@ -230,16 +231,17 @@ async def _process_card_button_click(msg: dict):
|
||||
if order_id and wo_client.enabled:
|
||||
success = await wo_client.submit_order(order_id, result=f"已处理 by {user_id}")
|
||||
if success:
|
||||
logger.info(f"IoT工单已提交: alarm={alarm_id}, order={order_id}")
|
||||
else:
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="CLOSED",
|
||||
handle_status="DONE", handler=user_id, remark="企微完成(IoT降级)")
|
||||
logger.info(f"IoT工单已提交,等待sync-status回调: alarm={alarm_id}, order={order_id}")
|
||||
return # IoT 回调 sync-status 会更新告警+卡片
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="CLOSED",
|
||||
handle_status="DONE", handler=user_id, remark="企微完成(IoT降级)")
|
||||
else:
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="CLOSED",
|
||||
handle_status="DONE", handler=user_id, remark="企微已处理")
|
||||
|
||||
# 降级时才更新卡片
|
||||
if response_code:
|
||||
await wechat.update_alarm_card_terminal(
|
||||
response_code=response_code, user_ids=[user_id],
|
||||
@@ -251,16 +253,17 @@ async def _process_card_button_click(msg: dict):
|
||||
if order_id and wo_client.enabled:
|
||||
success = await wo_client.false_alarm(order_id)
|
||||
if success:
|
||||
logger.info(f"IoT工单已标记误报: alarm={alarm_id}, order={order_id}")
|
||||
else:
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="FALSE",
|
||||
handle_status="IGNORED", handler=user_id, remark="企微误报(IoT降级)")
|
||||
logger.info(f"IoT工单已标记误报,等待sync-status回调: alarm={alarm_id}, order={order_id}")
|
||||
return # IoT 回调 sync-status 会更新告警+卡片
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="FALSE",
|
||||
handle_status="IGNORED", handler=user_id, remark="企微误报(IoT降级)")
|
||||
else:
|
||||
service = get_alarm_event_service()
|
||||
service.handle_alarm(alarm_id=alarm_id, alarm_status="FALSE",
|
||||
handle_status="IGNORED", handler=user_id, remark="企微标记误报")
|
||||
|
||||
# 降级时才更新卡片
|
||||
if response_code:
|
||||
await wechat.update_alarm_card_terminal(
|
||||
response_code=response_code, user_ids=[user_id],
|
||||
|
||||
@@ -83,7 +83,7 @@ async def sync_status(req: SyncStatusRequest):
|
||||
|
||||
# 状态映射
|
||||
status_map = {
|
||||
"confirmed": {"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"},
|
||||
@@ -103,11 +103,11 @@ async def sync_status(req: SyncStatusRequest):
|
||||
)
|
||||
logger.info(f"告警状态已同步: alarm={req.alarmId}, status={req.status}")
|
||||
|
||||
# 2. 更新企微卡片
|
||||
# 2. 更新企微卡片(区分 confirm→step2 和其他→terminal)
|
||||
if mapping["card_action"] and wechat.enabled:
|
||||
response_code = wechat.get_response_code(req.alarmId)
|
||||
if response_code:
|
||||
if req.status == "confirmed":
|
||||
if mapping["card_action"] == "confirm":
|
||||
# 确认接单:重绘卡片提示去H5处理
|
||||
await wechat.update_alarm_card_step2(
|
||||
response_code=response_code,
|
||||
|
||||
@@ -246,35 +246,64 @@ async def handle_alert(
|
||||
if not alarm:
|
||||
raise HTTPException(status_code=404, detail="告警不存在")
|
||||
|
||||
# 终态操作(CLOSED/FALSE)同步更新企微卡片
|
||||
# 终态操作(CLOSED/FALSE)同步工单 + 更新卡片
|
||||
if alarmStatus in ("CLOSED", "FALSE"):
|
||||
action = "complete" if alarmStatus == "CLOSED" else "false"
|
||||
asyncio.create_task(_sync_wechat_card_terminal(alarm_id, action, handler))
|
||||
asyncio.create_task(_sync_work_order_and_card(alarm_id, alarmStatus, handler))
|
||||
|
||||
return YudaoResponse.success(True)
|
||||
|
||||
|
||||
async def _sync_wechat_card_terminal(alarm_id: str, action: str, operator: str):
|
||||
"""前端处理/误报后,异步同步企微卡片到终态"""
|
||||
async def _sync_work_order_and_card(alarm_id: str, alarm_status: str, operator: str):
|
||||
"""前端操作后:优先调 IoT 工单 API,降级直接更新卡片"""
|
||||
try:
|
||||
from app.services.work_order_client import get_work_order_client
|
||||
|
||||
wo_client = get_work_order_client()
|
||||
order_id = _get_order_id_for_alarm(alarm_id) if wo_client.enabled else ""
|
||||
|
||||
if order_id and wo_client.enabled:
|
||||
if alarm_status == "CLOSED":
|
||||
success = await wo_client.submit_order(order_id, result=f"管理员处理 by {operator}")
|
||||
else: # FALSE
|
||||
success = await wo_client.false_alarm(order_id)
|
||||
if success:
|
||||
logger.info(f"前端操作已同步IoT工单: alarm={alarm_id}, status={alarm_status}")
|
||||
return # IoT 回调 sync-status 会更新卡片
|
||||
|
||||
# 降级:直接更新卡片
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
wechat = get_wechat_service()
|
||||
if not wechat.enabled:
|
||||
return
|
||||
response_code = wechat.get_response_code(alarm_id)
|
||||
if not response_code:
|
||||
logger.debug(f"告警 {alarm_id} 无企微卡片 response_code,跳过卡片更新")
|
||||
return
|
||||
await wechat.update_alarm_card_terminal(
|
||||
response_code=response_code,
|
||||
user_ids=[],
|
||||
alarm_id=alarm_id,
|
||||
action=action,
|
||||
operator_name=operator,
|
||||
)
|
||||
logger.info(f"前端操作同步企微卡片: alarm={alarm_id}, action={action}")
|
||||
if wechat.enabled:
|
||||
response_code = wechat.get_response_code(alarm_id)
|
||||
if response_code:
|
||||
action = "complete" if alarm_status == "CLOSED" else "false"
|
||||
await wechat.update_alarm_card_terminal(
|
||||
response_code=response_code, user_ids=[],
|
||||
alarm_id=alarm_id, action=action, operator_name=operator,
|
||||
)
|
||||
logger.info(f"降级直接更新卡片: alarm={alarm_id}, action={action}")
|
||||
except Exception as e:
|
||||
logger.error(f"同步企微卡片失败: alarm={alarm_id}, error={e}", exc_info=True)
|
||||
logger.error(f"同步工单/卡片失败: alarm={alarm_id}, error={e}", exc_info=True)
|
||||
|
||||
|
||||
def _get_order_id_for_alarm(alarm_id: str) -> str:
|
||||
"""从 alarm_event_ext 中获取关联的工单ID"""
|
||||
from app.models import get_session, AlarmEventExt
|
||||
|
||||
db = get_session()
|
||||
try:
|
||||
ext = db.query(AlarmEventExt).filter(
|
||||
AlarmEventExt.alarm_id == alarm_id,
|
||||
AlarmEventExt.ext_type == "WORK_ORDER",
|
||||
).first()
|
||||
if ext and ext.ext_data:
|
||||
return ext.ext_data.get("order_id", "")
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.error(f"查询工单ID失败: alarm={alarm_id}, error={e}")
|
||||
return ""
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.delete("/alert/delete")
|
||||
|
||||
@@ -147,24 +147,9 @@ async def process_alarm_notification(alarm_data: Dict):
|
||||
else:
|
||||
logger.debug("未配置群聊ID,跳过群聊推送")
|
||||
|
||||
# ---- 3b. 个人按钮交互卡片 ----
|
||||
sent = await wechat_service.send_alarm_card(
|
||||
user_ids=user_ids,
|
||||
alarm_id=alarm_id,
|
||||
alarm_type=alarm_type,
|
||||
area_name=area_name,
|
||||
camera_name=camera_name,
|
||||
description=description,
|
||||
event_time=event_time_str,
|
||||
alarm_level=alarm_level,
|
||||
)
|
||||
if sent:
|
||||
logger.info(f"告警通知完成: {alarm_id}")
|
||||
else:
|
||||
logger.warning(f"个人卡片发送失败: {alarm_id}")
|
||||
|
||||
# ---- 4. 创建安保工单 ----
|
||||
# ---- 3b. 创建工单(优先于卡片发送) ----
|
||||
wo_client = get_work_order_client()
|
||||
order_created = False
|
||||
if wo_client.enabled:
|
||||
wo_area_id = _get_alarm_area_id(alarm_id) or area_id_int
|
||||
if wo_area_id:
|
||||
@@ -187,9 +172,28 @@ async def process_alarm_notification(alarm_data: Dict):
|
||||
)
|
||||
if order_id:
|
||||
_save_order_id(alarm_id, order_id)
|
||||
order_created = True
|
||||
logger.info(f"工单已创建,等待IoT回调发卡片: alarm={alarm_id}, order={order_id}")
|
||||
else:
|
||||
logger.warning(f"告警无 area_id,跳过工单创建: {alarm_id}")
|
||||
|
||||
# ---- 3c. 个人卡片(仅在工单未创建时降级发送) ----
|
||||
if not order_created:
|
||||
sent = await wechat_service.send_alarm_card(
|
||||
user_ids=user_ids,
|
||||
alarm_id=alarm_id,
|
||||
alarm_type=alarm_type,
|
||||
area_name=area_name,
|
||||
camera_name=camera_name,
|
||||
description=description,
|
||||
event_time=event_time_str,
|
||||
alarm_level=alarm_level,
|
||||
)
|
||||
if sent:
|
||||
logger.info(f"降级直发卡片完成: {alarm_id}")
|
||||
else:
|
||||
logger.warning(f"降级直发卡片失败: {alarm_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"告警通知处理失败: {alarm_id}, error={e}", exc_info=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user