feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型 - 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息 - 撤销:灰色弹窗 + 灰色系统消息,无礼花 - 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏 - 系统消息加随机鼓励语(各5条轮换) - ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds) - 用户名片折叠优化:管理员视野、职务履历均可折叠 - 管理操作 + 职务操作合并为「🔧 管理操作」折叠区 - 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
This commit is contained in:
@@ -53,15 +53,18 @@ class ChatStateService
|
||||
public function getUserRooms(string $username): array
|
||||
{
|
||||
$rooms = [];
|
||||
$prefix = config('database.redis.options.prefix', '');
|
||||
$cursor = '0';
|
||||
do {
|
||||
[$cursor, $keys] = Redis::scan($cursor, ['match' => 'room:*:users', 'count' => 100]);
|
||||
foreach ($keys ?? [] as $key) {
|
||||
if (Redis::hexists($key, $username)) {
|
||||
// 从 key "room:123:users" 中提取房间 ID
|
||||
preg_match('/room:(\d+):users/', $key, $matches);
|
||||
if (isset($matches[1])) {
|
||||
$rooms[] = (int) $matches[1];
|
||||
// scan 带前缀通配,返回的 key 也带前缀
|
||||
[$cursor, $keys] = Redis::scan($cursor, ['match' => $prefix.'room:*:users', 'count' => 100]);
|
||||
foreach ($keys ?? [] as $fullKey) {
|
||||
// 去掉前缀得到 Laravel Redis Facade 认识的短少 key
|
||||
$shortKey = $prefix ? ltrim(substr($fullKey, strlen($prefix)), '') : $fullKey;
|
||||
if (Redis::hexists($shortKey, $username)) {
|
||||
preg_match('/room:(\d+):users/', $shortKey, $m);
|
||||
if (isset($m[1])) {
|
||||
$rooms[] = (int) $m[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +91,34 @@ class ChatStateService
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描 Redis,返回当前所有有在线用户的房间 ID 数组(用于全局广播)。
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public function getAllActiveRoomIds(): array
|
||||
{
|
||||
$roomIds = [];
|
||||
$prefix = config('database.redis.options.prefix', '');
|
||||
$cursor = '0';
|
||||
do {
|
||||
// scan 带前缀通配,返回的 key 也带前缀
|
||||
[$cursor, $keys] = Redis::scan($cursor, ['match' => $prefix.'room:*:users', 'count' => 100]);
|
||||
foreach ($keys ?? [] as $fullKey) {
|
||||
$shortKey = $prefix ? substr($fullKey, strlen($prefix)) : $fullKey;
|
||||
// 只有 hash 非空(有在线用户)才算活跃房间
|
||||
if (Redis::hlen($shortKey) > 0) {
|
||||
preg_match('/room:(\d+):users/', $shortKey, $m);
|
||||
if (isset($m[1])) {
|
||||
$roomIds[] = (int) $m[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ($cursor !== '0');
|
||||
|
||||
return array_unique($roomIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一条新发言推入 Redis 列表,并限制最大保留数量,防止内存泄漏。
|
||||
*
|
||||
@@ -115,6 +146,42 @@ class ChatStateService
|
||||
Redis::del($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定房间内,关于某个用户的旧欢迎消息(支持普通人、管理员、新人)。
|
||||
* 保证聊天记录里只保留最新的一条,解决频繁进出造成的刷屏问题。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
* @param string $username 用户名
|
||||
*/
|
||||
public function removeOldWelcomeMessages(int $roomId, string $username): void
|
||||
{
|
||||
$key = "room:{$roomId}:messages";
|
||||
$messages = Redis::lrange($key, 0, -1);
|
||||
|
||||
if (empty($messages)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
|
||||
foreach ($messages as $msgJson) {
|
||||
$msg = json_decode($msgJson, true);
|
||||
// 只要消息里带了 welcome_user 且等于当前用户,就抛弃这条旧的
|
||||
if ($msg && isset($msg['welcome_user']) && $msg['welcome_user'] === $username) {
|
||||
continue;
|
||||
}
|
||||
$filtered[] = $msgJson;
|
||||
}
|
||||
|
||||
// 重新写回 Redis(如果发生了过滤)
|
||||
if (count($filtered) !== count($messages)) {
|
||||
Redis::del($key);
|
||||
if (! empty($filtered)) {
|
||||
Redis::rpush($key, ...$filtered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定房间的新发言记录。
|
||||
* 在高频长轮询或前端断线重连拉取时使用。
|
||||
|
||||
Reference in New Issue
Block a user