From 83192ffcce838c1a19e7cd4bc0e2fea38578c156 Mon Sep 17 00:00:00 2001 From: pllx Date: Tue, 30 Jun 2026 11:32:55 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E9=87=8D=E6=9E=84=E6=95=8F=E6=84=9F?= =?UTF-8?q?=E8=AF=8D=E8=BF=87=E6=BB=A4=E4=B8=BADFA=E7=AE=97=E6=B3=95?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=BC=95=E5=85=A5WebSocket=E6=88=BF=E9=97=B4?= =?UTF-8?q?=E9=A2=91=E9=81=93=E9=89=B4=E6=9D=83Redis=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Room.php | 16 ++++++ app/Services/MessageFilterService.php | 73 ++++++++++++++++++++++++--- routes/channels.php | 4 +- 3 files changed, 85 insertions(+), 8 deletions(-) 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; }