Files
chatroom/app/Jobs/ProcessUserLeave.php

136 lines
5.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:用户离开聊天室后的异步清理与播报任务
* 负责清理在线状态、关闭勤务日志,并根据会员/管理员身份发送不同的离场提示。
*/
namespace App\Jobs;
use App\Models\PositionDutyLog;
use App\Models\Sysparam;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\RoomBroadcastService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
class ProcessUserLeave implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 构造离场处理任务实例。
*/
public function __construct(
public int $roomId,
public User $user,
public float $leaveTime
) {}
/**
* 执行离场任务:清理在线状态并广播离场消息。
*/
public function handle(ChatStateService $chatState, RoomBroadcastService $broadcast): void
{
// 获取该用户最后一次进入房间的时间
$lastJoinTime = (float) Redis::get("room:{$this->roomId}:join_time:{$this->user->username}");
// 如果最后一次加入的时间 > 当前离线任务产生的时间,说明用户又刷新重新进来了
if ($lastJoinTime >= $this->leaveTime) {
return;
}
// 1. 从 Redis 删除该用户
$chatState->userLeave($this->roomId, $this->user->username);
// 记录退出时间和退出信息
$this->user->update([
'out_time' => now(),
'out_info' => '正常退出了房间',
]);
// 关闭该用户尚未结束的在职登录记录(结算在线时长)
$this->closeDutyLog($this->user->id);
// 2. 发送离场播报
$superLevel = (int) Sysparam::getValue('superlevel', '100');
if ($this->user->user_level >= $superLevel) {
// 管理员离场:系统公告
$leaveMsg = [
'id' => $chatState->nextMessageId($this->roomId),
'room_id' => $this->roomId,
'from_user' => '系统公告',
'to_user' => '大家',
'content' => "👋 管理员 【{$this->user->username}】 已离开聊天室。",
'is_secret' => false,
'font_color' => '#b91c1c',
'action' => 'admin_welcome',
'welcome_user' => $this->user->username,
'sent_at' => now()->toDateTimeString(),
];
} else {
[$leaveText, $color] = $broadcast->buildLeaveBroadcast($this->user);
$vipPresencePayload = $broadcast->buildVipPresencePayload($this->user, 'leave');
$leaveMsg = [
'id' => $chatState->nextMessageId($this->roomId),
'room_id' => $this->roomId,
'from_user' => '进出播报',
'to_user' => '大家',
'content' => "<span style=\"color: {$color}; font-weight: bold;\">{$leaveText}</span>",
'is_secret' => false,
'font_color' => $color,
'action' => empty($vipPresencePayload) ? 'system_welcome' : 'vip_presence',
'welcome_user' => $this->user->username,
'sent_at' => now()->toDateTimeString(),
];
// 会员离场时,把横幅与特效信息挂到消息体,前端才能展示专属离场效果。
if (! empty($vipPresencePayload)) {
$leaveMsg = array_merge($leaveMsg, $vipPresencePayload);
}
}
// 将播报存入 Redis 历史及广播
$chatState->pushMessage($this->roomId, $leaveMsg);
broadcast(new \App\Events\UserLeft($this->roomId, $this->user->username))->toOthers();
broadcast(new \App\Events\MessageSent($this->roomId, $leaveMsg))->toOthers();
// 离场特效单独发送给房间内仍在线的其他人,避免和消息播报逻辑耦死。
if (! empty($leaveMsg['presence_effect'])) {
broadcast(new \App\Events\EffectBroadcast($this->roomId, $leaveMsg['presence_effect'], $this->user->username))->toOthers();
}
}
/**
* 关闭该用户尚未结束的在职登录记录(结算在线时长)
*/
private function closeDutyLog(int $userId): void
{
// 将今日开放日志关闭并结算实际时长
PositionDutyLog::query()
->where('user_id', $userId)
->whereNull('logout_at')
->whereDate('login_at', today())
->update([
'logout_at' => now(),
'duration_seconds' => DB::raw('GREATEST(0, TIMESTAMPDIFF(SECOND, login_at, NOW()))'),
]);
// 关闭历史遗留的跨天未关闭日志login_at 非今日)
PositionDutyLog::query()
->where('user_id', $userId)
->whereNull('logout_at')
->whereDate('login_at', '!=', today())
->update([
'logout_at' => DB::raw('DATE_ADD(DATE(login_at), INTERVAL "23:59:59" HOUR_SECOND)'),
]);
}
}