- 主题参与者摘要 · 增强ID topic_participant_summary_plus版本 1.6.1插件制作者 one免费325 行 / 22.3 KBHook / 后台页在主题详情页以头像墙展示本贴参与者,直观呈现每位成员的发言次数与参与顺序,并可一键跳转到其最近楼层。后台可设置头像墙人数上限、参与者排序(最先参与或最近参与优先)以及头像尺寸档位,自适应不同规模的讨论。app/plugins/topic_participant_summary_plus/plugin.php开发日志已有 7 条主楼点赞与点评3
one2026-08-17
bbs1org2026-08-18
charcoal-fire2026-08-18
插件质量报告
插件:
topic_participant_summary_plusHook 功能范围 循环 使用频率 文件读写 修改系统表 读写系统表 读写自己的表 topic.content_after 非系统hook 否 1 - - app_replies, app_topics, app_users - 颜色说明:黄色表示有数据读写,红色表示 Hook 在系统循环中执行,浅蓝色表示有文件写入。“列表跳过”表示回调在列表路径会提前返回,“回调内”表示操作位于回调自身或其帮助函数的循环体内。“文件批处理”表示逐个处理用户一次提交的文件,属于必要操作,不标记修正。只有操作实际处于需要优化的循环路径时,才会标记“修正❗️”。
数据字典
未检测到插件数据表声明。
版本 1.3.6 更新:
原始作者:307
原贴地址:https://bbs1.org/topic/428一、安全问题修复
- CSRF 防护:保存设置前校验 hash_equals(csrf_token(), $_POST['_csrf']),杜绝跨站请求伪造
- 参数边界校验:max_participants≥0、size_lg_max 1100、size_sm_min 2200,非法值不落库
- 数值关联约束:保证大尺寸上限 < 小尺寸下限,保存时自动调整为"下限 − 1"
- 展示顺序白名单:order 非 latest 一律回落为 earliest,拒绝非法值注入
- 匿名身份保护:匿名主楼/回帖不计入摘要、不渲染,避免暴露作者
- XSS 防护:JSON 输出 JSON_HEX_TAG/APOS/QUOT 转义,DOM 文本用 textContent,链接用 href 赋值
- 最小权限:无 exec 等危险函数,不写文件,仅声明 1 个钩子 + 后台管理页
二、性能问题修复
- 消除 N+1:无循环内查询,全部改为两次独立 SQL + 内存聚合
- 单次全量汇总:UNION ALL 合并主楼 + 全部回帖,一次分组统计全贴参与者
- 配置请求级缓存:static $cfg 缓存,单请求只解析一次配置
- 聚合结果缓存:按 topic_id:max:order 静态缓存,避免重复渲染重复查询
- 服务端零 DOM 拼接:只输出 JSON 数据,DOM 由前端 JS 构建
- 防重复渲染:页面已有摘要时跳过,仅渲染一次
三、新增功能
- 后台设置面板:新增管理页,可配置头像墙人数上限、展示顺序、尺寸阈值
- 头像三档自适应:按展示人数自动切换 48px / 36px / 24px,含移动端适配
- 发言次数徽标:头像右下角显示该用户发言次数
- 楼层直达链接:点击头像跳转到该用户最近回复楼层(主楼作者显示"主贴")
- 悬停提示:显示"用户名 · 发言 N 次 · 楼层"
- 截断计数文案:"显示前 N 位,共 M 位"
- 头像兜底:图片加载失败自动替换为首字母占位
- 无侵入渲染:仅主题详情页渲染,列表页不显示,不存储用户数据
最后由 one 编辑于 2026-08-17 20:13- bbs1org2026-08-17
头像排列可以优化一下,改成纵向也对齐
版本 1.3.9 更新:
细节调整:大头像时参与者头像之间更宽松,小头像时更紧凑,视觉比例协调。
漂亮,这效果把自己都美哭了- bbs1org2026-08-17
感谢之前作者307和你的付出。我修改捡漏的。
还有,你那个积分规则,我一直用不成功。下午又全新重装了论坛测试,安装积分规则后,就出错:
自动停用原因
2026-08-17 22:44:27 PDOException: SQLSTATE[HY000]: General error: 1 cannot start a transaction within a transaction (index.php:316)- bbs1org2026-08-17
- bbs1org2026-08-17
🔍 插件审查报告(对照《AI 开发规则》与开发者文档)
🔒 安全问题
未发现明显问题。JSON 载荷用
JSON_HEX_TAG/APOS/QUOT转义后放入<script type="application/json">,头像由核心avatar_tag()(内部已 h 转义)生成,姓名用textContent输出;匿名主楼被排除。⚡ 性能问题
topic.after_render属红区钩子,但topic_participant_summary_plus_after_render(plugin.php:114)直接调用..._participants(),内部执行两次q()(plugin.php:47-58、61-64)查库。虽靠静态缓存和“仅主楼”判断把开销限制在每主题页 2 次,仍违反“渲染循环零数据库读取”。建议把聚合查询移到topic.after_view预加载,渲染钩子只读内存。
🐛 功能/规范缺陷
- 参与者按
strcmp($a['first_at'], $b['first_at'])(plugin.php:89-93)排序,但 first_at 是整数时间戳(plugin.php:83 被转成字符串),不等长数字会排错。请保留 int 并用<=>比较。 - CSS 中
border-radius:4px(plugin.php:152 多处)写死,建议改用var(--radius)派生值。
✅ 修复优先级
先改红区 DB 读为预加载 → 修正时间戳比较 → CSS 圆角变量。
版本 1.5.0 更新:
感谢 @350,现修复以下内容:排序 bug(真 bug)
- plugin.php:89-93 的 usort 从字符串比较 strcmp($a['first_at'], $b['first_at']) 改为整数比较
created_at 是 uint 整数时间戳,之前转字符串后逐位比较,遇到不相等位数(尤其极早期 9 位时间戳或空值)会错排。现在强制 (int) 比较,稳健。
渲染钩子零查库(规范层)
- 新增 topic_participant_summary_plus_after_view($ctx):在 topic.after_view 钩子触发后(主题数据加载完、渲染前),提前调用 participants() 并写入 static $cache。
- return 数组注册 'topic.after_view' => '...after_view'。
- 渲染钩子 after_render 调用 participants() 时直接命中静态缓存,渲染阶段零数据库读取。
#其他安全性和细节的修复。
🔍 复核报告(v1.5.0,回应 1.3.9 版审查)· @one
✅ 已确认修复
- 排序 bug:usort 已从 strcmp 改为
(int)整数比较 + id 决胜(plugin.php:91-93),修复正确。 - 后台 lg≥sm 改为显式报错不落库、CSRF hash_equals 校验——均正确。
❌ 红区修复未生效(关键,需再改一行)
新增的
topic.after_view预热回调是单参签名(?array $ctx),但核心 hook()/fire() 对回调固定传两参($value=null, $ctx)(index.php:697)——第一个参数 null 会绑定到$ctx,真正的上下文数组被丢弃,函数在is_array(null)处直接 return,预热从未执行。结果 topic.after_render 首次渲染时仍会在渲染链内执行 2 条 DB 查询(participants() 两处 q()),红区违规与原状相同。一行修复(规范②:fire 回调必须两参):
function topic_participant_summary_plus_after_view($value, array $ctx): void💡 顺带优化
预热的楼层计算会全量拉取该主题所有回帖(仅用于算每人最后楼层),万楼主题下每页视图 O(N) 行读取;可只 SELECT user_id,或复用 ctx['replies'] 与 reply_count 推导。
其余(JSON_HEX 转义防 XSS、命名前缀、assets、description)均合规。签名改完这个插件就干净了。
- 排序 bug:usort 已从 strcmp 改为
🔍 复核报告(v1.5.1,回应 1.5.0 版审查)· @one
✅ 已确认修复
- 排序 bug:usort 已改为 (int) 整数比较 + id 决胜(plugin.php:87-88),latest 倒序正确——修复正确。
- lg≥sm 后台显式报错不落库(plugin.php:276-278)、CSRF hash_equals 校验(plugin.php:268)——均正确。
- 红区关键项:topic.after_view 回调已改为两参签名(plugin.php:102),核心 fire('topic.after_view')(index.php:2951)把上下文正确传入 $ctx,预热 participants()(plugin.php:109)在 topic.after_render(plugin.php:112)之前填充静态缓存,渲染链零 DB 读——修复生效。
❌ 未生效 / 新问题
未发现明显问题。
💡 顺带建议
聚合查询改用窗口函数 ROW_NUMBER() OVER (...)(plugin.php:56),MySQL 8.0 / MariaDB 10.2 以下不支持;规则要求兼容 SQLite/MySQL/PostgreSQL,建议确认目标库版本或改用兼容写法。
这个插件现在干净了。
🔍 插件审查报告(AI 自动审查,对照《AI 开发规则》与开发者文档;本插件未被 8/18 楼上 350 的报告覆盖,属独立首次审查)
插件:主题参与者摘要 · 增强(topic_participant_summary_plus)v1.5.1
结论:已完成命名规范、生命周期(install/uninstall 幂等)、数据库跨库兼容、红区 Hook 零 DB 读、Hook 真实性(含核心与跨插件依赖核对)、SQL 注入风险、CSRF/权限校验、CSS 变量规范等项审查,未发现安全、性能或功能性问题。
版本 1.6.0 更新:
Changed
- 渲染注入机制从红区钩子
topic.after_render(配合topic.after_view预热)切换为核心专用非红区钩子topic.content_after(仅主题详情页主楼触发),渲染位置不变;回调遵循管道追加语义(拼接$value返回),与同钩子的其他插件(如 topic_manage)共存不互相覆盖。 - 前端挂载逻辑改为锚定插件自身的 payload
<script>(服务端已注入到主楼 post-content 末尾)相邻插入,不再依赖核心内部 DOM 结构选择器(ul.post-list.topic-post-list、li.post-item.post-entry),符合核心 data-slot 定位规范。 - CSS 圆角规范修正:面板圆角由不存在的
var(--radius-md,8px)(fallback 恒生效,等同写死 8px)改为var(--radius);设置页提示border-radius:4px改为var(--radius-sm);清理已定义变量的冗余 fallback。
Fixed
- 修复性能缺陷:原
topic.after_view预热在主题页每个分页(p≥2)都会执行一次全帖聚合 SQL,但主楼仅在 p=1 渲染、摘要也仅在 p=1 显示,第 2 页起查询全部浪费;切换钩子后聚合查询仅在真正渲染摘要时执行一次。
Removed
- 删除无引用的死代码:CSS 规则
-title-placement、-order-row(非 settings- 版)、-order-label,移动端媒体查询中与基础规则重复的size-sm头像尺寸;JS 辅助函数mainPost()及对应的列表查询、fallback 插入分支。 - 删除无理由的
!important(实测核心 CSS 无任何label元素选择器,不存在需要对抗的冲突)。
- 渲染注入机制从红区钩子
版本 1.6.1 更新:
Fixed
- 后台保存配置时对「头像墙最多显示人数」增加服务端上限钳制(≤500,与表单上限一致),修复伪造 POST 可写入超限值的安全隐患。
- 前端重建头像
<img>时补回loading="lazy"与decoding="async",与服务端avatar_tag()的懒加载行为一致,外域头像不再阻塞首屏渲染。
Changed
- 头像底色
color-mix()前置var(--brand-soft)回退声明,不支持color-mix的旧浏览器不再出现透明底色。 - 前端尺寸档位判定(sizeClass)的异常回退默认值与 PHP 归一化口径对齐(lg=8、sm=21),保证三档不退化。
- 合并前端三处首字母回退元素的重复构造为局部辅助函数,消除重复代码。