80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
|
|
"""离线检测定时调度器
|
|||
|
|
|
|||
|
|
使用 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)
|