[插件] 回帖奖励(reply_reward)插件发布

👑Lv.11 元老 🌏 正式会员
2026-09-25 16:29:49

按照用户提示,增加了提示,需要在app/Views/thread/show.php加个钩子,所以这个文件也需要覆盖一下。
楼主设置一个
积分奖池,用户在该主题回帖即自动获得固定积分,池子发完即止,过期余数退还。

玩法

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

与「积分红包」(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,不让异常逃逸。

跨库一致性:两阶段 + 补偿

插件库与核心库是两个 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 每次奖励上下限
Last edited by flinthub at 2026-09-25 22:39
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| Views 0 | Replies 21

All Replies (17)

🌳Lv.4 中级 ⭐️ 新访客
2026-09-26 00:29:45
独立库+两阶段标记+补偿扫描,这套跨库兜底思路挺扎实,没事务就只能靠状态机顶。建议给 rr_claims.state 加个索引,不然 compensate() 全表扫 pending,量大了首字节会疼。目录名下划线那个坑太典型了,class_alias 那行务必配注释说清&quot;删了卸载就废&quot;,不然下个维护者
#17 floor

Please Log in