修复聊天室离开播报:显式点击离开按钮时绕过队列防抖,同步发送离开广播,解决本地无队列运行时播报丢失的问题
This commit is contained in:
@@ -184,6 +184,17 @@ class AuthController extends Controller
|
||||
'out_time' => now(),
|
||||
'out_info' => '正常退出了聊天室',
|
||||
]);
|
||||
|
||||
// [NEW] 同步清除该用户在所有房间的在线状态和心跳,确保其如果马上重登,能触发全新入场欢迎
|
||||
try {
|
||||
$chatState = app(\App\Services\ChatStateService::class);
|
||||
$roomIds = $chatState->getUserRooms($user->username);
|
||||
foreach ($roomIds as $roomId) {
|
||||
$chatState->userLeave($roomId, $user->username);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 忽略清理缓存时发生的异常
|
||||
}
|
||||
}
|
||||
|
||||
Auth::logout();
|
||||
|
||||
@@ -69,7 +69,17 @@ class ChatController extends Controller
|
||||
$user->update(['in_time' => now()]);
|
||||
|
||||
// 0. 判断是否已经是当前房间的在线状态
|
||||
$isAlreadyInRoom = $this->chatState->isUserInRoom($id, $user->username);
|
||||
$hasKey = $this->chatState->isUserInRoom($id, $user->username);
|
||||
// 增强校验:判断心跳是否还存在。如果遇到没有启动队列任务的情况,离线任务未能清理脏数据,心跳必定过期。
|
||||
$isHeartbeatAlive = (bool) \Illuminate\Support\Facades\Redis::exists("room:{$id}:alive:{$user->username}");
|
||||
|
||||
// 如果虽然在名单里,但心跳早已丢失(可能直接关浏览器且队列未跑),视为全新进房
|
||||
if ($hasKey && ! $isHeartbeatAlive) {
|
||||
$this->chatState->userLeave($id, $user->username); // 强制洗净状态
|
||||
$hasKey = false;
|
||||
}
|
||||
|
||||
$isAlreadyInRoom = $hasKey;
|
||||
|
||||
// 1. 先将用户从其他所有房间的在线名单中移除(切换房间时旧记录自动清理)
|
||||
// 避免直接跳转页面时 leave 接口未触发导致"幽灵在线"问题
|
||||
@@ -180,7 +190,8 @@ class ChatController extends Controller
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
$this->chatState->pushMessage($id, $generalWelcomeMsg);
|
||||
broadcast(new MessageSent($id, $generalWelcomeMsg))->toOthers();
|
||||
// 修复:之前使用了 ->toOthers() 导致自己看不到自己的进场提示
|
||||
broadcast(new MessageSent($id, $generalWelcomeMsg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,13 +203,6 @@ class ChatController extends Controller
|
||||
$toUser = $msg['to_user'] ?? '';
|
||||
$fromUser = $msg['from_user'] ?? '';
|
||||
$isSecret = ! empty($msg['is_secret']);
|
||||
$action = $msg['action'] ?? '';
|
||||
$welcomeUser = $msg['welcome_user'] ?? '';
|
||||
|
||||
// 过滤自己的进出场提示,避免自己被自己刷屏
|
||||
if (($action === 'system_welcome' || $action === 'admin_welcome' || empty($action)) && $welcomeUser === $username) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 公众发言(对大家说):所有人都可以看到
|
||||
if ($toUser === '大家' || $toUser === '') {
|
||||
@@ -610,11 +614,23 @@ class ChatController extends Controller
|
||||
return response()->json(['status' => 'error'], 401);
|
||||
}
|
||||
|
||||
// 不立刻执行离线逻辑,而是给个 3 秒的防抖延迟
|
||||
// 这样如果用户只是刷新页面,很快在 init 中又会重新加入房间(记录的 join_time 会大于当前 leave 时的 leaveTime)
|
||||
// Job 中就不会执行完整的离线播报和注销流程
|
||||
$leaveTime = microtime(true);
|
||||
\App\Jobs\ProcessUserLeave::dispatch($id, clone $user, $leaveTime)->delay(now()->addSeconds(3));
|
||||
$isExplicit = strval($request->query('explicit')) === '1';
|
||||
|
||||
if ($isExplicit) {
|
||||
// 人工显式点击“离开”,不再进行浏览器刷新的防抖,直接同步执行清算和播报。
|
||||
// 这对本地没有开启 Queue Worker 的环境尤为重要,能保证大家立刻看到消息。
|
||||
// 为了防止 ProcessUserLeave 中的时间对比失败,我们直接删掉 join_time 表示彻底离线。
|
||||
\Illuminate\Support\Facades\Redis::del("room:{$id}:join_time:{$user->username}");
|
||||
|
||||
$job = new \App\Jobs\ProcessUserLeave($id, clone $user, $leaveTime);
|
||||
dispatch_sync($job);
|
||||
} else {
|
||||
// 不立刻执行离线逻辑,而是给个 3 秒的防抖延迟
|
||||
// 这样如果用户只是刷新页面,很快在 init 中又会重新加入房间(记录的 join_time 会大于当前 leave 时的 leaveTime)
|
||||
// Job 中就不会执行完整的离线播报和注销流程
|
||||
\App\Jobs\ProcessUserLeave::dispatch($id, clone $user, $leaveTime)->delay(now()->addSeconds(3));
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
@@ -235,11 +235,11 @@
|
||||
* 优先级:如果有新人礼包特效,优先播放新人大礼包;如果没有,再播放周卡特效
|
||||
*/
|
||||
setTimeout(() => {
|
||||
if (window.EffectManager) {
|
||||
if (typeof EffectManager !== 'undefined') {
|
||||
@if (!empty($newbieEffect))
|
||||
window.EffectManager.play('{{ $newbieEffect }}');
|
||||
EffectManager.play('{{ $newbieEffect }}');
|
||||
@elseif (!empty($weekEffect))
|
||||
window.EffectManager.play('{{ $weekEffect }}');
|
||||
EffectManager.play('{{ $weekEffect }}');
|
||||
@endif
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
@@ -1413,7 +1413,7 @@
|
||||
clearTimeout(visibilityTimer);
|
||||
|
||||
try {
|
||||
await fetch(window.chatContext.leaveUrl, {
|
||||
await fetch(window.chatContext.leaveUrl + '?explicit=1', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
|
||||
|
||||
Reference in New Issue
Block a user