[开发]回帖奖励(reply_reward)

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

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

回帖奖励(reply_reward)

楼主设置一个积分奖池,用户在该主题回帖即自动获得固定积分,池子发完即止,过期余数退还。

玩法

  1. 楼主:发帖时勾选「设置回帖奖励」,填「奖池总积分 X」+「每次回帖奖励 N 分」+ 有效期 + 每人限领次数。
  • 表单会实时提示「共发放几层、最后一层得多少」(如 100 分 / 每层 3 分 → 34 层,最后一层 1 分)
  • 提交后从楼主账户一次性预扣 X 分存进奖池
  1. 其他用户:在该主题回帖成功 → 自动到账 N 分(无需点击领取)
  2. 结束:池子发完自动关闭;到期未发完的余数自动退还楼主;楼主也可手动关闭退余

与「积分红包」(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 键,键集合零差异、键序一致。

配置项一览

键默认说明
enabled1总开关
allow_all_users10 = 仅管理员可挂池
default_expire_days7默认有效期(天)
default_per_user_limit1默认每人限领次数
daily_pool_limit3每人每日挂池上限,0 = 不限
min_total_points / max_total_points10 / 100000单池总积分上下限
min_per_reward / max_per_reward1 / 1000每次奖励上下限

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

All Replies (0)

No replies yet

Please Log in