diff --git a/app/routers/edge_compat.py b/app/routers/edge_compat.py index b258283..df2d765 100644 --- a/app/routers/edge_compat.py +++ b/app/routers/edge_compat.py @@ -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) diff --git a/app/routers/wechat_callback.py b/app/routers/wechat_callback.py index 1feea99..fc45191 100644 --- a/app/routers/wechat_callback.py +++ b/app/routers/wechat_callback.py @@ -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], diff --git a/app/routers/wechat_notify_api.py b/app/routers/wechat_notify_api.py index 79dbd90..db6760c 100644 --- a/app/routers/wechat_notify_api.py +++ b/app/routers/wechat_notify_api.py @@ -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, diff --git a/app/routers/yudao_aiot_alarm.py b/app/routers/yudao_aiot_alarm.py index 5371419..02da997 100644 --- a/app/routers/yudao_aiot_alarm.py +++ b/app/routers/yudao_aiot_alarm.py @@ -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") diff --git a/app/services/notify_dispatch.py b/app/services/notify_dispatch.py index adf29a9..2366cc6 100644 --- a/app/services/notify_dispatch.py +++ b/app/services/notify_dispatch.py @@ -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)