[开发]楼中楼(floor_reply)通知能力全面升级方案

👑Lv.11 元老 🌞 骨干会员
2026-10-25 01:12:00

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/楼中楼通知升级方案_2026-09-21.md。

楼中楼(floor_reply)通知能力全面升级方案

文档性质:方案(未动一行代码,等指令执行)

日期:2026-09-21

适用版本:站点当前 FLINTHUB_VERSION = V1.0.26

方案依据:全部结论均来自本次实测(读源码 + 查生产库),关键处标注 文件:行号


零、一页速览(决策摘要)

#决策点推荐影响面
1通知楼层作者✅ 做插件 1 处 + 核心 1 方法
2通知被 @ 的人✅ 做(支持多人)同上,零额外查询
3通知楼主❌ 默认不通知,留后台开关插件 1 处判断
4去重规则每个收件人最多 1 条,优先级 @ > 楼层作者 > 楼主纯逻辑,无额外 IO
5通知类型启用已有 mention + 新增 floor_reply核心 2 处 + 三语文案
6图标/配色改核心 index.php 的 $iconMap/$colorMap(各补 3 行)顺带修好 reply_quote 缺失
7深链定位插件侧 ?fr= + 核心侧 ?pid=(同时修好既有 4 类通知)核心 2 文件小改(可选)
8插件后台开关✅ 做(复用 SEO 插件范式)新增 2 文件 + 改 plugin.json
9发布形态update/27/ 增量包(含核心文件)沿用 update/26 体例

工作量:核心 5 文件(约 60 行)+ 插件 9 文件(约 350 行)+ 三语文案 6 键。

风险等级:低—中。唯一中风险项是第 7 项的核心改动(ThreadController::show()),但它可独立开关、可单独回滚。


一、现状核查(事实基线)

1.1 楼中楼通知:完全空白(本次升级的主因)

plugins/floor_reply/FrontController.php 的 reply()(:61-127)整条链路零通知调用。它做了 7 件事,唯独没有通知:

:67   Csrf::verifyOrDie()
:70   RateLimiter::hitConfig('reply', 3, 30)
:90   Plugin::parseMentions($content)          ← 已拿到「全部」被 @ 用户
:98   $mentionUserId = (int)reset($mentions['exists'])  ← 只取第一个
:105  Plugin::addReply(...)
:109  Thread::incrementReplyCount()
:111  Settings::runtimeIncr('total_posts')
:113  Points::markReplied()
:115  Points::award()
:118  PageCache::invalidate()

生产库实测(plugins/floor_reply/data/floor_reply.sqlite):

指标值
fr_replies 总条数28
含 @(mention_user_id > 0)4
自己 @ 自己0
单楼层最多回复数5(post 1344)
多人对话楼层4 个(post 786 / 844 / 1045 / 1344)

后果:三个场景全部静默——① 楼层作者不知道有人回了自己;② 被 @ 的人不知道被提到;③ 计数变了(incrementReplyCount)却没有任何通知,用户感知不一致。

1.2 引用回复通知:已实现,但有三个缺陷

链路完整且生产已验证:

thread/show.php:338  [data-action="quote"](带 data-quote-uid)
  → assets/js/thread.js:8-24   事件委托
  → assets/js/editor.js:2307    window.quotePost() 动态注入 #quoted_uids
  → ThreadController.php:185   读 $_POST['quoted_uids']
  → Notification.php:388       notifyQuote()

生产库实证:notifications 表已有 6 条 reply_quote(id = 1711 / 1713 / 1715 / 1727 / 1903 / 2041,2026-09-14 ~ 09-19,收件人 269 / 1 / 267 / 275);protected/error.log 零 notifyQuote error。

缺陷证据性质
a. 只有点「引用」按钮才发quoted_uids 纯 JS 注入,手打 @ 或 > 引用语法不发设计边界,本方案不扩大
b. 通知中心没有专属图标index.php:13-27 的 $iconMap 无 reply_quote → 落 ?? 'info' 通用感叹号真缺陷,顺带修
c. 注释过时Notification.php:22 写「预留:无引用检测,暂不触发」,实际早已触发真缺陷,顺带修

1.3 通知基础设施:零钩子

文件是否有 Hook
app/Helpers/Notification.php❌ 无
app/Controllers/NotificationController.php❌ 无
app/Views/notifications/index.php❌ 无
app/Views/_components/notification_bell.php❌ 无

推论:新增通知类型的图标/配色无法纯靠插件实现,必须改核心视图(见 §五)。

(docs/插件开发规范.md §9.1 的 40 个钩子里,没有任何通知相关钩子。)

1.4 ⚠️ 深链缺陷(连带发现,影响既有全部 4 类通知)

现有通知的 link 一律形如 /thread/{tid}?pid={pid}#post-{pid}(Notification.php:219 / 262 / 397 / 441)。实测两个环节都是断的:

环节现状证据
?pid=被完全忽略ThreadController.php:220 只读 $_GET['page'];全项目无任何 pid → 页码换算逻辑
#post-{pid}从未被消费thread/show.php 楼层容器(:248-251)没有 id="post-{pid}";全页仅 id="replies"(:235)、id="post-body-{threadId}"(:150)

结论:今天所有通知点进去都只到帖子第 1 页顶部,不会跳到目标楼层。 这是既有缺陷,不是本次引入的——但楼中楼要精准跳转,就必须先把它解决。

1.5 插件形态:无后台页

plugins/floor_reply/plugin.json 现状:"activated": true、"permissions": []、无 admin_url、无 admin_route_register 钩子、无 AdminController.php。即:楼中楼目前没有任何可配置项。

对照范式(plugins/seo,同项目先例):admin_url: "/admin/seo" + hook/admin_route_register.php + AdminController.php + permissions: ["route:admin", "system:settings"]。

1.6 数据模型与可用素材

notifications 表(app/SplitDB/Schema.php:563-576):

列类型本方案用法
user_idINTEGER收件人
typeTEXT(20)mention / floor_reply
titleTEXT(500)通知主文案
linkTEXT(500)深链
actor_nameTEXT(100)触发者用户名
summaryTEXT(300)楼中楼内容摘要(比主题名更有信息量)
related_idINTEGERpost_id(楼层 ID)
related_typeTEXT(20)'floor'(新语义,为将来按楼层聚合预留)
agg_countINTEGER首版固定 1,阶段二可用于聚合

已存在但未启用的类型常量(Notification.php:19-47):

常量值现状
TYPE_MENTIONmention预留,从未触发 ← 本方案启用
TYPE_REPLY_QUOTEreply_quote已触发(注释错)

核心图标表(TemplateCompiler.php:611+)已确认可用:chat / comment(均 f075)、user(f007)、quote(f10d)、forum(f086)。

⚠️ comments 不在表中 → plugin.json 现有 "icon": "comments" 是失效值(静默回落),顺带修。

主题色变量:--mn-primary / --mn-success / --mn-warning / --mn-error 在 9 个主题文件(assets/css/themes/modern-*.css)中均有定义,可安全使用。


二、设计目标与边界

目标

  1. 楼中楼回复 → 让该知道的人知道,且不重复打扰。
  2. 通知点进去 → 精准落到那一条楼中楼(而不是帖子第 1 页顶部)。
  3. 保留站长控制权 → 后台可开关、可静音。
  4. 顺带把引用回复通知的两个既有缺陷一起修掉。

非目标(明确不做,避免范围失控)

  • ❌ 不做「楼内订阅」(同楼层的其他参与者不通知)
  • ❌ 不做「手打 @ 即通知」的引用回复扩展(引用回复维持现状:仅按钮触发)
  • ❌ 不做邮件 / 推送 / WebSocket
  • ❌ 不新增「通知设置」用户级偏好页(只做站长级开关)
  • ❌ 不改 fr_replies 表结构(零迁移)

三、通知对象与去重规则 ★核心决策

3.1 三个候选角色

设:用户 U 在某楼层 F(作者 A)中发了一条楼中楼,内容 @ 了 M,所在帖子楼主为 O。

角色是否通知理由
M(被 @ 的人)✅ 总是显式定向,最高优先级
A(楼层作者)✅ 总是(A ≠ U)"有人回了我的回复",对话的基本预期
O(楼主)⚠️ 默认否,开关控制见下

3.2 楼主为什么默认不通知

  1. 语义:楼中楼是「楼层内部的对话」,不是「对帖子的新回复」。核心已有 notifyReplyAggregated() 负责「帖子有新回复」的聚合通知。
  2. 行业惯例:Discourse / Flarum / Discuz 均只通知「评论作者」,不通知「主题作者」——除非被 @ 或是评论作者。
  3. 噪音:实测 post 1344 单个楼层已有 5 条楼中楼;热门楼层会对楼主形成刷屏。
  4. 可回退:做成后台开关(默认关),站长想要就开。

⚠️ 已知代价:楼中楼会 incrementReplyCount,楼主看到回复数 +1 却无通知,存在轻微感知不一致。这是刻意的取舍,由开关交给站长决定。

3.3 去重规则(硬约束)

recipients = {}
if M > 0 且 M ≠ U:  recipients[M] = 'mention'        # 优先级 1
if A > 0 且 A ≠ U 且 A ∉ recipients: recipients[A] = 'floor_reply'   # 优先级 2
if 开关开启 且 O > 0 且 O ≠ U 且 O ∉ recipients: recipients[O] = 'floor_reply_owner'  # 优先级 3

不变式:一次楼中楼回复,任一用户最多收到 1 条通知。

(U 永远不通知自己——与核心 notifyReply / notifyQuote / notifyVote 的既有约定一致。)

示例:

场景UAMO收件人
普通回复59–99(floor_reply)
回复 + @楼主59339(floor_reply)、3(mention)
@了楼层作者本人59999(mention,只 1 条)
楼主自己回自己楼层99–9无人
开楼主通知,回复他人楼层59–39(floor_reply)、3(floor_reply_owner)

3.4 多 @ 支持

现状:reply() 的 :98-101 只把第一个被 @ 用户存进 mention_user_id(该列仅用于列表高亮渲染)。

升级做法(推荐):通知时用全部,渲染仍用第一个。

// FrontController::reply() 已算出 $mentions['exists'] = [用户名 => uid](全部)
$mentionUids = array_values($mentions['exists']);   // ← 零额外查询
Notification::notifyFloorReply($postId, $threadId, $uid, $safeContent, $mentionUids);

✅ 不改表结构、不加查询、不影响渲染。

(阶段二可选:新增 mention_uids 列,让列表也显示全部 @ 对象。)

3.5 删除楼中楼时是否撤回通知

推荐:不撤回。 理由:通知是「历史事件」记录;撤回需要按 related_id + actor + 时间窗 反查,易误删且增加耦合。用户点进去看到内容已删,属正常。

(列为 §十三 待决项,若要撤回另议。)


四、通知类型与文案

4.1 类型方案

用途类型说明
楼中楼里被 @mention(启用已有常量)Notification.php:21 常量已存在,注释「预留」→ 改为已启用
楼中楼回复了我的楼层floor_reply(新增常量)语义独立,避免污染 notifyReplyAggregated 的 type='reply' 聚合查询
楼主兜底(开关开启时)floor_reply复用类型,文案不同

为什么必须新类型而不是复用 reply:notifyReplyAggregated()(:446-451)按 type = 'reply' AND related_id = threadId AND related_type = 'thread' 查未读并累加 agg_count。若楼中楼也用 reply,会被聚合进「N 条新回复」,且 related_type 语义冲突(帖子 vs 楼层)。

4.2 新增文案键(三语,lang/{zh,en,zh_tw}.php)

⚠️ i18n 红线:三语键集合零差异 + 键序一致(当前各 1340 键)。以下 3 键须在三个文件的同一位置插入。

键zhenzh_tw
plugin.notifications.floor_reply{user} 回复了你在「{title}」中的回复{user} replied to your reply in "{title}"{user} 回覆了你在「{title}」中的回覆
plugin.notifications.floor_reply_owner{user} 在「{title}」的楼中楼中回复了 {target}{user} replied in "{title}"{user} 在「{title}」的樓中樓中回覆了 {target}
plugin.notifications.mentioned{user} 在回复中提到了你,主题「{title}」{user} mentioned you in a reply, topic "{title}"{user} 在回覆中提到了你,主題「{title}」

插入位置建议:紧邻现有 plugin.notifications.quote(lang/zh.php:49)之后,三语同步。

({title} = 主题标题前 40 字;{user} = 触发者用户名,空则回落 plugin.notifications.someone。)

4.3 字段取值规则

字段取值
title上表文案(mb_substr 到 500,Notification::add() 内部已截断)
summary楼中楼内容摘要(strip_tags 后前 40 字)← 与 reply/vote 用主题名不同,楼中楼更有信息量
link/thread/{tid}?pid={pid}&fr={rid}#fr-{rid}(见 §六)
actor_name触发者用户名
related_id楼层 post_id
related_type'floor'(新语义,为阶段二按楼层聚合预留)
agg_count1(首版不聚合)


五、图标与配色

5.1 落点:改核心视图(唯一可行方案)

app/Views/notifications/index.php:13-40 的 $iconMap / $colorMap 各补 3 行:

// $iconMap 追加
'floor_reply' => 'chat',      // 楼中楼(对话气泡,f075)
'mention'     => 'user',      // @ 提及(f007)
'reply_quote' => 'quote',     // ★ 顺带修:既有缺失,原落 'info' 通用图标(f10d)

// $colorMap 追加
'floor_reply' => 'var(--mn-primary)',
'mention'     => 'var(--mn-primary)',
'reply_quote' => 'var(--mn-primary)',   // ★ 顺带修

✅ 所有图标名已在 TemplateCompiler.php 图标表中确认存在;--mn-primary 在 9 个主题文件中均有定义(夜间主题自动适配)。

5.2 为什么不做成钩子

可以新增 notifications_icon_map 钩子(app/Views/notifications/index.php 是视图,符合 controller_view_before 的改造模式),但:

  • 只为 3 个映射项引入 1 个新钩子 + 1 个插件钩子文件,性价比低;
  • 新钩子一旦被注册就不能改名(插件开发规范.md §9.1 注),等于永久 API 负担。

推荐直接改核心(3 行 × 2 处)。若你更希望「插件零侵入核心」,可改用钩子方案,代价是多 2 个文件。


六、深链定位策略 ★核心决策

6.1 目标链接形态

/thread/{tid}?pid={pid}&fr={rid}#fr-{rid}
      ↑          ↑          ↑        ↑
      帖子      楼层ID    楼中楼ID  楼中楼条目锚点

三段各解决一个问题:?pid= → 跳到正确页码;?fr= → 展开正确楼层的楼中楼并定位到具体那一条;#fr-{rid} → 原生滚动兜底。

6.2 三个方案对比

方案做法优劣结论
A. 只跳帖子维持现状,link 只带 tid零改动、零风险点进去还是看不到内容 → 通知等于废掉❌ 不可接受
B. 纯插件 JS插件 JS 读 ?fr=,自己 fetch 算页码后 location.replace() 重载不碰核心需要 2 次页面加载(闪烁);#post-{pid} 锚点仍缺;既有 4 类通知照样跳不准⚠️ 次选
C. 核心+插件分工(推荐)核心支持 ?pid= 算页码 + 楼层加 id="post-{pid}";插件支持 ?fr= 展开定位一次加载即到位;同时修好既有 4 类通知;每处改动都很小需改 2 个核心文件✅ 推荐

6.3 推荐方案(C)的实现要点

核心侧(2 文件,约 25 行)

① app/Controllers/ThreadController.php — show() 在 :220 之前插入页码推导:

// ?pid= 深链:把「回复 ID」换算为「所在页码」,优先于 ?page=
// 口径必须与 Post::getByThread() 完全一致(同桶同表同排序),否则页码会漂
$page = max(1, min($totalPages, (int)($_GET['page'] ?? 1)));
$pidHint = (int)($_GET['pid'] ?? 0);
if ($pidHint > 0) {
    $p = $postModel->pageOf($threadId, $pidHint, $perPage);   // 见 ②
    if ($p > 0) $page = min($totalPages, $p);
}

② app/Models/Post.php — 新增 pageOf():

/**
 * 回复 ID → 所在页码(口径与 getByThread 严格一致:同桶同表同排序)
 * @return int 0 = 该回复不存在或已删(调用方保持原页码)
 */
public function pageOf(int $threadId, int $postId, int $perPage): int
{
    $idx = $this->fetchThreadIndex($threadId);
    if (!$idx) return 0;
    $bdb = Schema::bucketFromPath($idx['bucket_path']);
    $st = $bdb->prepare('SELECT create_time FROM reply WHERE id = ? AND pid = ? AND status = ?');
    $st->execute([$postId, $threadId, self::STATUS_NORMAL]);
    $ct = $st->fetchColumn();
    if ($ct === false) return 0;                 // 不存在/已删 → 不跳
    $st2 = $bdb->prepare(
        'SELECT COUNT(*) FROM reply WHERE pid = ? AND status = ? AND (create_time < ? OR (create_time = ? AND id < ?))'
    );
    $st2->execute([$threadId, self::STATUS_NORMAL, $ct, $ct, $postId]);
    return (int)floor(((int)$st2->fetchColumn()) / max(1, $perPage)) + 1;
}

✅ 为什么不用 reply_index 算:getByThread() 读的是桶内 reply 表(Post.php:252-257),而 reply_index 是主库全局索引。两者理论上可能漂移(项目已知有计数器漂移史)。同桶同表同排序才能保证页码与实际渲染逐字节一致——这也顺带规避了 idx_reply_index_* 的索引选择风险。

③ app/Views/thread/show.php — 楼层容器(:248)补 id:


⚠️ 注意 #replies(:235)带 hx-boost + hx-target="#replies"。锚点加在循环内的楼层容器上,htmx 局部替换 #replies 时会随之重建——这是期望行为。

插件侧(3 文件)

① FrontController::list() — 支持 ?focus={rid}:

$focus = (int)($_GET['focus'] ?? 0);
// 若 focus 命中本楼层,则强制渲染该条所在页(而非第 1 页),并标记高亮

② Plugin.php — 新增 pageOfReply():

/** 楼中楼 ID → 所在页(口径与 getByPost 一致:post_id + id ASC) */
public static function pageOfReply(int $postId, int $replyId, int $perPage = 5): int

③ assets/script.js — 深链自动展开:

页面加载 →
  读 location.search 的 pid / fr
  若 pid 命中某楼层锚点 #fr-anchor-{pid}:
     自动触发该楼层的 htmx 展开(带 focus=fr)
     等 htmx:afterSwap 后:
        高亮 [data-fr-id="{fr}"](加 .fr-focus 类,2 秒后淡出)
        scrollIntoView({block:'center'})

④ assets/style.css — 高亮样式(**只能用既有 --mn-* 变量**):

.fr-item.fr-focus { background: var(--mn-bg-row-hover); outline: 2px solid var(--mn-primary); }

6.4 风险与回滚

风险缓解
?pid= 与 ?page= 冲突pid 优先,page 作回退;pid 非法/已删则静默用 page(pageOf 返回 0)
老链接(无 pid)完全不受影响($pidHint = 0 直接跳过)
分页链接被污染pagination() 生成的分页链接不含 pid → 翻页后回到正常分页,符合预期
回滚核心 3 处改动互相独立,任一可单独还原;pageOf() 是新增方法,删除即无副作用

⚠️ 附加收益:方案 C 落地后,既有 4 类通知(reply / vote / reply_quote / reply_aggregated)的深链同时被修好(它们本就在拼 ?pid=…#post-,只是没人消费)。这是本方案最划算的一笔。


七、插件后台开关(可选但推荐)

7.1 范式(照搬 plugins/seo)

文件动作
plugin.json加 "admin_url": "/admin/floor-reply";permissions 改 ["route:admin", "system:settings"]
hook/admin_route_register.php新建:注册 GET /floor-reply + POST /floor-reply/save
AdminController.php新建:读/写开关
views/admin.php新建:后台表单
lang/{zh,en,zh_tw}.php加后台文案键

7.2 开关项建议

键(存核心 `settings`)默认说明
floor_reply_notify_floor_author1通知楼层作者
floor_reply_notify_mention1通知被 @ 的人
floor_reply_notify_thread_owner0通知楼主(默认关)
floor_reply_auto_expand1深链自动展开楼中楼

配置写入:Settings::update($key, $value, 'floor_reply') —— 显式传插件名,走 system:settings 权限校验(Settings.php:255-271,SEO 插件同款先例)。

7.3 ⚠️ 部署必带 plugins_cache.json

plugin.json 改动(新增 admin_url / permissions / 新钩子)不会自动生效 —— 必须一并更新 plugins/plugins_cache.json(update/25 正是这么做的)。漏了会导致后台入口不出现。


八、顺带修正项(低成本、高确定性)

#问题位置修法
1说明文档带 BOM(ef bb bf)plugins/floor_reply/说明.md 首 3 字节去 BOM(项目约定 UTF-8 无 BOM + LF)
2说明文档路由写错同上「三、目录结构」章节实际为 /floor-reply/reply、/floor-reply/list/{id}、/floor-reply/delete;文中写的 /floor-reply 与 /floor-reply/list 不存在
3TYPE_REPLY_QUOTE 注释过时Notification.php:22「预留:无引用检测,暂不触发」→「引用回复已启用(V1.0.26 起生产已验证)」
4TYPE_MENTION 注释过时Notification.php:21「预留:无 @ 解析,暂不触发」→「楼中楼 @ 提及(本版启用)」
5plugin.json 图标失效plugins/floor_reply/plugin.json"icon": "comments" → "chat"(comments 不在核心图标表,静默回落)
6reply_quote 通知无图标index.php:13-40见 §5.1(已含)


九、改动清单(文件级)

核心(5 文件)

文件改动行数
app/Helpers/Notification.php新增 TYPE_FLOOR_REPLY 常量;新增 notifyFloorReply();改 2 处过时注释~55
app/Views/notifications/index.php$iconMap +3、$colorMap +36
app/Controllers/ThreadController.phpshow() 支持 ?pid=~6
app/Models/Post.php新增 pageOf()~18
app/Views/thread/show.php楼层容器加 id="post-{pid}"1
lang/{zh,en,zh_tw}.php各 +3 键(同位置同序)9

插件(9 文件)

文件改动状态
plugins/floor_reply/FrontController.phpreply() 加通知调用;list() 支持 ?focus=改
plugins/floor_reply/Plugin.php新增 pageOfReply()改
plugins/floor_reply/assets/script.js深链自动展开 + 高亮改
plugins/floor_reply/assets/style.css.fr-focus 样式改
plugins/floor_reply/views/list.php高亮标记改
plugins/floor_reply/plugin.jsonadmin_url / permissions / icon 修正 / 新钩子改
plugins/floor_reply/AdminController.php后台开关读写新建
plugins/floor_reply/hook/admin_route_register.php注册后台路由新建
plugins/floor_reply/views/admin.php后台表单新建
plugins/floor_reply/lang/{zh,en,zh_tw}.php后台文案键改
plugins/floor_reply/说明.md去 BOM + 修路由 + 补通知章节改
plugins/plugins_cache.json重新生成改


十、验证方案

层手段判据
语法php -l(8.0.2 + 8.2.9)、node --check全 PASS
编码0 BOM / 0 CRLF / UTF-8全 PASS
i18n三语键集合差异 0 + 键序一致 + 模板引用键全量比对1340 → 1343,三语一致
逻辑(桩)Node DOM 桩:pageOf 边界(首条 / 末条 / 不存在 / 已删)断言全过
去重构造 §3.3 全部 5 种场景,断言收件人集合与类型与表逐条一致
E2Ephp -S + 手写 sessions 行登录态,实发楼中楼 → 查 notifications 表行数/类型/link 正确
深链点通知 → 断言落在正确页码 + 楼层展开 + 条目高亮视觉 + ?pid 生效
回归verify_b7_csrf.php / verify_d6_cache.php / test_p5_content_img.php / test_p8d_img_group.php / test_trash_preview.sh零回归
A/B 反证核心 ?pid= 改动前后各跑一次深链,比对落点反证成立后 sha256 还原一致
清理访客会话 0、测试通知行删除、页面缓存清空、php -S 停止无残留

⚠️ 测试通知会写入生产 business.sqlite.notifications —— 必须记录插入的 id 并在测后删除,避免污染真实用户的通知中心。


十一、发布形态

推荐:update/27/ 增量包(因核心文件也改了)。

沿用 update/26 体例,包内镜像站点根结构:

update/27/
├── app/
│   ├── Controllers/ThreadController.php
│   ├── Helpers/Notification.php
│   ├── Models/Post.php
│   └── Views/{notifications/index.php, thread/show.php}
├── lang/{zh,en,zh_tw}.php
├── plugins/
│   ├── plugins_cache.json
│   └── floor_reply/**
├── version.php            ← V1.0.27
└── 改动说明.md

部署注意(沿用既有红线):

  1. 只传「代码 + 新增的 md_*」;data/ 与 assets/uploads/ 绝不覆盖;
  2. 不要传 sessions.sqlite(会把全站踢下线);
  3. 部署后必须走后台「维护 → 清理缓存」(data/runtime/pages/*.html 是永久缓存);
  4. 插件新增 AdminController.php + 新钩子 → 必须同时更新 plugins_cache.json;
  5. sw.js 的 CACHE_VERSION 由 SW_VERSION 派生 → 若前端资源有改动,version.php 升版即可自动破缓存。

若你希望「只发插件、不升核心版本」:可改走 update/25 的纯插件包先例(update/25 就只有 plugins/ + 改动说明.md,无 version.php)。但那样 §六 的核心深链修复必须放弃或延后——通知点进去仍跳不准。不推荐。


十二、分批建议

批次内容价值风险
第 1 批(必做)§三 通知逻辑 + §四 文案 + §五 图标 + §八 顺带修正楼中楼从「完全静默」到「有人知道」低
第 2 批(推荐)§六 深链(核心 ?pid= + 楼层锚点 + 插件 ?fr=)通知从「知道」到「一键到位」;顺带修好既有 4 类通知中(核心改动,可独立回滚)
第 3 批(可选)§七 后台开关站长可控低

建议 1 + 2 一起做:第 1 批产出的通知如果不带第 2 批的深链,用户体验是「收到通知 → 点进去 → 看不到内容」,等于半个废功能。


十三、待你决策的 5 个问题

  1. 楼主通知:同意「默认不通知 + 后台开关」吗?(§3.2)
  2. 深链:第 2 批(核心 ?pid=)做不做?它同时修好既有 4 类通知,但需改 2 个核心文件。(§六)
  3. 后台开关:第 3 批做不做?(§七)
  4. 多 @ 支持:确认「通知全部被 @ 的人、渲染仍只显示第一个」?(§3.4)
  5. 发布形态:update/27/ 增量包(含核心)还是纯插件包?(§十一)

附:本次核查使用的证据来源

结论证据
楼中楼零通知FrontController.php:61-127 全文
生产 28 条 / 4 条带 @ / 0 条自 @直查 plugins/floor_reply/data/floor_reply.sqlite
引用回复已发通知生产 notifications 表 6 条 reply_quote
通知基础设施零钩子4 个文件全文 + 插件开发规范.md §9.1(40 钩子表)
?pid= 被忽略ThreadController.php:220 只读 $_GET['page']
#post-{pid} 无消费方grep id="post- 于 thread/show.php → 仅 post-body-{threadId}
reply_quote 无图标index.php:13-27 $iconMap 无该键 → 落 ?? 'info'
comments 图标不存在TemplateCompiler.php:611-760 图标表
--mn-primary 等 9 主题均定义assets/css/themes/modern-*.css:12,43-45
notifications 表结构app/SplitDB/Schema.php:563-576
插件后台页范式plugins/seo/{plugin.json,hook/admin_route_register.php,AdminController.php}
纯插件包先例update/25/ 仅含 plugins/ + 改动说明.md
Post::getByThread() 口径app/Models/Post.php:243-260(桶内 reply 表,create_time ASC, id ASC)

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 5 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录