'exp_per_heartbeat'], ['body' => '0']); Sysparam::updateOrCreate(['alias' => 'jjb_per_heartbeat'], ['body' => '0']); Sysparam::updateOrCreate(['alias' => 'superlevel'], ['body' => '100']); Cache::flush(); $room = Room::create(['room_name' => 'asave']); $vipLevel = VipLevel::factory()->create([ 'name' => '至尊会员', 'icon' => '👑', ]); $user = User::factory()->create([ 'username' => 'autosave-user', 'user_level' => 100, 'exp_num' => 168762, 'jjb' => 1100017, 'vip_level_id' => $vipLevel->id, 'hy_time' => now()->addDay(), ]); $department = Department::create([ 'name' => '办公厅', 'rank' => 100, 'color' => '#1d4ed8', 'sort_order' => 1, ]); $position = Position::create([ 'department_id' => $department->id, 'name' => '厅长', 'icon' => '🏛️', 'rank' => 100, 'level' => 100, 'sort_order' => 1, ]); UserPosition::create([ 'user_id' => $user->id, 'position_id' => $position->id, 'appointed_at' => now(), 'is_active' => true, ]); 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(); $messages = collect(Redis::lrange("room:{$room->id}:messages", 0, -1)) ->map(fn (string $message): array => json_decode($message, true)); $notice = $messages->first(fn (array $message): bool => ($message['to_user'] ?? '') === $user->username); $this->assertIsArray($notice); $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); } }