"""告警相关 Celery 任务""" import traceback from datetime import datetime from sqlalchemy import func, case from app.core.celery_app import celery_app from app.core.database import SessionLocal @celery_app.task(queue='h3c_onu_ms', ignore_result=True) def check_school_offline_alerts(): """ 检查是否有学校全部离线(在线率为 0%),如有则发送企业微信告警。 该任务在每次全量状态检查完成后异步调用。 """ from app.models.device import ONUDevice, DeviceStatusHistory from app.services.wechat_service import send_wechat_markdown db = SessionLocal() try: # 每台设备最新状态的子查询 latest_subq = ( db.query( DeviceStatusHistory.onu_device_id, func.max(DeviceStatusHistory.checked_at).label("max_checked_at") ) .group_by(DeviceStatusHistory.onu_device_id) .subquery() ) latest_status_subq = ( db.query( DeviceStatusHistory.onu_device_id, DeviceStatusHistory.status ) .join( latest_subq, (DeviceStatusHistory.onu_device_id == latest_subq.c.onu_device_id) & (DeviceStatusHistory.checked_at == latest_subq.c.max_checked_at) ) .subquery() ) # 按学校聚合在线率,只查询在线数为 0 的学校 rows = ( db.query( ONUDevice.school_name, ONUDevice.region, func.count().label("total"), func.sum(case((latest_status_subq.c.status == 'online', 1), else_=0)).label("online"), ) .outerjoin(latest_status_subq, ONUDevice.id == latest_status_subq.c.onu_device_id) .group_by(ONUDevice.school_name, ONUDevice.region) .having( func.sum(case((latest_status_subq.c.status == 'online', 1), else_=0)) == 0 ) .all() ) if not rows: return {"alerted": False, "reason": "没有全离线的学校"} # 构造告警消息 now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") lines = [ "## [告警] 学校全部离线", f"> 检查时间:{now_str}", "> 以下学校所有设备均处于离线状态:", "", ] for row in rows: school = row.school_name or "未知" region = row.region or "未知" total = int(row.total or 0) if total > 0: lines.append(f"- **{school}**({region}): {total} 台设备全离线") content = "\n".join(lines) send_wechat_markdown(content) return {"alerted": True, "schools": len(rows)} except Exception as e: return {"alerted": False, "error": str(e), "traceback": traceback.format_exc()} finally: db.close()