197 lines
7.6 KiB
Python
197 lines
7.6 KiB
Python
"""
|
||
实时在线状态清理服务
|
||
|
||
此模块提供实时的在线状态清理功能,确保用户在断开连接后立即从在线列表中移除。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import json
|
||
from datetime import datetime, timedelta
|
||
|
||
from app.dependencies.database import get_redis, get_redis_message
|
||
from app.log import logger
|
||
from app.router.v2.stats import (
|
||
REDIS_ONLINE_USERS_KEY,
|
||
REDIS_PLAYING_USERS_KEY,
|
||
_redis_exec,
|
||
)
|
||
|
||
|
||
class RealtimeOnlineCleanup:
|
||
"""实时在线状态清理器"""
|
||
|
||
def __init__(self):
|
||
self._running = False
|
||
self._task = None
|
||
|
||
async def start(self):
|
||
"""启动实时清理服务"""
|
||
if self._running:
|
||
return
|
||
|
||
self._running = True
|
||
self._task = asyncio.create_task(self._realtime_cleanup_loop())
|
||
logger.info("[RealtimeOnlineCleanup] Started realtime online cleanup service")
|
||
|
||
async def stop(self):
|
||
"""停止实时清理服务"""
|
||
if not self._running:
|
||
return
|
||
|
||
self._running = False
|
||
if self._task:
|
||
self._task.cancel()
|
||
try:
|
||
await self._task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
logger.info("[RealtimeOnlineCleanup] Stopped realtime online cleanup service")
|
||
|
||
async def _realtime_cleanup_loop(self):
|
||
"""实时清理循环 - 每30秒检查一次"""
|
||
while self._running:
|
||
try:
|
||
# 执行快速清理
|
||
cleaned_count = await self._quick_cleanup_stale_users()
|
||
if cleaned_count > 0:
|
||
logger.debug(f"[RealtimeOnlineCleanup] Quick cleanup: removed {cleaned_count} stale users")
|
||
|
||
# 等待30秒
|
||
await asyncio.sleep(30)
|
||
|
||
except asyncio.CancelledError:
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"[RealtimeOnlineCleanup] Error in cleanup loop: {e}")
|
||
# 出错时等待30秒再重试
|
||
await asyncio.sleep(30)
|
||
|
||
async def _quick_cleanup_stale_users(self) -> int:
|
||
"""快速清理过期用户,返回清理数量"""
|
||
redis_sync = get_redis_message()
|
||
redis_async = get_redis()
|
||
|
||
total_cleaned = 0
|
||
|
||
try:
|
||
# 获取所有在线用户
|
||
online_users = await _redis_exec(redis_sync.smembers, REDIS_ONLINE_USERS_KEY)
|
||
|
||
# 快速检查:只检查metadata键是否存在
|
||
stale_users = []
|
||
for user_id in online_users:
|
||
user_id_str = user_id.decode() if isinstance(user_id, bytes) else str(user_id)
|
||
metadata_key = f"metadata:online:{user_id_str}"
|
||
|
||
# 如果metadata标记不存在,立即标记为过期
|
||
if not await redis_async.exists(metadata_key):
|
||
stale_users.append(user_id_str)
|
||
|
||
# 立即清理过期用户
|
||
if stale_users:
|
||
# 从在线用户集合中移除
|
||
await _redis_exec(redis_sync.srem, REDIS_ONLINE_USERS_KEY, *stale_users)
|
||
# 同时从游玩用户集合中移除(如果存在)
|
||
await _redis_exec(redis_sync.srem, REDIS_PLAYING_USERS_KEY, *stale_users)
|
||
total_cleaned = len(stale_users)
|
||
|
||
logger.info(f"[RealtimeOnlineCleanup] Immediately cleaned {total_cleaned} stale users")
|
||
|
||
except Exception as e:
|
||
logger.error(f"[RealtimeOnlineCleanup] Error in quick cleanup: {e}")
|
||
|
||
return total_cleaned
|
||
|
||
async def verify_user_online_status(self, user_id: int) -> bool:
|
||
"""实时验证用户在线状态"""
|
||
try:
|
||
redis = get_redis()
|
||
metadata_key = f"metadata:online:{user_id}"
|
||
|
||
# 检查metadata键是否存在
|
||
exists = await redis.exists(metadata_key)
|
||
|
||
# 如果不存在,从在线集合中移除该用户
|
||
if not exists:
|
||
redis_sync = get_redis_message()
|
||
await _redis_exec(redis_sync.srem, REDIS_ONLINE_USERS_KEY, str(user_id))
|
||
await _redis_exec(redis_sync.srem, REDIS_PLAYING_USERS_KEY, str(user_id))
|
||
logger.debug(f"[RealtimeOnlineCleanup] Verified user {user_id} is offline, removed from sets")
|
||
|
||
return bool(exists)
|
||
|
||
except Exception as e:
|
||
logger.error(f"[RealtimeOnlineCleanup] Error verifying user {user_id} status: {e}")
|
||
return False
|
||
|
||
async def force_refresh_online_list(self):
|
||
"""强制刷新整个在线用户列表"""
|
||
try:
|
||
redis_sync = get_redis_message()
|
||
redis_async = get_redis()
|
||
|
||
# 获取所有在线用户
|
||
online_users = await _redis_exec(redis_sync.smembers, REDIS_ONLINE_USERS_KEY)
|
||
playing_users = await _redis_exec(redis_sync.smembers, REDIS_PLAYING_USERS_KEY)
|
||
|
||
# 验证每个用户的在线状态
|
||
valid_online_users = []
|
||
valid_playing_users = []
|
||
|
||
for user_id in online_users:
|
||
user_id_str = user_id.decode() if isinstance(user_id, bytes) else str(user_id)
|
||
metadata_key = f"metadata:online:{user_id_str}"
|
||
|
||
if await redis_async.exists(metadata_key):
|
||
valid_online_users.append(user_id_str)
|
||
|
||
for user_id in playing_users:
|
||
user_id_str = user_id.decode() if isinstance(user_id, bytes) else str(user_id)
|
||
metadata_key = f"metadata:online:{user_id_str}"
|
||
|
||
if await redis_async.exists(metadata_key):
|
||
valid_playing_users.append(user_id_str)
|
||
|
||
# 重建在线用户集合
|
||
if online_users:
|
||
await _redis_exec(redis_sync.delete, REDIS_ONLINE_USERS_KEY)
|
||
if valid_online_users:
|
||
await _redis_exec(redis_sync.sadd, REDIS_ONLINE_USERS_KEY, *valid_online_users)
|
||
# 设置过期时间
|
||
await redis_async.expire(REDIS_ONLINE_USERS_KEY, 3 * 3600)
|
||
|
||
# 重建游玩用户集合
|
||
if playing_users:
|
||
await _redis_exec(redis_sync.delete, REDIS_PLAYING_USERS_KEY)
|
||
if valid_playing_users:
|
||
await _redis_exec(redis_sync.sadd, REDIS_PLAYING_USERS_KEY, *valid_playing_users)
|
||
# 设置过期时间
|
||
await redis_async.expire(REDIS_PLAYING_USERS_KEY, 3 * 3600)
|
||
|
||
cleaned_online = len(online_users) - len(valid_online_users)
|
||
cleaned_playing = len(playing_users) - len(valid_playing_users)
|
||
|
||
if cleaned_online > 0 or cleaned_playing > 0:
|
||
logger.info(f"[RealtimeOnlineCleanup] Force refresh: removed {cleaned_online} stale online users, {cleaned_playing} stale playing users")
|
||
|
||
return cleaned_online + cleaned_playing
|
||
|
||
except Exception as e:
|
||
logger.error(f"[RealtimeOnlineCleanup] Error in force refresh: {e}")
|
||
return 0
|
||
|
||
|
||
# 全局实例
|
||
realtime_cleanup = RealtimeOnlineCleanup()
|
||
|
||
|
||
def start_realtime_cleanup():
|
||
"""启动实时清理服务"""
|
||
asyncio.create_task(realtime_cleanup.start())
|
||
|
||
|
||
def stop_realtime_cleanup():
|
||
"""停止实时清理服务"""
|
||
asyncio.create_task(realtime_cleanup.stop())
|