diff --git a/app/Models/Room.php b/app/Models/Room.php index 16ac173..e5bea6c 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -139,4 +139,20 @@ class Room extends Model || $user->username === $this->master || $user->user_level >= $superLevel; } + + /** + * 模型的 booted 方法。 + * + * 在房间被更新或删除时,同步清理 Redis 上的 Presence Channel 鉴权缓存。 + */ + protected static function booted(): void + { + static::saved(function (Room $room) { + \Illuminate\Support\Facades\Cache::forget("room:meta:{$room->id}"); + }); + + static::deleted(function (Room $room) { + \Illuminate\Support\Facades\Cache::forget("room:meta:{$room->id}"); + }); + } } diff --git a/app/Services/MessageFilterService.php b/app/Services/MessageFilterService.php index 959be17..bc8869a 100644 --- a/app/Services/MessageFilterService.php +++ b/app/Services/MessageFilterService.php @@ -20,6 +20,35 @@ class MessageFilterService '外挂', '刷单', '脚本', // 示例黑名单 ]; + /** + * Trie 字典树实例,用于 DFA 过滤 + */ + private ?array $trie = null; + + /** + * 构建 Trie 字典树 + */ + private function buildTrie(): void + { + if ($this->trie !== null) { + return; + } + + $this->trie = []; + foreach ($this->badWords as $word) { + $temp = &$this->trie; + $len = mb_strlen($word); + for ($i = 0; $i < $len; $i++) { + $char = mb_substr($word, $i, 1); + if (! isset($temp[$char])) { + $temp[$char] = []; + } + $temp = &$temp[$char]; + } + $temp['is_end'] = true; + } + } + /** * 执行过滤净化,保障入库和显示安全。 * @@ -35,16 +64,46 @@ class MessageFilterService // 1. HTML 标签全量脱除,阻绝任意 XSS/HTML 注入 $content = strip_tags($content); - // 2. 敏感词替换 - foreach ($this->badWords as $word) { - if (mb_strpos($content, $word) !== false) { - // 将脏字替换为相同长度的 星号 或 提示 - $replacement = str_repeat('*', mb_strlen($word)); - $content = str_replace($word, $replacement, $content); + // 2. 惰性初始化并构建 DFA 字典树 + $this->buildTrie(); + + // 3. 将字符串转为多字节字符数组,进行 DFA 扫描与替换 + $len = mb_strlen($content); + $chars = []; + for ($i = 0; $i < $len; $i++) { + $chars[] = mb_substr($content, $i, 1); + } + + $result = []; + $i = 0; + while ($i < $len) { + $temp = &$this->trie; + $matchLength = 0; + $j = $i; + + while ($j < $len && isset($temp[$chars[$j]])) { + $temp = &$temp[$chars[$j]]; + if (isset($temp['is_end']) && $temp['is_end'] === true) { + $matchLength = $j - $i + 1; // 匹配到最长敏感词 + } + $j++; + } + + if ($matchLength > 0) { + // 替换为相同长度的 * + for ($k = 0; $k < $matchLength; $k++) { + $result[] = '*'; + } + $i += $matchLength; // 跳过敏感词 + } else { + $result[] = $chars[$i]; + $i++; } } - // 3. 将连续的空格去重,只保留一个真正的空格 + $content = implode('', $result); + + // 4. 将连续的空格去重,只保留一个真正的空格 $content = preg_replace('/\s+/', ' ', $content); return trim($content); diff --git a/routes/channels.php b/routes/channels.php index 79a377d..0e471af 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -9,7 +9,9 @@ Broadcast::channel('App.Models.User.{id}', function ($user, $id) { // 聊天室房间 Presence Channel 鉴权与成员信息抓取 Broadcast::channel('room.{roomId}', function ($user, $roomId) { - $room = \App\Models\Room::find($roomId); + $room = \Illuminate\Support\Facades\Cache::remember("room:meta:{$roomId}", 300, function () use ($roomId) { + return \App\Models\Room::find($roomId); + }); if (! $room || ! $room->canUserEnter($user)) { return false; }