本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/update/27/plugins/floor_reply/说明.md。
楼中楼(floor_reply)插件说明
版本:1.1.0 | 作者:FlintHub | 状态:✅ 已启用
功能:在帖子楼层(posts)下方支持对楼层的再回复(楼中楼),支持 @ 提及用户(高亮 + 跳转个人主页);
并在有人回复楼层 / @ 到某人时发送通知,通知可深链直达那一条楼中楼。
一、功能简介
- 楼中楼:对某条回复(楼层)进行再回复,数据存独立库,不占用核心楼层数
- @ 提及:回复时 @ 指定用户,前端高亮显示,点击跳转其个人主页
mention_user_id只记录第一个被 @ 的用户,仅用于列表高亮渲染- 通知则覆盖全部被 @ 的用户(见第四节)
- 回复计数联动:回复成功后同步更新帖子的回复数与最后回复时间(
Thread::incrementReplyCount) - 积分奖励:回复成功触发核心积分奖励(
Points::markReplied/Points::award),并计入站点统计 - 通知 + 深链(V1.1.0):楼中楼回复会通知楼层作者与被 @ 的人(楼主可选),
点通知直达该条楼中楼所在页并高亮
二、目录结构
plugins/floor_reply/
├── plugin.json # 插件清单(6 个钩子 + admin_url + permissions)
├── Plugin.php # 独立库连接 + 建表 + 楼中楼统计/计数 + 配置读写 + pageOfReply()
├── FrontController.php # 楼中楼回复 / 列表接口(?focus= 深链)
├── AdminController.php # 后台通知设置页(V1.1.0 新增)
├── hook/
│ ├── init_after.php # 幂等建表兜底
│ ├── route_register.php # 前台路由(回复/列表/删除)
│ ├── admin_route_register.php # 后台路由(V1.1.0 新增,需 route:admin 权限)
│ ├── layout_head_end.php # 注入楼中楼 CSS/JS(仅 thread/show)
│ ├── post_operation_after.php # 楼层下注入"楼中楼"入口 + 锚点
│ └── controller_view_before.php # 一次性批量注入各楼层楼中楼数 + 自动展开开关
├── views/
│ ├── list.php # 楼中楼列表局部模板(htmx 片段)
│ └── admin.php # 后台设置页(V1.1.0 新增)
├── assets/ # style.css / script.js
└── lang/ # zh.php / en.php / zh_tw.php三、数据表(插件独立库 plugins/floor_reply/data/floor_reply.sqlite)
| 表 | 用途 |
|---|---|
fr_replies | 楼中楼回复(post_id / thread_id / user_id / mention_user_id / content / created_at,索引 idx_fr_post、idx_fr_thread) |
- 建表:
activate()+init_after双保险(幂等 DDL) - 卸载:
uninstall()删表 并清理核心settings表里本插件的配置键(会清空全部楼中楼数据,请谨慎)
四、路由
前台
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /floor-reply/list/{id} | 某楼层({id} = 楼层 post_id)的楼中楼列表片段(htmx 局部渲染,每页 5 条)<br>可选 ?page=N 翻页;可选 ?focus=<楼中楼ID> 强制渲染该条所在页并标记高亮(通知深链用) |
| POST | /floor-reply/reply | 提交楼中楼回复(CSRF + 频控) |
| POST | /floor-reply/delete | 删除楼中楼回复(作者本人 / 管理员 / 楼主) |
后台
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/floor-reply | 通知设置页 |
| POST | /admin/floor-reply/save | 保存设置(CSRF) |
⚠️ 后台路由需在
plugin.json声明route:admin;写全局设置需system:settings。二者均已声明。
五、通知机制(V1.1.0)
收件人规则
一条楼中楼回复可能同时命中三类收件人,按优先级去重,保证「每个收件人最多收到 1 条」:
| 优先级 | 收件人 | 通知类型 | 默认 |
|---|---|---|---|
| 1 | 被 @ 到的人 | mention | ✅ 开 |
| 2 | 楼层作者 | floor_reply | ✅ 开 |
| 3 | 楼主(帖子作者) | floor_reply | ❌ 关 |
- 回复人自己永不收到通知(与核心
notifyReply/notifyQuote/notifyVote一致) - 楼主默认不通知:楼中楼是「楼层内部的对话」,核心
notifyReplyAggregated()已负责「帖子有新回复」的聚合通知 - 通知写入核心
business.sqlite的notifications表:related_id= 楼层 post_id、related_type='floor'
深链格式
/thread/{主题ID}?pid={楼层ID}&fr={楼中楼ID}#fr-{楼中楼ID}三段分工:
?pid=→ 核心ThreadController::show()经Post::pageOf()算出楼层所在页码并跳转?fr=→ 本插件?focus=参数算出该条楼中楼所在页并标记高亮#fr-{id}→ 原生锚点兜底,script.js负责滚动到视野中央
通知文案键(核心语言包 lang/{zh,en,zh_tw}.php)
| 键 | 用途 |
|---|---|
plugin.notifications.floor_reply | 「{user} 回复了你在「{title}」中的回复」 |
plugin.notifications.floor_reply_owner | 「{user} 在「{title}」的楼中楼中回复了 {target}」(楼主专用,避免误称「你的回复」) |
plugin.notifications.mentioned | 「{user} 在回复中提到了你,主题「{title}」」 |
六、后台设置项
配置存核心 settings 表,键前缀 floor_reply_。
| 键 | 默认 | 说明 |
|---|---|---|
floor_reply_notify_floor_author | 1 | 通知楼层作者 |
floor_reply_notify_mention | 1 | 通知被 @ 的人 |
floor_reply_notify_thread_owner | 0 | 通知楼主(默认关) |
floor_reply_auto_expand | 1 | 打开帖子时自动展开已有楼中楼(通知深链不受此开关影响) |
七、安全与规范
- 提交受 RateLimiter 频控(3 次 / 30 秒)保护,防刷分漏洞
- 回复内容入库前
strip_tags(),输出经$this->e()/renderMentions()转义,防 XSS - 全部 PDO 预处理绑定;ID 一律
(int)强转 - 后台设置页表单带
csrf隐藏域,服务端Csrf::verifyOrDie()校验 - 楼中楼仅占独立库,不写核心帖子表结构,卸载/禁用对核心数据零污染
- 样式全部走
--mn-*主题变量(含 fallback),双主题(浅色 / 暗夜星辰)可读
八、注意事项
- 依赖
controller_view_before一次性注入全帖楼中楼数据,避免每楼层一次查询 - 删除楼层(post)时不自动清理其楼中楼(历史遗留记录保留)
- 删除楼中楼不撤回已发出的通知(通知按「历史事件」处理)
- 积分奖励 / 回复计数联动为核心约定,流程调整时需同步
Plugin.php内相关方法 - 改动
plugin.json的 hooks 后必须重建plugins/plugins_cache.json(删文件或调Plugin::refresh()),否则新钩子不生效 - 通知深链的页码换算口径与核心
Post::getByThread()严格同桶同表同排序;若核心改了楼层排序规则,Post::pageOf()需同步调整