model_train_dm/backend/apps/cloud_terminal/scheduler.py

80 lines
2.4 KiB
Python
Raw Normal View History

2026-07-27 17:51:49 +08:00
"""离线检测定时调度器
使用 APScheduler Django 进程中启动后台线程 30 秒检测一次终端心跳超时
"""
import logging
from apscheduler.schedulers.background import BackgroundScheduler
from django.utils.timezone import now
logger = logging.getLogger(__name__)
CHECK_INTERVAL_SECONDS = 30 # 检测间隔30 秒
_scheduler: BackgroundScheduler | None = None
def _check_offline_terminals():
"""检测超时心跳的终端(3 分钟内未心跳),标记为离线并推送 WebSocket 通知"""
from datetime import timedelta
from apps.core.models import AiSmartTerminal
HEARTBEAT_TIMEOUT = timedelta(minutes=3)
threshold = now() - HEARTBEAT_TIMEOUT
offline_terminals = AiSmartTerminal.objects.filter(
online_status=1,
last_heartbeat_time__isnull=False,
last_heartbeat_time__lt=threshold,
)
if not offline_terminals.exists():
return
offline_list = list(offline_terminals.values("stcd", "stnm"))
count = offline_terminals.update(online_status=0)
logger.warning("标记 %d 个终端为离线:", count)
for t in offline_list:
logger.warning(" - %s (%s)", t["stcd"], t["stnm"])
# WebSocket 推送离线通知
try:
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
if channel_layer is not None:
async_to_sync(channel_layer.group_send)(
"terminal_status",
{
"type": "terminal.status",
"data": {
"event": "offline",
"count": count,
"terminals": offline_list,
},
},
)
except Exception:
logger.exception("WebSocket 推送失败")
def start_offline_checker():
"""启动离线检测后台调度器(仅在主进程中启动一次)"""
global _scheduler
if _scheduler is not None:
return # 已启动,避免重复
_scheduler = BackgroundScheduler(daemon=True)
_scheduler.add_job(
_check_offline_terminals,
"interval",
seconds=CHECK_INTERVAL_SECONDS,
id="check_terminal_offline",
replace_existing=True,
)
_scheduler.start()
logger.info("离线检测调度器已启动,每 %d 秒检测一次", CHECK_INTERVAL_SECONDS)