feat(alerts): 添加微信告警、WebSocket实时推送和地图功能
- 新增微信告警服务(wechat_service)和告警任务(alert_tasks) - 新增 WebSocket 实时推送端点 - 新增监控管理模块(monitor) - 增强统计仪表板:趋势图、区域分布、光功率历史 - 设备管理:添加坐标信息、标签系统、复合索引优化 - 前端:重构Dashboard/Charts页面,新增业务组件 - 新增5个数据库迁移(坐标、复合索引、标签、光功率历史、显示名) - 更新部署配置和脚本 - 新增测试框架基础结构
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""告警相关 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 = [
|
||||
"## <font color=\"warning\">[告警] 学校全部离线</font>",
|
||||
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()
|
||||
Reference in New Issue
Block a user