还在为某些会员刷屏、引战、阴阳怪气的帖子烦恼?装上这个轻量小插件,只需点一下,就能把 TA 彻底"静音":TA 的帖子和回复对你自动隐藏,世界瞬间清净。
功能亮点
- 一键屏蔽:帖子主楼右下角、每条回复的操作区都有「屏蔽」按钮,点一下即生效,再点一下取消,零门槛。
- 内容自动过滤:被你屏蔽的人,TA 发的帖子不会出现在首页 / 版块 / 搜索等列表里;进到帖子详情,TA 的主楼正文会变成"该内容已被你屏蔽"的占位提示,TA 的回复也一并隐藏。
- 「我的屏蔽」管理页:在
/blocked集中管理,可按用户名添加、随时取消,一目了然。 - 入口顺手:登录后右上角用户菜单里直接有「我的屏蔽」入口,随时进出。
- 后台可开关:如果你觉得帖子里的屏蔽按钮碍眼,管理员可以在后台一键关掉(关掉后仍能按用户名屏蔽)。
怎么用
- 安装并启用插件;
- 想屏蔽谁,进 TA 的帖子,点右下角「屏蔽」即可;
- 想统一管理,点右上角头像 → 「我的屏蔽」,随时增删。
就这么简单,不需要任何配置就能直接用。
为什么放心用
- 零核心侵入:全部功能通过钩子 + 插件自带页面实现,不改动站点主程序;禁用或卸载即干净移除,不留任何残留。
- 独立存储:屏蔽关系存在插件自己的数据库里,和站点核心数据完全隔离,互不影响。
- 几乎不影响速度:按钮渲染和内容过滤都做了缓存处理,列表页不受任何影响,再长的帖子也不会多查库。
- 多语言:内置简体中文 / 繁体中文 / 英文。
基本信息
- 插件名:屏蔽用户
- 标识:
vn_block - 版本:1.0.0
- 分类:社区 / 互动
如果你也被某些会员的内容困扰,不妨试试这个插件,把清净还给自己。有问题欢迎回帖交流。
vn_block(屏蔽用户)插件修复报告
- 修复对象:
plugins/vn_block/ - 修复日期:2026-09-23
- 版本:
1.0.0→1.0.1 - 发布状态:仍未进入任何发布包(
update//CHANGELOG.md无记录) - 修复范围:审计报告 P0 全部 1 项 + P1 全部 4 项 + P2 全部 4 项 + P3 全部 5 项,共 14 项,另含修复过程中新发现的 3 项(见 §4)
一、结论速览
| 编号 | 问题 | 状态 | 验证方式 |
|---|---|---|---|
| P0-1 | 点击屏蔽/取消屏蔽 404 | ✅ 已修 | HTTP 实测:7 种回跳场景全部正确 |
| P1-1 | 缺 class_alias → 三生命周期方法静默跳过 |
✅ 已修 | class_exists('\Plugin\vn_block\Plugin') = true |
| P1-2 | 16 个文件 CRLF | ✅ 已修 | 全量复核:零 CR、零 BOM |
| P1-3 | 后台路由注册在 route_register |
✅ 已修 | 新建 admin_route_register.php + 重建缓存,后台页 200 |
| P1-4 | #replies 内表单无 hx-disinherit |
✅ 已修 | 渲染输出核对(18 处中 9 处为本插件表单)⚠️ 未做浏览器端验证 |
| P2-1 | init_after 每请求跑 DDL |
✅ 已修 | 戳文件已生成;代码路径核对 |
| P2-2 | uninstall() 漏删 blk_settings |
✅ 已修 | 副本干跑:两张表全部清空 |
| P2-3 | 搜索页过滤映射键写错 | ✅ 已修 | A/B 实测:屏蔽者 0 命中 / 未屏蔽者 1 命中 |
| P2-4 | 视图未用 section('css')/section('title') |
✅ 已修 | 渲染输出:<title> 正确、CSS 走 section |
| P3-1 | nav 钩子忽略 placement |
✅ 已修 | 按 PC / 移动分别渲染 |
| P3-2 | 详情页 CSS 重复注入 | ✅ 已修 | 实测 CSS link 数 2 → 1 |
| P3-3 | 硬编码中文「未知用户」 | ✅ 已修 | 三语新增 unknown_user 键 |
| P3-4 | 图标硬编码与 plugin.json 不一致 | ✅ 已修 | 统一为 user-slash() |
| P3-5 | setSetting() 不清请求级缓存 |
✅ 已修 | 缓存改为静态属性 + 写入失效 |
门禁全绿:编码/行尾 PASS、php -l PASS、零内联 PASS、无残留文件 PASS、i18n 16 键三语零差异 PASS、CSS 变量 8/8 存在且无硬编码色 PASS。
二、P0-1:404 的修复(核心改动)
改法
/block/toggle 是 POST-only 路由,控制器不能用 REQUEST_URI 回跳(会 302 回自身 → GET → 404)。改为「表单隐藏域 return_to + 服务端四重校验」:
// FrontController.php — toggle()
$this->redirect($this->safeReturnTo((string)($_POST['return_to'] ?? '')));
// FrontController.php:82 新增
private function safeReturnTo(string $raw): string
{
$raw = \trim($raw);
if ($raw === '' || $raw[0] !== '/') return '/blocked'; // 空 / 非根相对路径
if (\str_starts_with($raw, '//') // 协议相对 URL
|| \strpos($raw, '\\') !== false // 反斜杠(代理可归一为协议前缀)
|| \strpbrk($raw, "\r\n") !== false // 响应头注入
|| \strpos($raw, 'block/toggle') !== false // 回跳自身 → 死循环 404
) return '/blocked';
return $raw;
}
三个入口的 return_to 来源:
| 入口 | return_to 值 |
位置 |
|---|---|---|
| 主帖按钮 | $_SERVER['REQUEST_URI'](渲染期即当前详情页) |
thread_operation_after.php:26 |
| 回复按钮 | 同上 | post_operation_after.php:26 |
| 管理页取消屏蔽 | 固定 /blocked |
views/manage.php:46 |
currentPath() 方法已删除(无用且是缺陷源头)。没有给 /block/toggle 补 GET 路由——那会把写操作暴露给预取器/爬虫。
实测(HTTP,登录态 user_id=2)
① return_to=/thread/89 -> 302 Location: /thread/89 ← 主路径修复
② return_to=/blocked -> 302 Location: /blocked
③ return_to=//evil.com -> 302 Location: /blocked ← 开放重定向被拦
④ return_to=https://evil.x -> 302 Location: /blocked ← 被拦
⑤ return_to=/block/toggle -> 302 Location: /blocked ← 自跳被拦
⑥ 无 return_to -> 302 Location: /blocked ← 兜底
⑦ return_to=/\a(反斜杠) -> 302 Location: /blocked ← 被拦
GET /thread/89 -> 200 GET /blocked -> 200 (跳转目标均可达,404 消失)
三、其余修复明细
P1-1 class_alias(Plugin.php:271)
if (!\class_exists('Plugin\\vn_block\\Plugin', false)) {
\class_alias(Plugin::class, 'Plugin\\vn_block\\Plugin', false);
}
实测:Plugin\vn_block\Plugin false → true,method_exists(..., 'uninstall') = true → 卸载不再留孤儿表。
P1-2 CRLF → LF(16 个文件)
用 Python 二进制读写逐个转换,转换前后 sha256 均记录(例:Plugin.php 5b5e48e6 → db3ce619、style.css 702b52c9 → 203bb8fb)。复核:全插件零 CR、零 BOM。
先转行尾再改代码,避免新写入的行与旧 CRLF 混行。
P1-3 后台路由改走专用钩子
新建
hook/admin_route_register.php(路径不含/admin,核心 admin 分组已带前缀):$router->get('/vn-block', ['\\Plugin\\VnBlock\\AdminController', 'settings']); $router->post('/vn-block', ['\\Plugin\\VnBlock\\AdminController', 'save']);route_register.php删除这 2 行;plugin.jsonhooks 增补admin_route_register;Plugin::refresh()重建plugins/plugins_cache.json(已确认新钩子在缓存中、8 个 hook 文件全部存在);AdminController.php顶部错误注释("admin_route_register 未被核心触发")已改正。
实测:GET /admin/vn-block → 200(含侧栏、<title>贴子内的屏蔽按钮),POST → 302 /admin/vn-block?saved=1。
P1-4 hx-disinherit="*"
两个操作区钩子的 <form> 均加 hx-disinherit="*"。渲染输出核对:页面内 18 处 hx-disinherit="*" = 9 处核心楼层容器(thread/show.php:252)+ 9 处本插件表单。
⚠️ 本机无 Chromium,htmx 的实际接管行为未做浏览器端验证,建议在浏览器里点一次回复区的「屏蔽」确认。
P2-1 init_after 加戳文件守卫
$__stamp = __DIR__ . '/../../../protected/.vn_block_table_cache';
$__dbFile = __DIR__ . '/../data/vn_block.sqlite';
if (is_file($__stamp) && is_file($__dbFile)) return; // 两条件同时满足才短路
try { \Plugin\VnBlock\Plugin::activate(); @file_put_contents($__stamp, '1', LOCK_EX); } ...
比仓库既有 7 个插件多判了一个「库文件存在」——避免库被删(手工清理 data/、换服务器只传代码)时表永远建不出来。实测戳文件已生成(protected/.vn_block_table_cache,1 字节,与其余 7 个同款)。
P2-2 uninstall() 补删表
foreach (['blk_blocks', 'blk_settings'] as $__table) {
try { self::db()->exec('DROP TABLE IF EXISTS ' . $__table); } catch ...
}
在库副本上干跑(不动真库):['blk_blocks','sqlite_sequence','blk_settings'] → ['sqlite_sequence']。 唯一残留是 SQLite 内部表 sqlite_sequence(AUTOINCREMENT 自带、不可删、无数据),非插件表。
P2-3 列表过滤映射修正
$listMap = [
'forum/index' => ['latestThreads'],
'forum/category' => ['latestThreads'],
'home/index' => ['latestThreads'],
'home/index_default' => ['latestThreads'],
'tags/show' => ['threads'],
'search/index' => ['threadResults', 'results'], // 原写 'threads'(不存在)→ 死映射
];
另外 Plugin::filterThreads() 兼容两种作者字段:装饰后行用 user_id、topic_index 原始行用 uid(覆盖搜索 LIKE 降级分支)。
A/B 实测(搜索「微博」):
| 身份 | 命中被屏蔽作者的帖子 |
|---|---|
| uid1(已屏蔽 275) | 0 |
| uid2(未屏蔽) | 1 |
P2-4 视图区块规范化
两视图均改为 section('title') + section('css'),CSS 从内容区移到 css 区。 实测 /blocked → <title>我的屏蔽列表 - 燧石社区</title>;/admin/vn-block → <title>贴子内的屏蔽按钮 - 燧石社区</title>。
P3-1 / P3-4 导航钩子
按 $params['placement'] 分支:PC 用 .mn-dropdown-item(核心已补样式),移动端用裸 <a>(容器 .mn-mobile-nav-mine-menu a 是 flex + gap:8px,<i>+<span> 即可对齐)。图标统一为 user-slash(,与 plugin.json 一致),字号沿用核心工具类 mn-fs-14(零内联)。
P3-2 CSS 重复注入
注入条件改为「仅 htmx 局部请求才在 body 补注入」($_SERVER['HTTP_HX_REQUEST']):整页加载由 layout_head_end 的 head 注入负责,htmx 导航(只换 body、丢弃 head)由 body 兜底。 实测:/thread/89 整页加载 CSS link 2 → 1;带 HX-Request: true 时仍为 2(head 被 htmx 丢弃、body 生效)——符合预期。
P3-3 / P3-5
- 三语各新增 3 键:
user_not_found({name}占位)、cannot_block_self、unknown_user;视图/控制器不再硬编码中文。顺带补了「按用户名屏蔽时命中自己」的提示(原先提交后毫无反应)。 setting()的static $cache改为类静态属性self::$settingCache,setSetting()写入后unset该键。
四、修复过程中新发现并一并修掉的 3 项
| # | 问题 | 影响 | 修复 |
|---|---|---|---|
| N-1 | 两视图的 CSS 版本号路径写错:__DIR__ . '/../../assets/style.css'(视图在 views/,../../assets 指向 plugins/assets,恒不命中) |
?v= 永远退化成 v=1 → 改 CSS 后浏览器不刷新(静默) |
改为 ../assets/style.css;实测版本号由 v=1 变为 v=d34b2380 |
| N-2 | 'profile/index' => 'userThreads' 语义错误:/profile 是**「我的资料」页**,userThreads 恒为本人帖子(ProfileController.php:122),过滤永远无意义 |
死映射(无害但误导) | 删除该映射;改为支持公开个人主页 plugins/user_profile/front:主页主人被屏蔽时清空其帖子/博客列表 |
| N-3 | AdminController.php 注释断言「admin_route_register 未被核心触发」——与核心 index.php:276 事实不符 |
误导后续维护者 | 随 P1-3 一并改正 |
N-2 的 A/B 实测(/u/275,uid1 已屏蔽 275):
| 身份 | 页面命中该用户帖子标题数 |
|---|---|
| uid1(已屏蔽) | 0(列表已清空) |
| uid2(未屏蔽) | 1 |
五、改动文件清单(20 个文本文件,sha256 前 12 位)
| 文件 | sha256 | 说明 |
|---|---|---|
Plugin.php |
1fbc26978594 |
uninstall 补删表 / 缓存静态化 / filterThreads 兼容 uid / class_alias |
FrontController.php |
4c74347e684a |
return_to + safeReturnTo / 删除 currentPath / i18n 提示 |
AdminController.php |
b2e4f01e0ba3 |
注释修正 + @version |
plugin.json |
cbb09b8a2f9b |
version 1.0.1 + 新增 admin_route_register |
hook/admin_route_register.php |
e014b1404ad3 |
新增 |
hook/route_register.php |
ad99f825c55e |
移除后台路由 |
hook/init_after.php |
f52d78ecc5ea |
戳文件守卫 |
hook/controller_view_before.php |
537e9f82f377 |
映射修正 + 个人主页支持 |
hook/thread_operation_after.php |
4e0370bd8ad9 |
return_to + hx-disinherit + CSS 注入条件 |
hook/post_operation_after.php |
e24b68361d2d |
同上 |
hook/nav_user_menu_items.php |
beded4530608 |
placement 分支 + 图标统一 |
hook/layout_head_end.php |
ee9bd8472c70 |
仅行尾转换(逻辑未变) |
views/manage.php |
c3f339de8815 |
section 区块 / return_to / 版本号路径 / i18n |
views/admin_settings.php |
e39872953e75 |
section 区块 / 版本号路径 |
lang/zh.php |
74f5a56039c5 |
+3 键 |
lang/en.php |
37dd2e03bb0d |
+3 键 |
lang/zh_tw.php |
347aae675156 |
+3 键 |
assets/style.css |
203bb8fb0c5a |
仅行尾转换(逻辑未变) |
data/.htaccess / data/web.config |
未变 | 自动生成的防护文件 |
连带产物:plugins/plugins_cache.json 已重建(vn_block 条目含 8 个钩子、version 1.0.1);protected/.vn_block_table_cache 运行时戳文件(与其余 7 个插件同款)。
六、回归验证矩阵(全部实测)
| 项 | 命令/方式 | 结果 |
|---|---|---|
| 编码行尾 | Python 数 \r / BOM |
PASS(零 CR、零 BOM) |
| 语法 | php -l × 20 |
PASS |
| 零内联 | 正则扫 views/hook/css | PASS(零命中) |
| 残留文件 | .bak/.revert/.tmp 扫描 |
PASS(无) |
| i18n | 三语键集合/键序/引用比对 | PASS(各 16 键,缺 0 多 0,键序一致) |
| CSS | 变量存在性 + 硬编码色 + 花括号 | PASS(8/8 存在、无硬编码、22/22 配对) |
| 生命周期类名 | class_exists + method_exists |
PASS |
| P0 回跳 | 7 种 return_to |
全部正确(详见 §2) |
| 详情页 | /thread/89 |
200;占位提示生效;9 表单带 return_to;CSS 1 份 |
| 管理页 | /blocked |
200;标题正确;return_to=/blocked |
| 后台页 | /admin/vn-block GET/POST |
200 / 302 saved=1 |
| htmx 路径 | 带 HX-Request |
body 补注入生效 |
| 搜索过滤 | A/B(uid1 vs uid2) | 0 vs 1 ✅ |
| 个人主页过滤 | A/B(uid1 vs uid2) | 0 vs 1 ✅ |
| uninstall | 库副本干跑 | 两表清空 ✅ |
| 戳文件 | 删后请求 | 自动重建 ✅ |