本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/reply_reward/说明.md。
回帖奖励(reply_reward)
楼主设置一个积分奖池,用户在该主题回帖即自动获得固定积分,池子发完即止,过期余数退还。
玩法
- 楼主:发帖时勾选「设置回帖奖励」,填「奖池总积分 X」+「每次回帖奖励 N 分」+ 有效期 + 每人限领次数。
- 表单会实时提示「共发放几层、最后一层得多少」(如 100 分 / 每层 3 分 → 34 层,最后一层 1 分)
- 提交后从楼主账户一次性预扣 X 分存进奖池
- 其他用户:在该主题回帖成功 → 自动到账 N 分(无需点击领取)
- 结束:池子发完自动关闭;到期未发完的余数自动退还楼主;楼主也可手动关闭退余
与「积分红包」(red_packet)的区别
| 积分红包 | 回帖奖励(本插件) | |
|---|---|---|
| 核心动作 | 用户点「抢红包」 | 用户回帖即自动到账 |
| 金额 | 随机(二倍均值法) | 固定每层 N 分 |
| 上限 | 按份数 | 按总积分 |
| 关系 | 完全独立,不共享表、不互相调用 | 同左 |
两者都会在帖子详情页渲染卡片(同一 thread_redpacket_area 锚点)。同一主题同时挂两种会显示两张卡片,插件间不做互斥,由运营自行选择。
数据表(独立库 data/reply_reward.sqlite)
| 表 | 用途 |
|---|---|
rr_pools | 奖池主表:总积分 / 每层分 / 限领次数 / 余额 / 状态 / 到期时间 |
rr_claims | 领取计数:UNIQUE(pool_id, user_id) 累加行 + state 两阶段标记 |
rr_settings | 全局配置键值表 |
关键实现说明
发奖时机
挂在 post_create_after(核心 ThreadController.php:175)。
⚠️ 该钩子在回帖事务之外触发(帖子行已 commit)。实测依据见 docs/插件开发规范.md §9.1 的说明块。
⚠️ 钩子的异常会被核心 ThreadController.php:194 的 catch (\Throwable) 吃掉并记日志 → 表现为「回帖成功但没发奖」。因此插件内部自行 try/catch,不让异常逃逸。
回帖后的到账提示(thread_show_top)
回帖流程是传统表单 POST + 302 跳转(thread/show.php → ThreadController::redirect('/thread/{id}?page=N')),
钩子在跳转之前触发,无法直接输出提示。故用「一次性 session 提示」:
post_create_after → 捕获 rewardOnReply() 返回值 → $_SESSION['rr_reward_tip']
↓ 302 跳转
thread_show_top → 读 session → 页顶渲染 mn-alert → **立即 unset(读后即焚)**- 锚点:
app/Views/thread/show.php:47(?msg=白名单之后、归档横幅之前)—— 核心为此新增了thread_show_top钩子。 - 三档口径(白名单,其余静默):
| 档位 | 文案 | 样式 |
|---|---|---|
ok | 回帖成功,获得 N 积分奖励 | mn-alert-success |
limit_reached | 本次未获得奖励(你已领满该奖池的领取上限) | mn-alert-error |
empty | 本次未获得奖励(该奖池已发放完毕) | mn-alert-error |
no_pool / race / 其他 | 不提示(无池子零打扰;race 并发极少数静默) | — |
- ⚠️ 只用
success/error两个变体:核心没有.mn-alert-warning,--mn-alert-warning-*变量全站也不存在(9 套主题只定义 success/error 两组)。 - ⚠️ 跨帖保护:session 里带
thread_id,只在同帖渲染,避免用户从 A 帖跳到 B 帖时串场。 - ⚠️ 读后即焚:渲染前
unset,刷新页面不重放(已 HTTP 实测:第 2 次 GET 零输出)。
跨库一致性:两阶段 + 补偿
插件库与核心库是两个 PDO 连接,SQLite 无法跨库事务。发放流程:
① 原子扣池 UPDATE rr_pools SET remaining = remaining - :got
WHERE id = ? AND status = 0 AND remaining >= :got → rowCount 校验
② 写领取记录 INSERT/UPSERT rr_claims (state = 0 pending, cnt + 1)
③ Points::award(用户, got, ...) ← 核心库,独立写入
④ 置 state = 1 (done)若 ③ 失败或进程崩溃 → rr_claims 停在 state=0 → route_after_dispatch 里的 compensate() 扫描 5 分钟前仍是 pending 的记录补发。
防超领三层
| 层 | 机制 |
|---|---|
| 池子变负 | UPDATE ... WHERE remaining >= :got + rowCount() |
| 同一用户超领 | UNIQUE(pool_id, user_id) + cnt < K 判定(UPSERT 累加) |
| 跨库丢/超发 | state 列 + 补偿扫描 |
维护任务
route_after_dispatch(index.php:321,响应已产出后触发),session 门控 60 秒:
refundExpired()—— 过期退余(单条失败只记日志并跳过,不整批回滚)compensate()—— 补发 pending 记录
⚠️ 故意不挂 init_after:那是每请求路径,全表扫描 + 退款会拖慢首字节。
目录名含下划线的坑
本插件目录名 reply_reward 含下划线,而核心 app/Helpers/Plugin.php:699/750/846 用目录名硬拼类名
\Plugin\{目录名}\Plugin → class_exists 恒为 false → activate/deactivate/uninstall
静默跳过(不报错、后台还提示成功),典型症状是卸载后表残留。
因此 Plugin.php 末尾必须保留:
\class_alias(Plugin::class, 'Plugin\reply_reward\Plugin', false);删掉这行会导致卸载不干净。
后台
入口:后台 → 插件 → 回帖奖励(/admin/reply-reward),或在帖子的奖池卡片上点「去后台设置」。
- 全局配置:总开关、是否允许所有用户挂池、默认有效期 / 限领次数、每日挂池上限、数额上下限
- 奖池列表:状态筛选、进度、手动「关闭并退余」
语言包
zh / en / zh_tw 三包,各 81 键,键集合零差异、键序一致。
配置项一览
| 键 | 默认 | 说明 |
|---|---|---|
enabled | 1 | 总开关 |
allow_all_users | 1 | 0 = 仅管理员可挂池 |
default_expire_days | 7 | 默认有效期(天) |
default_per_user_limit | 1 | 默认每人限领次数 |
daily_pool_limit | 3 | 每人每日挂池上限,0 = 不限 |
min_total_points / max_total_points | 10 / 100000 | 单池总积分上下限 |
min_per_reward / max_per_reward | 1 / 1000 | 每次奖励上下限 |