修复自动存点挂机经验重复累加和离线僵尸吃经验的漏洞,完善对应单元测试
This commit is contained in:
@@ -87,14 +87,22 @@ class AutoSaveExp extends Command
|
||||
$totalProcessed = 0;
|
||||
$usersByUsername = $this->preloadOnlineUsers($roomMap);
|
||||
|
||||
$processedUsernames = [];
|
||||
|
||||
foreach ($roomMap as $roomId => $usernames) {
|
||||
foreach ($usernames as $username) {
|
||||
// 如果本轮已经处理过该用户,跳过发放经验(防止多房间/多开重复吃经验)
|
||||
if (isset($processedUsernames[$username])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = $usersByUsername->get($username);
|
||||
if (! $user) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->processUser($user, $roomId, $expGainRaw, $jjbGainRaw, $superLevel);
|
||||
$processedUsernames[$username] = true;
|
||||
$totalProcessed++;
|
||||
}
|
||||
}
|
||||
@@ -125,7 +133,19 @@ class AutoSaveExp extends Command
|
||||
// Laravel 的 Redis facade 会自动加配置的前缀,与 ChatStateService 存入时完全一致
|
||||
$usernames = Redis::hkeys("room:{$roomId}:users");
|
||||
if (! empty($usernames)) {
|
||||
$roomMap[(int) $roomId] = $usernames;
|
||||
$activeUsernames = [];
|
||||
foreach ($usernames as $username) {
|
||||
// 检查活跃标记是否存在(90秒内)
|
||||
if (Redis::exists("room:{$roomId}:alive:{$username}")) {
|
||||
$activeUsernames[] = $username;
|
||||
} else {
|
||||
// 懒清理僵尸数据
|
||||
Redis::hdel("room:{$roomId}:users", $username);
|
||||
}
|
||||
}
|
||||
if (! empty($activeUsernames)) {
|
||||
$roomMap[(int) $roomId] = $activeUsernames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ class AutoSaveExpCommandTest extends TestCase
|
||||
]);
|
||||
|
||||
Redis::hset("room:{$room->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
|
||||
Redis::setex("room:{$room->id}:alive:{$user->username}", 90, 1);
|
||||
|
||||
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
|
||||
|
||||
@@ -98,4 +99,60 @@ class AutoSaveExpCommandTest extends TestCase
|
||||
$this->assertStringContainsString('部门 办公厅 · 职务 🏛️ 厅长 · 会员 👑 至尊会员', $notice['content']);
|
||||
$this->assertStringContainsString('LV.100 · 经验 168762 · 金币 1100017 · 已满级 ✓', $notice['content']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试自动存点命令会清理掉线不活跃的用户(懒清理)并不发放奖励。
|
||||
*/
|
||||
public function test_auto_save_cleans_up_inactive_zombie_users(): void
|
||||
{
|
||||
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '5']);
|
||||
Cache::flush();
|
||||
|
||||
$room = Room::create(['room_name' => 'zmb']);
|
||||
$user = User::factory()->create([
|
||||
'username' => 'zombie-user',
|
||||
'exp_num' => 10,
|
||||
]);
|
||||
|
||||
// 只把用户加入哈希表,但不设置 alive 活跃键
|
||||
Redis::hset("room:{$room->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
|
||||
|
||||
// 验证用户已经被从房间在线哈希表中懒清理剔除
|
||||
$this->assertFalse((bool) Redis::hexists("room:{$room->id}:users", $user->username));
|
||||
|
||||
// 验证用户的经验没有增加(仍然是 10)
|
||||
$user->refresh();
|
||||
$this->assertEquals(10, $user->exp_num);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试自动存点命令会全局去重,防止玩家在多房间同时挂机时重复吃挂机奖励。
|
||||
*/
|
||||
public function test_auto_save_deduplicates_multiple_rooms_rewards(): void
|
||||
{
|
||||
Sysparam::updateOrCreate(['alias' => 'exp_per_heartbeat'], ['body' => '5']);
|
||||
Cache::flush();
|
||||
|
||||
$room1 = Room::create(['room_name' => 'r1']);
|
||||
$room2 = Room::create(['room_name' => 'r2']);
|
||||
$user = User::factory()->create([
|
||||
'username' => 'multi-room-user',
|
||||
'exp_num' => 10,
|
||||
]);
|
||||
|
||||
// 玩家同时在两个房间在线,且两边都有心跳活跃标记
|
||||
Redis::hset("room:{$room1->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
|
||||
Redis::setex("room:{$room1->id}:alive:{$user->username}", 90, 1);
|
||||
|
||||
Redis::hset("room:{$room2->id}:users", $user->username, json_encode(['username' => $user->username], JSON_UNESCAPED_UNICODE));
|
||||
Redis::setex("room:{$room2->id}:alive:{$user->username}", 90, 1);
|
||||
|
||||
$this->artisan('chatroom:auto-save-exp')->assertSuccessful();
|
||||
|
||||
// 验证用户只被加了一次经验(10 + 5 = 15,而不是 10 + 5 + 5 = 20)
|
||||
$user->refresh();
|
||||
$this->assertEquals(15, $user->exp_num);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user