Fix: 彻底抛弃Redis SCAN方案,改用Room::pluck+hkeys精准读取在线用户,解决predis前缀导致扫描失败的问题

This commit is contained in:
2026-02-27 12:48:23 +08:00
parent 2bbb13e85b
commit eb7bc58417

View File

@@ -87,32 +87,28 @@ class AutoSaveExp extends Command
}
/**
* 扫描 Redis获取所有活跃房间及其在线用户列表。
* 查询所有活跃房间及其在线用户列表。
*
* 改为从数据库获取所有房间,再用 Redis::hkeys() 查询在线用户。
* 这样可以避免 Redis SCAN + 前缀匹配不一致的问题,
* Redis::hkeys() 会自动正确地加上前缀,与 ChatStateService::userJoin() 一致。
*
* @return array<int, array<string>> 格式:[房间ID => [用户名, ...]]
*/
private function scanOnlineRooms(): array
{
$roomMap = [];
$cursor = '0';
// 获取原始 Redis 客户端(不自动加前缀),用于直接操作完整 key 名
$rawClient = Redis::connection()->client();
// 从数据库取出所有房间 ID
$roomIds = \App\Models\Room::pluck('id');
do {
[$cursor, $keys] = Redis::scan($cursor, ['match' => '*room:*:users', 'count' => 100]);
foreach ($keys ?? [] as $key) {
// 从 key 中提取房间 ID支持带前缀的格式如 chatroom-database-room:1:users
if (preg_match('/room:(\d+):users/', $key, $m)) {
$roomId = (int) $m[1];
// 直接用原始客户端查找,避免 Laravel 自动加前缀导致 key 被拼接两次
$usernames = $rawClient->hkeys($key);
if (! empty($usernames)) {
$roomMap[$roomId] = $usernames;
}
}
foreach ($roomIds as $roomId) {
// Laravel 的 Redis facade 会自动加配置的前缀,与 ChatStateService 存入时完全一致
$usernames = Redis::hkeys("room:{$roomId}:users");
if (! empty($usernames)) {
$roomMap[(int) $roomId] = $usernames;
}
} while ($cursor !== '0');
}
return $roomMap;
}