Files
chatroom/app/Jobs/ProcessUserLeave.php
lkddi fa5e37f003 feat: 增加发送微信群内自定义公告功能,并优化离线防抖与自我播报过滤机制
- 后台微信机器人增加群内独立公告的分发推送模块
- 聊天室系统引入3秒离线延迟(防抖)防重复播报
- 优化聊天界面消息拉取过滤自身的欢迎或离场广播
- 管理员登录时的烟花特效同步至用户当前的前端显示
2026-04-02 16:07:40 +08:00

114 lines
4.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);
$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' => 'system_welcome',
'welcome_user' => $this->user->username,
'sent_at' => now()->toDateTimeString(),
];
}
// 将播报存入 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();
}
/**
* 关闭该用户尚未结束的在职登录记录(结算在线时长)
*/
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)'),
]);
}
}