[开发]Points 积分等级系统 — 插件开发者接口参考

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

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:暂存20260913/docs/points_level_api.md。

Points 积分等级系统 — 插件开发者接口参考

版本:1.0(2026-09-12)

依据:《积分等级系统(Points Lv1-11)对外接口审计》(只读审计,未改核心代码)

适用范围:需要在插件中消费「积分等级」的第三方插件

配套代码:app/Helpers/Points.php、app/Helpers/Auth.php、app/Models/User.php、app/SplitDB/Schema.php


一、数据模型速览

数据位置说明
users.points / users.level核心库 users 表快照:level 仅在 Points::award() 升级时写入;Points::deduct() 不降级
level_config 表app/SplitDB/Schema.php L477-485(建表)、L176-185(种子)阈值配置(非硬编码、非 settings 键),后台 /admin/levels 可改
默认阈值Points.php L273-278Lv1-11 = 0/10/40/90/160/250/360/490/640/810/1000

两条必须知道的事实:

  1. 等级只升不降:升级判定在 Points.php L91($newLevel > current),扣分不触发降级。
  2. users.level 是快照,不是权威值:管理员在后台改 level_config 阈值后,快照不会重算;Points::calculateLevel($points) 才是按当前阈值的权威值。

二、推荐接口清单(插件可放心调用)

1. 单用户等级摘要(权威重算)— ✅ 推荐

$summary = \app\Helpers\Points::getSummary($userId);   // ?array 或 null(用户不存在)
$level    = $summary['level'] ?? 0;                    // 按当前阈值重算的等级
$points   = $summary['points'] ?? 0;
$next     = $summary['next_level_points'] ?? 0;        // 下一级所需积分
$progress = $summary['progress'] ?? 0;                 // 距下一级进度 0-100

  • 位置:app/Helpers/Points.php L211-241
  • 说明:内部查一次 users + 全量 level_config,适合独立页面 / 控制器;不要放每页必跑的钩子里(违反每请求 ≤5 查询的钩子预算)。

2. 当前登录用户 — ✅ 推荐

$me   = \app\Helpers\Auth::getCurrentUser();          // ?array
$level = (int)($me['level'] ?? 0);                    // 快照
$points = (int)($me['points'] ?? 0);

  • 位置:app/Helpers/Auth.php L29-54
  • 说明:零额外查询(请求级缓存),读的是 users.level 快照。

3. 批量取用户等级(帖子楼层 / 列表)— ✅ 推荐

$users = \app\Models\User::batchGetUsers($uids);      // [uid => ['username','avatar','level']]
if (($users[$uid]['level'] ?? 0) >= 5) { /* 特权 */ }

  • 位置:app/Models/User.php L344-378
  • 说明:请求级缓存,插件开发规范强制:批量补用户名/头像/等级一律走它,禁止直查 users 表。
  • 同一批量数据也随核心列表自动附带:$thread['level'](app/Models/Thread.php L916)、$post['level'](app/Models/Post.php L436)。

4. 读取等级阈值(Lv1-Lv11 各档积分门槛)— ✅ 推荐

$configs = \app\Helpers\Points::getAllConfigs();      // [{level,title,required_points,icon,color}] 升序
$lv5Points = $configs[4]['required_points'] ?? 160;   // 索引 4 = Lv5

  • 位置:app/Helpers/Points.php L43-63
  • 说明:唯一推荐入口。DB 无数据时回退默认公式((level-1)^2 * 10,与种子一致)。
  • ⚠️ 不要用 Points::getLevelConfig()(L13-41)拿阈值——它只返回 title/color/icon,不含 required_points,且默认数组只写到 Lv10(Lv11 靠 L39 特判补齐)。

5. 积分 → 等级重算(权威值)— ✅ 推荐

$level = \app\Helpers\Points::calculateLevel($points); // int,按当前阈值重算

  • 位置:app/Helpers/Points.php L65-74
  • 说明:纯函数,不查库。适合"已知积分、要拿当前档位"的场景。

6. 渲染等级徽章 HTML — ✅ 推荐

echo \app\Helpers\Points::renderBadge($level);        // 输出 span.level-badge,已做色值 XSS 校验
$color = \app\Helpers\Points::getLevelColor($level);  // 单取色值

  • 位置:app/Helpers/Points.php L197-209、L192-195
  • 说明:核心右栏发帖人面板(app/Views/_components/right_sidebar.php L72)、user_profile 插件个人主页(plugins/user_profile/views/front.php L48)均用它。

7. 写积分(自动升级 + 通知 + 钩子)— ✅ 推荐

\app\Helpers\Points::award($userId, $points, $reason, $relatedId, $relatedType);  // 加积分,自动升级
\app\Helpers\Points::deduct($userId, $points, $reason, $relatedId, $relatedType);  // 扣积分(条件原子扣减),返回 bool

  • 位置:app/Helpers/Points.php L76-124、L126-175
  • 说明:插件开发规范红线:禁止直写 users.points/level,增减积分一律走 Points::award()/deduct()。

三、4 个真实缺口(当前需绕行的点)

缺口 1:等级变化没有插件钩子

Plugin::hook('level_up') 之类的事件不存在。升级只发生在 Points::award() 内(L90-105),外部唯一可用的关联事件是 points_award_after(L110-116,随调用方事务),但参数只有 user_id/points/reason/related_id/related_type,不含 old_level / new_level——插件要感知"谁升了级"必须自己先查一次 users.level 再对比,且无法精确知道升级前的等级。

另注意 Notification::TYPE_LEVEL_UP(app/Helpers/Notification.php L28)注释仍标"预留",但实际已在 L98 触发写入 notifications 表。

缺口 2:批量等级摘要缺失

批量接口只有 User::batchGetUsers() 返回的快照 level,没有 title/icon/next_level_points/progress 等展示字段。要批量渲染"等级徽章 + 进度"只能逐个调 getSummary()(N+1)或自行联查 level_config。

缺口 3:快照 vs 权威值口径不一致

  • users.level / $thread['level'] / batchGetUsers() → 快照(后台改阈值后失准);
  • Points::calculateLevel() / getSummary() → 按当前阈值重算(权威)。
  • 两者在管理员改过 /admin/levels 后会产生分歧。做权限判断时请明确口径,推荐以重算值为准。

缺口 4:核心模板无统一等级徽章占位

  • 帖子详情 thread/show 与首页/论坛/分类/标签列表页不渲染核心等级徽章(home/index_default.php L38、forum/index.php L50 算了 $level 但未输出)。
  • 现有一族"插件注入占位":$wearingMedals(medal)、$pmTitles(points_mall)、$ulBadges(user_level),均为 [uid => HTML],由各插件在 controller_view_before 注入、核心模板在用户名旁输出(thread/show.php L93-94、L239-240)。
  • 新插件要展示等级徽章,需自行走"controller_view_before 注入 → 复用既有占位或自建变量"的模式。

四、Lv5+ 特权参考实现(组合以上接口)

// 场景 A:单用户门槛(权威重算)
$level = \app\Helpers\Points::getSummary($userId)['level'] ?? 0;

// 场景 B:当前登录用户(快照,零查询)
$level = (int)(\app\Helpers\Auth::getCurrentUser()['level'] ?? 0);

// 场景 C:批量(帖子楼层、列表页)
$users = \app\Models\User::batchGetUsers($uids);
foreach ($users as $uid => $u) { if ($u['level'] >= 5) { /* 该楼层放行特权 */ } }

// 场景 D:动态读 Lv5 阈值(阈值可后台改,用配置而非硬编码 160)
$lv5Points = \app\Helpers\Points::getAllConfigs()[4]['required_points'] ?? 160;
if ($points >= $lv5Points) { /* 特权 */ }


五、待办:建议补充接口(暂不实施,第三周目重启评估)

按优先级排序,实施前逐个评估。仅建议,未列入任何版本计划。

优先级建议接口解决缺口说明
P0等级变更钩子 Plugin::hook('level_changed_after'),参数 user_id / old_level / new_level / points,在 Points.php L91-105 升级成功后触发缺口 1插件无需自查对比即可感知升级
P1Points::levelOf(int $userId): int(请求级缓存)缺口 3统一"单用户当前等级"入口,消除快照/重算两套口径与重复 SQL
P1Points::isAtLeast(int $userId, int $minLevel): bool缺口 3"LvN+ 特权"类需求的直接落点
P2Points::getSummaries(array $ids): array缺口 2批量摘要(含 title/icon/progress),现在批量只有快照 level
P2核心统一等级徽章占位 $data['levelBadges']([uid => HTML],核心批量填充、插件可挂钩覆盖)缺口 4消除各插件自造注入变量的重复模式
P3阈值变更后快照重算联动,或文档明确口径缺口 3saveLevelConfig 改阈值后 users.level 快照失准


本文档基于只读审计生成,审计时未修改任何核心或插件代码。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 4 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入