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)
|