本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:合并篇(见正文内 merged_from 列表)。
信任等级(user_level)插件 · 开发计划与任务体系三阶段记录
本篇由 10 篇原始文档合并去重而成:开发计划、改造方案、自包含审计、三阶段任务体系修复记录、插件使用说明。
时间线:开发计划与阶段 A/B/C 交付(2026-09-11)→ 全量逻辑检查与任务体系三阶段修复(2026-09-12)。
一、插件定位与等级模型
借鉴 Discourse 的 TL0–TL4 信任等级体系,作为独立 FlintHub 插件与核心既有「积分等级(Points Lv1-11)」并存,两者概念不同、互不冲突。核心能力:行为流水自动考核、能力门控(发帖/回帖/上传/私信/邀请 + TL0 单帖外链上限)、楼层/头像/列表三种徽章渲染、访问日历(/u/level/checkin)、偶像/粉丝列表(/u/level/idols、/u/level/fans)、历史补算(幂等可重跑)、升级时经核心 Notification 发系统通知。
等级划分
| 档位 | 获取方式 |
|---|---|
| TL0 新用户 | 默认档,未达任何考核门槛 |
| TL1 / TL2 / TL3(见习/正式/骨干) | 自动考核,按窗口行为指标逐档判定 |
| TL4 核心用户 | 只能手动授予(ul_users.manual_lv >= 4) |
| 锁定档 | ul_users.locked_lv > 0 时直接采用锁定值,优先于手动档与自动考核 |
判定优先级(Plugin::calc() / calcForLevels(),从高到低):① locked_lv > 0 → 取 min(4, max(0, locked_lv)) 直接返回;② manual_lv >= 4 → TL4;③ 逐档考核 TL1→TL2→TL3,任一项未达标即该档不通过,最终等级 = 通过的最高档,全不通过则 TL0。
六类行为指标与升级规则
事实源为流水表 ul_logs:
| 指标 | 记录时机 |
|---|---|
visit | 每次请求(登录用户,每天 1 条,route_after_dispatch) |
read | 浏览他人主题(controller_view_before,thread/show) |
reply | 发布回复(post_create_after;回自己的主题不计) |
topic | 发布主题(thread_create_after) |
like | 点赞他人内容(vote_after,like_topic+like_reply 合并),参与升级条件 |
recv_like | 被他人点赞(vote_after 联动记录),仅展示统计,不参与升级 |
- 每档独立时间窗:
t1/t2/t3各带win(天数),win=0累计不截断,win>0近 N 天窗口; - 达标口径(
targetMet()):某档需求中>0的项必须全部满足,=0的项视为不要求; - 不发积分(决策 D3):访问签到只驱动
visit_days指标,积分奖励收敛到核心daily_checkin。
默认考核门槛(defaultCfg(),后台可改):
| 档 | win(天) | visit | read | reply | topic | like |
|---|---|---|---|---|---|---|
| t1 | 0(累计) | 2 | 3 | 0 | 0 | 0 |
| t2 | 0(累计) | 7 | 12 | 6 | 3 | 5 |
| t3 | 0(累计) | 30 | 60 | 25 | 8 | 15 |
两套易混体系:本插件管「信任等级」(
ul_users.map_lv,类名.user-level-badge),核心另有「级别」(users.level+level_config,类名.level-badge),排查时先确认看的是哪一个。
二、开发计划要点
2.1 定位与五个拍板决策
Discourse 式信任等级移植到 FlintHub 插件框架;点赞是核心能力(/api/vote + app\Helpers\Vote + vote_after),like 指标不依赖第三方插件。D1 徽章注入:允许核心模板最小整合(与既有 $wearingMedals/$pmTitles 平行加 1 行占位,同步 update/);D2 独立插件与核心积分等级并存;D3 签到不发积分;D4 like 走核心 vote_after;D5 P0 只做发帖/回帖门控,上传/私信/邀请顺延 P1。命名空间最终统一为 Plugin\user_level(计划中的 Plugin\UserLevel 为旧值;与目录名一致可避开核心生命周期类名推导缺陷,无需 class_alias)。
2.2 数据结构(插件独立库)
独立 SQLite 库 plugins/user_level/data/user_level.sqlite:
ul_logs行为流水(唯一事实源):主键(user_id, type, ref_id, src_uid)天然幂等去重(同话题回复只记 1;visit 按ref_id=Ymd保证每天 1 条);另有day(YYYY-MM-DD)、created_at;索引idx_ul_logs_user_day、idx_ul_logs_day;触发器trg_ul_logs_invalidate_map(见第五节);ul_users显示缓存 + 计数快照:主键user_id;字段visit_days/read_topics/reply_topics/topic_count/given_likes/recv_likes/tl1_at~tl3_at/manual_lv/locked_lv/map_lv(NULL=待重算)/updated_at。
配置存核心 settings 表单键 user_level_cfg(Plugin::PREFIX),不占独立库;合并必须用 array_replace —— badge 是数字键数组,array_merge 会把 0–4 重排成 5–9,导致徽章文案永远读不到。
2.3 任务类型与防刷
visit/read/reply/topic/like(given+recv)六类流水;防刷:私信不计、回自己主题不计、同主题回复计 1、自赞排除;写入一律 INSERT OR IGNORE(SQLite ON CONFLICT DO NOTHING)+ 计数器 +1 + 显示缓存失效。
2.4 等级计算与性能红线
calc() 返回 ['lv'=>int,'tiers','s','wins'],手动/锁定档短路,封禁按 TL0。每请求所有 hook 查询 ≤ 5(QUERY_BUDGET = 5,闸门 Plugin::budget()):read 1、topic 2、reply 3、vote 4、visit 1、同步 3、门控 currentLevel() 2、preload() 3;预算不足时 preload() 退化为只读 map_lv,门控返回 0(视为 TL0,宁可放行也不越线)。累计档零窗口查询;重逻辑走 CLI 不进页面进程。
2.5 后台配置
/admin/user-level(admin_route_register + admin_url,不注册 admin_sidebar_links),5 Tab:① 考核指标 ② 徽章文案与 emoji ③ 能力门控 ④ 访问签到 ⑤ 手动授予/锁定。保存走 Settings::update() → clearCache() → PageCache::invalidate();输入全部 $this->e(),数值/颜色校验白名单。权限声明 ["route:admin", "system:settings"],不用 net:fetch。
2.6 里程碑与验收
阶段 A(P0 MVP:数据模型+等级纯函数+行为流水+gate_topic/gate_reply+楼层徽章+「我的等级」页+后台配置+三语包)、阶段 B(P1:上传/私信/邀请门控接入、列表徽章、TL0 外链上限)、阶段 C(P2:签到日历、偶像/粉丝页、CLI 全量重建/回灌)全部交付。死配置收尾:show.list 真正实现、gate.limit 改义为 TL0 单帖外链上限(gate.links)、checkin.points/step/cap 删除。验收:19 个验证脚本 638 项断言 0 失败;真实 HTTP 冒烟 /、/login、/register、/forum 均 200,致命错误与 500 为 0;卸载三项自检(表删净/settings 清空/getPlugin 为 null)通过;发布包 update/168/ 33 个文件与源逐字节一致。安全红线:SQL 全绑定参数、输出全转义、不直查 SplitDB 分片表、不原生 SQL 写 users、渲染钩子禁 exit;全文件 UTF-8 无 BOM + LF。
三、全量逻辑与后台改造方案
2026-09-12 只读检查:基础流水链路已存在,但尚不能称「任务体系完整运行」;本方案只列问题与分阶段修法,当时未改后台代码。
3.1 检查发现的 8 类问题
- 浏览任务被展示配置阻断:
controller_view_before.php先判断show.badge,关徽章即return,read 流水也不写; - 窗口考核不按等级独立:
syncAndCache()/preload()/currentLevel()取三档最大win统一套用; keep/grace未落地:文档声称有浮动窗口/保持要求/降级保护期,配置只有win,代码也没用tl1_at~tl3_at;recv_likes汇总错误:metricsFor()未聚合recv_like;syncAndCache()把给出的like同时写进given_likes和recv_likes;- 等级显示缓存滞后:流水写入后
map_lv不失效,同步又有 10 分钟节流,徽章可能显旧等级; - 同步与预算/顺序耦合:预算闸门被跳过时 Session 仍先写
ul_last_sync,可能延迟重试; - 「我的等级」页展示累计指标:即使某档配了
win > 0也用累计值展示,与窗口条件可能不一致; - 后台过窄、Tab 结构不舒展:
admin-form-lg限宽 700px,7 列考核矩阵拥挤;Tab 是裸按钮,未用其它插件的.admin-tabs/.admin-tab-btn/.admin-tab-pane标准样式;无 URL/hash 状态,保存后回到第一 Tab。
3.2 任务引擎分阶段修法
阶段 1 先修浏览任务并建行为回归;阶段 2 修正指标聚合(metricsFor() 聚合 recv_like、syncAndCache() 分写 given/recv);阶段 3 修正窗口算法(每档用自己 win,新增内部 metricsForWindows(),一次按 user_id+type+day 聚合再在 PHP 内分窗累计,保持 calc() 公开兼容);阶段 4 处理 keep/grace 决策(推荐方向 A:只保留 win=0 累计 / win>0 窗口,移除未实现描述)。
3.3 缓存同步与后台改造要点
缓存侧:只让受影响用户(行为者 + recv_like 内容作者)map_lv 失效;预算不足不提前写 ul_last_sync;补「行为→失效→preload 重算」回归。后台侧(参考 forum_required_tag/medal/task_center 的 admin 视图):ul-admin-workspace 去掉 700px 限宽,五个 panel 对应五个 Tab,hash 定位(#metrics/#badge/#gate/#checkin/#manual)+ 键盘切换 + aria 属性,统计从保存表单独立,移动端每等级一张卡片。实施范围仅插件内文件;不建议改 app/Core/、app/Models/、app/Helpers/、SplitDB/、其它插件与数据库结构。
3.4 待确认产品决策(后被采纳)
① like=给别人点赞(参与升级)、recv_like=收到点赞(仅展示);② keep/grace 按方向 A 简化移除;③ reply_to_view 下能访问 thread/show 即计浏览。三项均被用户确认,落实情况见第五节。
四、自包含与核心隔离
(审计报告 + 核心文件改动清单综合结论,2026-09-11)
4.1 审计结论:通过,但需区分「插件自包含」与「框架集成」
plugins/user_level/符合「一目录一插件」:共 28 个文件(PHP 22 个 +script.js/style.css+plugin.json+ 三语包 + data 下数据库与双重 Web 防护),assets/data/hook/lang/views子目录完整,注册的 11 个钩子全部存在;- 无跨插件命名空间/路径/数据库依赖(不必要的
plugins/user_profile/front模板引用已从hook/layout_head_end.php删除); - 无越界写核心业务表,无任何文件系统写操作(
file_put_contents/fwrite/rename/copy/mkdir/unlink零命中); - 卸载无残留:删自身
ul_logs/ul_users+ settings 唯一键user_level_cfg; - 合法集成点 4 处(属框架集成非污染):
app/Helpers/Plugin.php(插件库/钩子/权限)、app/Helpers/Settings.php(配置读写)、5 个核心视图徽章占位、settings 键user_level_cfg。
4.2 回归验证
cli/_verify_plugin_selfcontained.php(新增)29/0;相关 20 个验证脚本零失败(自包含 29/0、签到日历 105/0、偶像/粉丝 112/0、回灌 66/0、三类门控 101/0);LF 统一:源目录与 update/168/ 共 52 个文本文件 CRLF/裸 CR/BOM 均为 0、逐字节一致;卸载自检 cli/_verify_ul_uninstall.php 5/0。
4.3 对核心目录的改动(6 项)
| # | 文件 | 改动 |
|---|---|---|
| 1 | app/Helpers/Plugin.php | 新增权限键 net:fetch/route:admin/system:settings 与 granted()/requirePermission()(未声明时按全局开关 permission_enforce 决定告警放行或拒绝);hook() 对 admin_route_register 做权限特判;钩子路径安全校验(realpath+目录分隔符,防兄弟目录绕过) |
| 2 | app/Helpers/Settings.php | +31 行:修文件缓存孤儿键。loadCacheFile() 命中即 return 从不与 DB 对账,导致 settings 表已 0 行但旧 JSON 缓存仍生效、改 defaultCfg() 无效;新增 reconcileWithDb() 剔除「缓存有 DB 无」的孤儿键,DB 异常/空库时跳过;回归 8/0 |
| 3 | app/Views/forum/index.php | +2 行:桌面/移动两处 ulListBadges 列表徽章占位 |
| 4 | app/Views/home/index_default.php | +1 行:首页列表徽章占位 |
| 5 | app/Views/thread/show.php | +1 行:楼层徽章占位 ulBadges(紧跟 $wearingMedals/$pmTitles) |
| 6 | sw.js | CACHE_VERSION → flinthub-static-v1.0.26 |
version.php 未动(仍 V1.0.168,update/168/ 为部分文件更新包);新增 cli/user_level_rebuild.php(~330 行,插件配套运维工具非核心):从核心真值表(threads/posts/thread_votes/post_votes/viewed_replies)全量重建 ul_logs 并重算等级,支持 --dry-run/--reset(配 --force)/--no-levels/--batch=N,visit 故意不回灌,回归 66/0。确认未改动:app/Core/Router.php、index.php、Auth.php、Controller.php、TemplateCompiler.php、User.php、modern.css。
4.4 遗留边界事项
配套 CLI 在插件目录外(cli/user_level_rebuild.php 及发布副本),按现有约定不算核心污染,但非最严格单目录封装,如规范加严可迁至 plugins/user_level/cli/;核心 --mn-bg-hover 变量被 modern.css 多处引用但从未定义,插件侧改用已定义的 --mn-bg-row-hover,建议核心侧修补。
五、任务体系三阶段修复
三份阶段报告的统一验证手段:cli/_verify_ul_task_system.php,用 97000 段探针数据,结束自动清理(logs=0/users=0),不写核心业务表。
阶段 1:基线验证(只加脚本,未改业务源码)
结果:通过 9 / 失败 5。 已通过:五类行为 hook 入口存在、show.badge=1 时 read 对照路径可记录、给别人点赞的 like 汇总为 1、t1/t2/t3 的 win 配置入口存在。锁定的 5 个失败即后续修复目标:① 关 show.badge 后 read 不记录;② recv_like 无独立汇总;③ 行为写入后 map_lv 不失效;④ 每档 win 未独立计算;⑤ keep/grace 描述未清理。
阶段 2:read 任务修复
- 问题:
hook/controller_view_before.php在 read 流水写入(约 67–82 行)之前判断show.badge,关徽章直接return,浏览任务被连带关闭 —— 任务流水与 UI 展示错误耦合; - 改法:顺序调整为「判登录 + 是否浏览他人主题 → 写 read 流水 → 再判
show.badge,关闭时只跳过徽章预载与$data['ulBadges']注入」;同时明确口径:能访问thread/show即计浏览、reply_to_view正文锁定不影响统计、不记自己的主题、同主题 PK 去重; - 涉及文件:
plugins/user_level/hook/controller_view_before.php(仅插件自身,不动核心); - 验证:通过 10 / 失败 4,read 四项断言全过(hook 存在、写入早于 UI 开关、关徽章仍记录、开徽章对照正常);剩余 recv_like、map_lv、独立 win、keep/grace 四项进入阶段 3。
阶段 3:点赞汇总与等级缓存修复
- 口径:
like=给别人点赞(参与升级);recv_like=收到点赞(仅展示); - 改法(
plugins/user_level/Plugin.php):①metricsFor()聚合recv_like,现返回 visit/read/reply/topic/like/recv_like 六项,calc()仍只读like;②syncAndCache()分写given_likes=like与recv_likes=recv_like;③ 插件独立库新增触发器trg_ul_logs_invalidate_map:新流水插入即把对应用户ul_users.map_lv置 NULL(recv_like的 user_id 是收到点赞者,故其缓存也正确失效),ensureOnce()自动为旧库补建触发器; - 涉及文件:
plugins/user_level/Plugin.php;触发器在插件库内,不触碰核心库,也不增加行为钩子的应用层查询; - 验证:通过 12 / 失败 2;本阶段新增通过「被点赞者 recv_like 独立汇总为 1」与「行为写入后 map_lv 失效」;剩余每档 win 独立计算、keep/grace 清理两项。
收尾去向(据最终版插件说明)
最终版说明显示等级判定已由 calcForLevels()/metricsForLevels() 按每档自己的 win 窗口取指标(一次查询构造累计+各档窗口指标,不再用最大窗口错套);keep/grace 按决策 A 简化,最终版说明中已无相关内容,只保留 win=0 累计与 win>0 近 N 天两种模式。一致性最终由「行为→map_lv 置 NULL→preload 重算 + 10 分钟节流同步兜底」共同保证。
六、遗留与未完成工作
基线:《未完成工作清单》(2026-09-11)核查时点阶段 A 已完工、/u/level 500 已修复(冒烟 22/22、卸载自检 5/5、三语零缺失、thread/show.php:92 徽章占位已加);「PHP 8.1+ 专属语法」「route_after_dispatch 全页 500」两个疑点复查均不成立。清单未完成项的后续去向:
| 清单项 | 当时状态 | 后续 |
|---|---|---|
| B1 上传/私信/邀请门控接入 | 零命中 | ✅ 阶段 B 交付,门控回归 101/0 |
| B2 列表头像徽章 | show.list 死配置、无消费逻辑 | ✅ 交付:forum/index、home 列表占位 + 按模板注入 |
| B3 TL0 限流/链接数 | gate.limit 死配置 | ✅ 交付:改义为 TL0 单帖外链上限(gate.links),回归 44/0+30/0 |
| C1 偶像/粉丝页 | 零命中 | ✅ 交付(回归 112/0) |
| C2 CLI 全量重建/回灌 | 无脚本 | ✅ 交付(回归 66/0) |
签到日历 /u/level/checkin | 路由/视图均不存在 | ✅ 交付(不发积分) |
仍开放或需注意:
- 后台 UI 改造未确认实施:Tab 重做方案(去限宽、hash、响应式、统计独立)已提交,但三阶段报告均注明「后台 UI 仍不修改,待任务逻辑完成后另行确认」,来源文档未记录其实施结果;
- 「我的等级」页展示口径:方案指出页面用累计指标、与窗口条件可能不一致,阶段报告未覆盖;
- 配套 CLI 位置:
cli/user_level_rebuild.php在插件目录外,如规范加严可迁入plugins/user_level/cli/; - 核心
--mn-bg-hover缺失:建议核心侧补定义或改用--mn-bg-row-hover; - 临时脚本:
cli/_repro_ul500.php已删;cli/_verify_ul_uninstall.php命名空间早已修正,建议保留为长期回归脚本。
附录:插件使用说明摘要
来源 WWW/plugins/user_level/说明.md(最终态)。
A.1 目录与路由
插件根含:主类 Plugin.php(等级纯函数/流水/指标聚合/徽章渲染/能力判断/补算/生命周期)、FrontController.php(前台四页)、AdminController.php(配置/手动/补算)、11 个钩子(init_after 幂等建表、路由注册 ×2、门控、访问流水+10 分钟同步、CSS 注入、阅读流水+徽章、thread/post/vote 流水、用户菜单)、views/(mine/checkin/like_list/error/admin)、assets/(零内联)、lang/(三语)。前台路由(均需登录):/u/level 我的等级、/u/level/checkin 访问日历(?month=YYYY-MM)、/u/level/idols 我赞过的人、/u/level/fans 赞过我的人。后台:GET/POST /admin/user-level(5 Tab 配置/保存)、/admin/user-level/manual(手动授予/锁定)、/admin/user-level/backfill(历史补算),均需 CSRF;需 route:admin 与 system:settings 权限(均已声明)。
A.2 数据与缓存要点
ul_logs 是唯一事实源,ul_users 只是显示缓存与计数快照,排查等级异常先查流水;map_lv=NULL 是待重算标记,触发器写入时自动置 NULL,preload() 读到 NULL 时现算并批量回填;建表走 activate() + init_after(ensureOnce() 每请求只探测一次);配置为 settings 单 JSON 键 user_level_cfg(t1/t2/t3 + badge[0..4] + gate 六项 + show 开关),保存链 saveCfg()→Settings::update()→clearCache()→PageCache::invalidate()。
A.3 能力门控一览(route_before_dispatch)
| 能力 | 命中路径 | 配置键 | 拦截表现 |
|---|---|---|---|
| 发帖 | POST /post/new | gate.topic | 422 + 插件错误页(草稿回显,可重提交) |
| 回帖 | POST /thread/{id} | gate.reply | 422 + 插件错误页(草稿回显) |
| 上传 | POST /api/upload | gate.upload | ?ajax=1 → 422 JSON;否则错误页 |
| 私信 | POST /message/compose | gate.dm | 422 错误页(带返回按钮) |
| 邀请 | POST /invites | gate.invite | 422 错误页(带返回按钮) |
| 外链 | 发帖/回帖 | gate.links | 仅对 TL0 生效,超限拦截 |
零查询短路顺序:非 POST 放行 → 路径未命中放行 → 未登录交控制器 → 门槛未启用放行。
A.4 徽章与历史补算
badgeHtml() 三模式:meta(楼层/详情页 图标+文字)、avatar(头像微徽章仅图标,emoji 空则输出空串)、list(列表行,桌面图标+文字、移动端 CSS 隐藏文字)。backfillHistory() 数据源:核心 topic_index/reply_index、thread_votes/post_votes、daily_checkin 插件库;全部 INSERT OR IGNORE 幂等可重跑;read 无历史来源不参与补算;完成后逐用户 syncAndCache()。
A.5 注意事项精选
- 钩子重复 include 陷阱:
route_before_dispatch一次请求触发两次(注册期+派发前),Plugin::hook()裸include加载,顶层函数必须function_exists包裹否则整站 500,且守卫块须置于所有调用点之前(条件块内声明不提升);防线cli/_verify_plugin_hook_reinclude.php18 项断言; - 两处白名单同步:徽章页面须同时登记
controller_view_before.php的$listTemplates与layout_head_end.php的$targets; - 卸载行为:删两张插件表 + settings 键
user_level_cfg(已 clearCache+buildCache 绕过文件缓存),不删其它用户数据; - 当前运行态(说明记录时):插件已启用,
ul_logs42 行 /ul_users4 行。
本篇由 9 篇过程文档与插件说明.md 合并整理,保留具体表名、字段、方法名与配置键;过程细节以原始文档为准。