From 3d8e270df4edcad68fa21495e8d9787e4ceae1c7 Mon Sep 17 00:00:00 2001 From: pllx Date: Mon, 27 Apr 2026 14:05:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=B0=E4=BA=BA=E8=BF=9B?= =?UTF-8?q?=E6=88=BF=E6=AC=A2=E8=BF=8E=E6=B6=88=E6=81=AF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ChatController.php | 13 ++++--- app/Services/ChatStateService.php | 5 +-- .../views/chat/partials/scripts.blade.php | 19 +++++++---- tests/Feature/ChatControllerTest.php | 34 +++++++++++++++++++ 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index c987fca..31fc088 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -127,14 +127,9 @@ class ChatController extends Controller // 必须在推送新消息之前执行,否则可能误删刚刚创建的欢迎播报 $this->chatState->removeOldWelcomeMessages($id, $user->username); - // 新人首次进入:赠送 6666 金币、播放满场烟花、发送全场欢迎通告 - // 双重校验:优先以 has_received_new_gift 标记为准,若标记异常则用 created_at 时间兜底 + // 新人首次进入:赠送 6666 金币、播放满场烟花、发送全场欢迎通告。 $user->refresh(); $isNewbie = ! $user->has_received_new_gift; - // 兜底:如果用户创建时间在 2 分钟内,即使标记异常也按新人处理 - if (! $isNewbie && $user->created_at && $user->created_at->diffInSeconds(now()) < 120) { - $isNewbie = true; - } if ($isNewbie) { // 通过统一积分服务发放新人礼包 6666 金币并记录流水 $this->currencyService->change( @@ -153,6 +148,7 @@ class ChatController extends Controller 'font_color' => '#b91c1c', 'action' => '', 'welcome_user' => $user->username, + 'welcome_kind' => 'newbie_bonus', 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage($id, $newbieMsg); @@ -178,11 +174,13 @@ class ChatController extends Controller 'id' => $this->chatState->nextMessageId($id), 'room_id' => $id, 'from_user' => 'AI小班长', - 'to_user' => $user->username, + 'to_user' => '大家', 'content' => $aiWelcomeContent, 'is_secret' => false, 'font_color' => '#16a34a', 'action' => '大声宣告', + 'welcome_user' => $user->username, + 'welcome_kind' => 'ai_newbie_welcome', 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage($id, $aiWelcomeMsg); @@ -204,6 +202,7 @@ class ChatController extends Controller 'font_color' => $color, 'action' => empty($vipPresencePayload) ? 'system_welcome' : 'vip_presence', 'welcome_user' => $user->username, + 'welcome_kind' => 'entry_broadcast', 'sent_at' => now()->toDateTimeString(), ]; diff --git a/app/Services/ChatStateService.php b/app/Services/ChatStateService.php index c33cbf2..b7c60a8 100644 --- a/app/Services/ChatStateService.php +++ b/app/Services/ChatStateService.php @@ -220,8 +220,9 @@ class ChatStateService foreach ($messages as $msgJson) { $msg = json_decode($msgJson, true); - // 只要消息里带了 welcome_user 且等于当前用户,就抛弃这条旧的 - if ($msg && isset($msg['welcome_user']) && $msg['welcome_user'] === $username) { + // 只清理普通进出播报,避免误删新人礼包公告和 AI 小班长新人欢迎。 + $welcomeKind = $msg['welcome_kind'] ?? 'entry_broadcast'; + if ($msg && isset($msg['welcome_user']) && $msg['welcome_user'] === $username && $welcomeKind === 'entry_broadcast') { continue; } $filtered[] = $msgJson; diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index ba85268..56e9bb8 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -2642,13 +2642,20 @@ // 后端下发的带有 welcome_user 的也是系统欢迎/离开消息,加上属性标记 if (msg.welcome_user) { + const welcomeKind = msg.welcome_kind || 'entry_broadcast'; div.setAttribute('data-system-user', msg.welcome_user); - // 收到后端来的新欢迎消息时,把界面上该用户旧的都删掉 - const welcomeSelector = `[data-system-user="${msg.welcome_user}"]`; - const oldWelcomes = container.querySelectorAll(welcomeSelector); - oldWelcomes.forEach(el => el.remove()); - renderBatch?.publicFragment.querySelectorAll(welcomeSelector).forEach(el => el.remove()); - renderBatch?.privateFragment.querySelectorAll(welcomeSelector).forEach(el => el.remove()); + div.setAttribute('data-system-welcome-kind', welcomeKind); + // 收到后端来的同类欢迎消息时,只替换同类旧消息,避免进场播报误删新人礼包和 AI 欢迎。 + const removeSameWelcome = (root) => { + root?.querySelectorAll('[data-system-user]').forEach(el => { + if (el.dataset.systemUser === msg.welcome_user && (el.dataset.systemWelcomeKind || 'entry_broadcast') === welcomeKind) { + el.remove(); + } + }); + }; + removeSameWelcome(container); + removeSameWelcome(renderBatch?.publicFragment); + removeSameWelcome(renderBatch?.privateFragment); } // 路由规则(复刻原版): diff --git a/tests/Feature/ChatControllerTest.php b/tests/Feature/ChatControllerTest.php index 79a6683..926b951 100644 --- a/tests/Feature/ChatControllerTest.php +++ b/tests/Feature/ChatControllerTest.php @@ -721,6 +721,8 @@ class ChatControllerTest extends TestCase $room = Room::create(['room_name' => 'annsafe']); $user = $this->createUserWithPositionPermissions([ PositionPermissionRegistry::ROOM_ANNOUNCEMENT, + ], [ + 'has_received_new_gift' => true, ]); $this->actingAs($user)->get(route('chat.room', $room->id)); @@ -992,6 +994,38 @@ class ChatControllerTest extends TestCase $this->assertStringContainsString($user->username, $presenceMessage['presence_text']); } + /** + * 测试新人首次进房时首屏历史包含礼包公告、AI 欢迎和普通进场播报。 + */ + public function test_newbie_first_join_keeps_bonus_ai_and_entry_welcome_messages(): void + { + $room = Room::create(['room_name' => 'newbie']); + $user = User::factory()->create([ + 'jjb' => 0, + 'has_received_new_gift' => false, + ]); + + $response = $this->actingAs($user)->get(route('chat.room', $room->id)); + + $response->assertOk(); + $history = collect($response->viewData('historyMessages')); + + $newbieBonusMessage = $history->first(fn (array $message): bool => ($message['welcome_kind'] ?? '') === 'newbie_bonus'); + $aiWelcomeMessage = $history->first(fn (array $message): bool => ($message['welcome_kind'] ?? '') === 'ai_newbie_welcome'); + $entryMessage = $history->first(fn (array $message): bool => ($message['welcome_kind'] ?? '') === 'entry_broadcast'); + + $this->assertNotNull($newbieBonusMessage); + $this->assertSame('系统公告', $newbieBonusMessage['from_user']); + $this->assertStringContainsString('6666 金币新人大礼包', $newbieBonusMessage['content']); + $this->assertNotNull($aiWelcomeMessage); + $this->assertSame('AI小班长', $aiWelcomeMessage['from_user']); + $this->assertSame('大家', $aiWelcomeMessage['to_user']); + $this->assertNotNull($entryMessage); + $this->assertSame($user->username, $entryMessage['welcome_user']); + $this->assertTrue($user->fresh()->has_received_new_gift); + $this->assertSame(6666, (int) $user->fresh()->jjb); + } + /** * 测试可以获取所有房间的在线人数状态。 */