修复在线问题

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

@@ -14,6 +14,7 @@ from app.database.playlists import Playlist
from app.database.room import Room
from app.database.score import Score
from app.dependencies.database import get_redis, with_db
from app.log import logger
from app.models.metadata_hub import (
TOTAL_SCORE_DISTRIBUTION_BINS,
DailyChallengeInfo,
@@ -93,16 +94,13 @@ class MetadataHub(Hub[MetadataClientState]):
async def _clean_state(self, state: MetadataClientState) -> None:
user_id = int(state.connection_id)
# Remove from online user tracking
from app.router.v2.stats import remove_online_user
asyncio.create_task(remove_online_user(user_id))
# Use centralized offline status management
from app.service.online_status_manager import online_status_manager
await online_status_manager.set_user_offline(user_id)
if state.pushable:
await asyncio.gather(*self.broadcast_tasks(user_id, None))
redis = get_redis()
if await redis.exists(f"metadata:online:{state.connection_id}"):
await redis.delete(f"metadata:online:{state.connection_id}")
async with with_db() as session:
async with session.begin():
user = (
@@ -122,12 +120,16 @@ class MetadataHub(Hub[MetadataClientState]):
async def on_client_connect(self, client: Client) -> None:
user_id = int(client.connection_id)
self.get_or_create_state(client)
store = self.get_or_create_state(client)
# Track online user
from app.router.v2.stats import add_online_user
# Use centralized online status management
from app.service.online_status_manager import online_status_manager
await online_status_manager.set_user_online(user_id, "metadata")
asyncio.create_task(add_online_user(user_id))
# CRITICAL FIX: Set online status IMMEDIATELY upon connection
# This matches the C# official implementation behavior
store.status = OnlineStatus.ONLINE
logger.info(f"[MetadataHub] Set user {user_id} status to ONLINE upon connection")
async with with_db() as session:
async with session.begin():
@@ -175,8 +177,23 @@ class MetadataHub(Hub[MetadataClientState]):
room_id=daily_challenge_room.id,
),
)
redis = get_redis()
await redis.set(f"metadata:online:{user_id}", "")
# CRITICAL FIX: Immediately broadcast the user's online status to all watchers
# This ensures the user appears as "currently online" right after connection
# Similar to the C# implementation's immediate broadcast logic
online_presence_tasks = self.broadcast_tasks(user_id, store)
if online_presence_tasks:
await asyncio.gather(*online_presence_tasks)
logger.info(f"[MetadataHub] Broadcasted online status for user {user_id} to watchers")
# Also send the user's own presence update to confirm online status
await self.call_noblock(
client,
"UserPresenceUpdated",
user_id,
store.for_push,
)
logger.info(f"[MetadataHub] User {user_id} is now ONLINE and visible to other clients")
async def UpdateStatus(self, client: Client, status: int) -> None:
status_ = OnlineStatus(status)
@@ -214,19 +231,22 @@ class MetadataHub(Hub[MetadataClientState]):
await asyncio.gather(*tasks)
async def BeginWatchingUserPresence(self, client: Client) -> None:
# Critical fix: Send all currently online users to the new watcher
# Must use for_push to get the correct UserPresence format
await asyncio.gather(
*[
self.call_noblock(
client,
"UserPresenceUpdated",
user_id,
store,
store.for_push, # Fixed: use for_push instead of store
)
for user_id, store in self.state.items()
if store.pushable
]
)
self.add_to_group(client, self.online_presence_watchers_group())
logger.info(f"[MetadataHub] Client {client.connection_id} now watching user presence, sent {len([s for s in self.state.values() if s.pushable])} online users")
async def EndWatchingUserPresence(self, client: Client) -> None:
self.remove_from_group(client, self.online_presence_watchers_group())

View File

@@ -164,10 +164,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
async def _clean_state(self, state: MultiplayerClientState):
user_id = int(state.connection_id)
# Remove from online user tracking
from app.router.v2.stats import remove_online_user
asyncio.create_task(remove_online_user(user_id))
# Use centralized offline status management
from app.service.online_status_manager import online_status_manager
await online_status_manager.set_user_offline(user_id)
if state.room_id != 0 and state.room_id in self.rooms:
server_room = self.rooms[state.room_id]
@@ -182,10 +181,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
"""Track online users when connecting to multiplayer hub"""
logger.info(f"[MultiplayerHub] Client {client.user_id} connected")
# Track online user
from app.router.v2.stats import add_online_user
asyncio.create_task(add_online_user(client.user_id))
# Use centralized online status management
from app.service.online_status_manager import online_status_manager
await online_status_manager.set_user_online(client.user_id, "multiplayer")
def _ensure_in_room(self, client: Client) -> ServerMultiplayerRoom:
store = self.get_or_create_state(client)

View File

@@ -171,10 +171,9 @@ class SpectatorHub(Hub[StoreClientState]):
"""
user_id = int(state.connection_id)
# Remove from online and playing tracking
from app.router.v2.stats import remove_online_user
asyncio.create_task(remove_online_user(user_id))
# Use centralized offline status management
from app.service.online_status_manager import online_status_manager
await online_status_manager.set_user_offline(user_id)
if state.state:
await self._end_session(user_id, state.state, state)
@@ -197,10 +196,9 @@ class SpectatorHub(Hub[StoreClientState]):
"""
logger.info(f"[SpectatorHub] Client {client.user_id} connected")
# Track online user
from app.router.v2.stats import add_online_user
asyncio.create_task(add_online_user(client.user_id))
# Use centralized online status management
from app.service.online_status_manager import online_status_manager
await online_status_manager.set_user_online(client.user_id, "spectator")
# Send all current player states to the new client
# This matches the official OnConnectedAsync behavior
@@ -269,7 +267,7 @@ class SpectatorHub(Hub[StoreClientState]):
# Critical addition: Notify about finished players in multiplayer games
elif (
room_user.state == MultiplayerUserState.RESULTS
hasattr(room_user.state, 'name') and room_user.state.name == 'RESULTS'
and room_user.user_id not in self.state
):
try:
@@ -340,10 +338,15 @@ class SpectatorHub(Hub[StoreClientState]):
)
logger.info(f"[SpectatorHub] {client.user_id} began playing {state.beatmap_id}")
# Track playing user
# Track playing user and maintain online status
from app.router.v2.stats import add_playing_user
from app.service.online_status_manager import online_status_manager
asyncio.create_task(add_playing_user(user_id))
# Critical fix: Maintain metadata online presence during gameplay
# This ensures the user appears online while playing
await online_status_manager.refresh_user_online_status(user_id, "playing")
# # 预缓存beatmap文件以加速后续PP计算
# await self._preload_beatmap_for_pp_calculation(state.beatmap_id)
@@ -357,21 +360,25 @@ class SpectatorHub(Hub[StoreClientState]):
async def SendFrameData(self, client: Client, frame_data: FrameDataBundle) -> None:
user_id = int(client.connection_id)
state = self.get_or_create_state(client)
if not state.score:
store = self.get_or_create_state(client)
if store.state is None or store.score is None:
return
state.score.score_info.accuracy = frame_data.header.accuracy
state.score.score_info.combo = frame_data.header.combo
state.score.score_info.max_combo = frame_data.header.max_combo
state.score.score_info.statistics = frame_data.header.statistics
state.score.score_info.total_score = frame_data.header.total_score
state.score.score_info.mods = frame_data.header.mods
state.score.replay_frames.extend(frame_data.frames)
# Critical fix: Refresh online status during active gameplay
# This prevents users from appearing offline while playing
from app.service.online_status_manager import online_status_manager
await online_status_manager.refresh_user_online_status(user_id, "playing_active")
header = frame_data.header
score_info = store.score.score_info
score_info.accuracy = header.accuracy
score_info.combo = header.combo
score_info.max_combo = header.max_combo
score_info.statistics = header.statistics
store.score.replay_frames.extend(frame_data.frames)
await self.broadcast_group_call(
self.group_id(user_id),
"UserSentFrames",
user_id,
frame_data,
self.group_id(user_id), "UserSentFrames", user_id, frame_data
)
async def EndPlaySession(self, client: Client, state: SpectatorState) -> None: