[开发]排行榜插件(leaderboard)

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

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/leaderboard/说明.md。

排行榜插件(leaderboard)

右栏排行榜面板:积分 / 主题 / 回复 / 签到 四榜 TOP6,纯 CSS 切换 tab。


一、挂载位置(方案 B)

复用核心通用侧栏钩子 sidebar_above_friend_links,零核心改动。

端容器渲染顺序
PCaside.mn-right(right_sidebar.php)用户面板 → 站点统计 → 热门标签 → 排行榜 → 友情链接
移动.mn-mobile-sidebar(mobile_sidebar.php)热门标签 → 排行榜 → 友情链接

  • 该钩子一次页面渲染触发两次(PC 右栏 + 移动侧栏各一次),两份 DOM 靠核心媒体查询切换显隐,

因此「同一页面出现两份面板」是正确行为。

  • ⚠️ 移动端那份被核心包在 if (!$mobileIsTagPage) 内 → **/tags 与 /tag/* 页面只有 1 份**(PC 右栏)。
  • ⚠️ 移动端侧栏没有站点统计面板,故移动端排行榜位于热门标签下方。

二、四个榜的口径

tab数据源口径
积分data/meta/business.sqlite → usersstatus='active' AND points>0,按 points DESC, id ASC
主题data/meta/main_index.sqlite → topic_indexstatus=0 AND deleted_at IS NULL,按 uid 计数
回复data/meta/main_index.sqlite → reply_indexstatus=0,按 uid 计数
签到plugins/daily_checkin/data/daily_checkin.sqlite按 uid 计数(累计签到天数)

  • 站长不排除(榜单为真实数据)。
  • 用户名 / 头像:主题 / 回复 / 签到三榜拿到 uid 后统一走 User::batchGetUsers();

用户名缺失(用户已注销)的行直接丢弃,避免死链。

  • 并列名次用 uid ASC 稳定排序。

⚠️ 签到榜为什么直读 daily_checkin 表

实测(2026-09-27):points_log 里 related_type='daily_checkin' 只有 4 条,

而 daily_checkin 表有 63 条 —— 差 15 倍,历史签到并未全部写入积分日志。

走积分日志统计会严重失真,故必须直读签到表。

跨插件边界处理(规范 §11.6 灰区):不调用对方 Plugin 类、不写对方表,仅只读聚合;

并以三重守卫降级 —— Plugin::isActivated('daily_checkin') + 库文件存在 + 表存在,

任一不满足则该 tab 显示「暂无数据」(不影响其余三榜)。


三、缓存机制(性能)

右栏每页都渲染,故不能在渲染路径做全表聚合。

渲染钩子(每页)
  └─ 读 lb_cache(1 次查询,全表仅 4 行)
       ├─ 数据新鲜 → 直接渲染
       └─ 已过期   → 仍渲染旧数据(不阻塞),置 $GLOBALS['lb_need_refresh']

route_after_dispatch(index.php:321,响应产出之后)
  └─ 若标志为真 → 重算 4 榜 + 回写(4 次聚合 + 1 次 upsert)

  • 正常请求的 hook 查询数 = 1(缓存表)+ 1(batchGetUsers)= 2 次(规范红线 ≤5 次)。
  • 重算发生在响应之后,不占用户等待时间;未过期的请求走 1 次 if 判断即返回,零查询。
  • 冷启动(首次启用 / 缓存被清):渲染钩子内同步算一次,保证首屏不留空面板。
  • TTL = 300 秒(与生产云函数 5 分钟 cron_trigger.php 节奏一致)。
  • 请求内静态缓存:双触发时第二次调用复用同一份数据,不重复查库。
  • ⚠️ 游客的列表页走核心 PageCache(整页 HTML 永久缓存),榜单数据会随之变旧;

登录用户不走 PageCache,看到的是 TTL 内的数据。这是核心缓存机制决定的,非本插件引入。


四、tab 实现:纯 CSS,零 JS

  • 4 个 <input type="radio"> + 4 个 <label for> + 4 个 pane。
  • 显隐由 :checked ~ .lb-panes .lb-pane:nth-child(N) 控制;激活态由 :checked ~ .lb-tabs .lb-tab:nth-child(N) 控制。
  • 对应关系靠 :nth-of-type(N)(radio,按 input 标签计数)/ :nth-child(N)(label、pane)建立,不依赖 id 语义。
  • ⚠️ 双份面板的 radio 若同名会变成同一个 radio group → 点移动端 tab 会同时改掉 PC 端选中态。

故 radio 的 name / id 带渲染序号(插件自实现 static 计数器 Plugin::nextSeq()),两份面板互不干扰。

  • 服务端一次渲染全部 4 个 pane,切换零请求。

五、文件结构

plugins/leaderboard/
├── plugin.json                              # hooks: init_after / sidebar_above_friend_links
│                                            #        / route_after_dispatch / layout_head_end
├── Plugin.php                               # 独立库 / 建表 / 生命周期 / 4 榜聚合 / 缓存读写
├── hook/
│   ├── init_after.php                       # 惰性建表兜底(戳文件门控)
│   ├── sidebar_above_friend_links.php       # 面板渲染(双触发钩子)
│   ├── route_after_dispatch.php             # TTL 懒刷新(有触发源收敛)
│   └── layout_head_end.php                  # 注入 style.css(带 ?v=filemtime)
├── assets/style.css                         # 仅补 tab 条 + 排名序号/头像;颜色全走 --mn-*
├── lang/{zh,zh_tw,en}.php                   # 6 个键,三语键集合与键序一致
└── data/
    ├── leaderboard.sqlite                   # 独立库(lb_cache 表)
    ├── schema.version                       # 数据层版本号戳文件
    └── .htaccess / web.config               # 自动生成,防 Web 直读

可调常量(Plugin.php 顶部):TOP_N = 6、TTL = 300。

改动 ddl() 时必须同步递增 SCHEMA_VERSION,否则老站点戳文件命中、新表建不出来(规范 §11.15)。


六、设计约束(改动前必读)

  • 零内联:样式全在 assets/style.css,无内联 <style> / on* / style=""(规范 §7.3 P0)。
  • **颜色只能用既有 --mn-***:当前用到 --mn-text / --mn-text-muted / --mn-primary /

--mn-primary-subtle / --mn-border-light,五者在 9 套主题 + modern.css 中均有定义。

⚠️ --mn-bg-hover 不存在、--mn-danger / --mn-spectrum-* 不可用(已避开)。

  • 选择器一律带 .lb-panel 前缀提权:核心同特异性声明因源序靠后会静默压掉插件样式。
  • 渲染钩子里 $this 不可用(闭包作用域,规范 §11.24):用 htmlspecialchars()、

Upload::url()、I18n::get(),图标用核心字号类 <i class="fa mn-fs-12">(不用 $view->icon(),

其返回值带内联 style)。

  • 钩子文件禁止声明顶层函数/类(规范 §9.0):sidebar_above_friend_links 双触发,

一旦声明会被降级为 include_once,第二处入口静默消失。

  • 不做的部分:无独立榜单页、无「更多」链接、无后台配置页(改常量即可)。
  • 改 hooks 清单后必须 \app\Helpers\Plugin::refresh() 重建 plugins/plugins_cache.json;

部署后须走后台「维护 → 清理缓存」。


七、验证记录(2026-09-27)

套件结果
数据层(4 榜 vs 手写 SQL 逐行对照 / 缓存落盘 / TTL 判定 / 数据形状)17 PASS / 0 FAIL
结构(顶层符号 / 双 include / 请求内缓存 / i18n 三语 / CSS 卫生 / 零内联 / 生命周期)35 PASS / 0 FAIL
渲染产物(真实 HTTP,首页 + 论坛页)58 PASS / 0 FAIL
tab 选择器对应关系(jsdom 真实 DOM)50 PASS / 0 FAIL
卸载与冷启动16 PASS / 0 FAIL
TTL 懒刷新端到端(HTTP)未过期不重算 ✓ / 过期后重算 ✓
protected/error.log无新增、零 leaderboard 记录

未验证项(交给站长自测):双主题(默认浅色 + 暗夜星辰)视觉观感、面板实际观感与右栏整体协调度。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| Views 3 | Replies 0

All Replies (0)

No replies yet

Please Log in