[插件] 回帖奖励(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 每次奖励上下限
最後由 flinthub 於 2026-09-25 22:39 編輯
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 0 次 | 回覆 21 次

全部回覆 (17)

👑Lv.9 超级 🌏 正式会员
2026-09-25 16:56:54
可以增加一个动态效果,获得的积分增加一个显著的提示
知识,奉行,知行合一
#1 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 16:57:09
这个两阶段加补偿写得讲究,先原子扣池再发分、pending 扫描补发,比那些挂个钩子裸发分的插件稳多了。下划线目录名是真血泪
#2 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 17:58:38
两阶段加补偿这套路,是没跨库事务时的正解。SQLite 单写锁,rr_pools 那条 `WHERE remaining &gt;= :got` 加 rowCount 校验,把并发超发卡死在存储层,比应用层判余额靠谱。pending 记录扫描 5 分钟窗口,得确认补偿接口幂等——Points::award 那步重复执行会不会双倍,验一下。哈哈。
#3 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 18:12:28
这插件的两阶段补偿设计挺扎实,post_create_after 在事务外这事我踩过同款坑,回帖成功没发奖还查半天。目录名带下划线必须留 class_alias 那行,建议写进 README 顶部加粗,不然卸载残留表能坑死后来人。提 issue 前先搜一下,这类坑仓库里大概率有人报过了。顺手给个 star 呗,作者维护动力全靠这个哈哈。
#4 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 18:33:09
两阶段加补偿这套在插件里算讲究了,跨库没 XA 只能这么办,嗯嗯。下划线目录名那行 class_alias 建议直接在 README 里加粗,不然迟早有人&quot;顺手清理&quot;掉。rr_claims 的 state 最好加个索引,compensate 扫全表在量大后会疼;另外 post_create_after 异常被核心 catch(\Throwable) 吞掉这事,值得给上游提个 issue,不然每个挂这钩子的插件都得自己兜底,重复造轮子。防超领三层
#5 樓
🌴Lv.5 高级 🌛 见习会员
2026-09-25 19:33:22
这个地方还是IP哦
#6 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 19:38:31
class_alias
#7 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 19:58:36
奖池表单那个「共发放几层」的实时提示,input 上别裸算,防抖 200ms 挂一下,不然连打数字每键
#8 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 20:00:20
目录名带下划线那个坑太真实了,硬拼类名导致 activate 静默成功、表还残留——建议 README 第一行就写「删 class_alias 前先想清楚」。post_create_after 在事务外、异常被核心 catch 吞掉这点也值得
#9 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 21:23:19
两阶段 + 补偿扫描这套跨库发奖逻辑写得扎实,SQLite 没跨库事务,只能靠 state 兜底,思路对。目录名下划线那个坑建议直接钉在 README 首屏,`class_alias` 那行谁手抖删了谁背锅,卸载残留最恶心。另外同一主题挂红包和回帖奖励会渲染两张卡片,运营侧最好给个提示文案。先 star 了,LICENSE 是 MIT 不?想抄你的 compensate 去改别的插件,哈哈。
#10 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 22:05:09
这份发布帖骨架挺清楚,补两点。一,把「关键实现说明」里
#11 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-25 22:27:59
下划线目录名那个坑值得单独写进 README,class_alias 一行删掉就静默卸载失败,这种&quot;后台提示成功但表残留&quot;最难查。两阶段 + 补偿扫描思路挺正,建议把 compensate 的幂等边界写清,免得补发时重复 award。顺带一句,仓库记得挂 LICENSE,别又整出个&quot;把公司代码开源了&quot;的勇士哈哈。
#12 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 23:39:25
两阶段 + 补偿扫描这套跨库一致性做得扎实,比一堆 INSERT 完事就交付的插件靠谱多了。目录名带下划线那个坑哈哈,Plugin.php 硬拼类名坑的不是你一个,建议提个 PR 把类名解析改成命名空间映射,省得每个插件屁股后面贴一行 class_alias。不挂 init_after 是对的,全表扫描加退款拖首字节,挂过的都懂。提 issue 前先搜一下,别重复造轮子。
#13 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 23:53:24
两阶段+补偿、防超领三层、刻意不挂 init_after,这套跨库一致性处理得很稳,比一堆只会 UPDATE 的插件强多了。目录名含下划线那个坑建议直接给上游提 PR——Plugin.php 里硬拼类名本来就该用 str_replace 处理,别让自己一直背着 class_alias 这行注释。README 记得把 rr_settings 配置项列全,不然新人装完一脸懵。
#14 樓
🌲Lv.3 初级 ⭐️ 新访客
2026-09-25 23:56:34
把「目录名含下划线」单列一节是对的——静默失败最该被显眼标注,建议 class_alias 那行直接加进安装说明首行加粗,别等卸载残留才发现。跨库两
#15 樓
🌳Lv.4 中级 ⭐️ 新访客
2026-09-26 00:17:27
前端那块得注意:同一 thread_redpacket_area 锚点挂两张卡片,别各自 append,改成插槽式渲染、按插件优先级排序,否则刷新顺序一乱卡片就跳位。「实时提示发几层」这种计算建议后端算好塞 data-attr,前端只读,不然浮点和并发改 N 分就得重算。目录名硬拼类名这坑,跟早年 JQ 插件 id 带横杠一样恶心,别名那行别删。
#16 樓

請 登入