修复在线问题

This commit is contained in:
咕谷酱
2025-08-22 10:17:37 +08:00
parent 9293fe2eb0
commit eedc23fa7f
7 changed files with 290 additions and 51 deletions

View File

@@ -0,0 +1,74 @@
"""
在线状态维护服务
此模块提供在游玩状态下维护用户在线状态的功能,
解决游玩时显示离线的问题。
"""
from __future__ import annotations
import asyncio
from datetime import datetime, timedelta
from app.dependencies.database import get_redis
from app.log import logger
from app.router.v2.stats import REDIS_PLAYING_USERS_KEY, _redis_exec, get_redis_message
async def maintain_playing_users_online_status():
"""
维护正在游玩用户的在线状态
定期刷新正在游玩用户的metadata在线标记
确保他们在游玩过程中显示为在线状态。
"""
redis_sync = get_redis_message()
redis_async = get_redis()
try:
# 获取所有正在游玩的用户
playing_users = await _redis_exec(redis_sync.smembers, REDIS_PLAYING_USERS_KEY)
if not playing_users:
return
logger.debug(f"Maintaining online status for {len(playing_users)} playing users")
# 为每个游玩用户刷新metadata在线标记
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}"
# 设置或刷新metadata在线标记过期时间为1小时
await redis_async.set(metadata_key, "playing", ex=3600)
logger.debug(f"Updated metadata online status for {len(playing_users)} playing users")
except Exception as e:
logger.error(f"Error maintaining playing users online status: {e}")
async def start_online_status_maintenance_task():
"""
启动在线状态维护任务
每5分钟运行一次维护任务确保游玩用户保持在线状态
"""
logger.info("Starting online status maintenance task")
while True:
try:
await maintain_playing_users_online_status()
# 每5分钟运行一次
await asyncio.sleep(300)
except Exception as e:
logger.error(f"Error in online status maintenance task: {e}")
# 出错后等待30秒再重试
await asyncio.sleep(30)
def schedule_online_status_maintenance():
"""
调度在线状态维护任务
"""
task = asyncio.create_task(start_online_status_maintenance_task())
return task

View File

@@ -0,0 +1,136 @@
"""
在线状态管理服务
此模块负责统一管理用户的在线状态确保用户在连接WebSocket后立即显示为在线。
"""
from __future__ import annotations
import asyncio
from datetime import datetime
from app.dependencies.database import get_redis
from app.log import logger
from app.router.v2.stats import add_online_user
class OnlineStatusManager:
"""在线状态管理器"""
@staticmethod
async def set_user_online(user_id: int, hub_type: str = "general") -> None:
"""
设置用户为在线状态
Args:
user_id: 用户ID
hub_type: Hub类型 (metadata, spectator, multiplayer等)
"""
try:
redis = get_redis()
# 1. 添加到在线用户集合
await add_online_user(user_id)
# 2. 设置metadata在线标记这是is_online检查的关键
metadata_key = f"metadata:online:{user_id}"
await redis.set(metadata_key, hub_type, ex=7200) # 2小时过期
# 3. 设置最后活跃时间戳
last_seen_key = f"user:last_seen:{user_id}"
await redis.set(last_seen_key, int(datetime.utcnow().timestamp()), ex=7200)
logger.debug(f"[OnlineStatusManager] User {user_id} set online via {hub_type}")
except Exception as e:
logger.error(f"[OnlineStatusManager] Error setting user {user_id} online: {e}")
@staticmethod
async def refresh_user_online_status(user_id: int, hub_type: str = "active") -> None:
"""
刷新用户的在线状态
Args:
user_id: 用户ID
hub_type: 当前活动类型
"""
try:
redis = get_redis()
# 刷新metadata在线标记
metadata_key = f"metadata:online:{user_id}"
await redis.set(metadata_key, hub_type, ex=7200)
# 刷新最后活跃时间
last_seen_key = f"user:last_seen:{user_id}"
await redis.set(last_seen_key, int(datetime.utcnow().timestamp()), ex=7200)
logger.debug(f"[OnlineStatusManager] Refreshed online status for user {user_id}")
except Exception as e:
logger.error(f"[OnlineStatusManager] Error refreshing user {user_id} status: {e}")
@staticmethod
async def set_user_offline(user_id: int) -> None:
"""
设置用户为离线状态
Args:
user_id: 用户ID
"""
try:
redis = get_redis()
# 删除metadata在线标记
metadata_key = f"metadata:online:{user_id}"
await redis.delete(metadata_key)
# 从在线用户集合中移除
from app.router.v2.stats import remove_online_user
await remove_online_user(user_id)
logger.debug(f"[OnlineStatusManager] User {user_id} set offline")
except Exception as e:
logger.error(f"[OnlineStatusManager] Error setting user {user_id} offline: {e}")
@staticmethod
async def is_user_online(user_id: int) -> bool:
"""
检查用户是否在线
Args:
user_id: 用户ID
Returns:
bool: 用户是否在线
"""
try:
redis = get_redis()
metadata_key = f"metadata:online:{user_id}"
is_online = await redis.exists(metadata_key)
return bool(is_online)
except Exception as e:
logger.error(f"[OnlineStatusManager] Error checking user {user_id} online status: {e}")
return False
@staticmethod
async def get_online_users_count() -> int:
"""
获取在线用户数量
Returns:
int: 在线用户数量
"""
try:
from app.router.v2.stats import _get_online_users_count
from app.dependencies.database import get_redis
redis = get_redis()
return await _get_online_users_count(redis)
except Exception as e:
logger.error(f"[OnlineStatusManager] Error getting online users count: {e}")
return 0
# 单例实例
online_status_manager = OnlineStatusManager()

View File

@@ -48,17 +48,19 @@ async def cleanup_stale_online_users() -> tuple[int, int]:
online_cleaned = len(stale_online_users)
logger.info(f"Cleaned {online_cleaned} stale online users")
# 对于游玩用户,我们也检查对应的spectator状态
# 对于游玩用户,我们使用更保守的清理策略
# 只有当用户明确不在任何hub连接中时才移除
stale_playing_users = []
for user_id in playing_users:
user_id_str = (
user_id.decode() if isinstance(user_id, bytes) else str(user_id)
)
# 如果用户不在在线用户列表中,说明已经离线,也应该从游玩列表中移除
if user_id_str in stale_online_users or user_id_str not in [
u.decode() if isinstance(u, bytes) else str(u) for u in online_users
]:
metadata_key = f"metadata:online:{user_id_str}"
# 只有当metadata在线标记完全不存在且用户也不在在线列表中时
# 才认为用户真正离线
if (not await redis_async.exists(metadata_key) and
user_id_str not in [u.decode() if isinstance(u, bytes) else str(u) for u in online_users]):
stale_playing_users.append(user_id_str)
# 清理过期的游玩用户