[lottery]抽奖帖热
- 抽奖帖ID lottery版本 1.6.4插件制作者 233免费1331 行 / 92.1 KBHook / 路由 / 后台页把帖子变成抽奖:读者回复即参与,到时间或满人数自动开奖,中奖名单公开且可复核。app/plugins/lottery/plugin.php开发日志已有 8 条
个人独立维护项目。
淘帖专辑官方安装插件合集主楼 插件质量报告
插件:
lotteryHook 功能范围 循环 使用频率 文件读写 修改系统表 读写系统表 读写自己的表 topic.index_data.loaded 首页/版块/用户主题列表查询后 否 5 - - - 读 plugin_lottery_draws topic.title_suffix 首页/版块主题列表逐条标题 是 5 - - - - topic.after_view 主题查看页完成加载 否 4 - - app_users 读 plugin_lottery_entries, plugin_lottery_winners, plugin_lottery_claims, plugin_lottery_point_ledger, plugin_lottery_draws, plugin_lottery_prizes reply.after_save 回帖保存后 否 3 - - app_users 写 plugin_lottery_draws, plugin_lottery_entries topic.after_save 主题保存后 否 3 - app_users app_users 写 plugin_lottery_prizes, plugin_lottery_point_ledger, plugin_lottery_draws topic.before_save 主题发布/编辑表单提交 否 3 - - app_users 读 plugin_lottery_draws topic.form_extra 主题发布/编辑表单 否 2 - - app_users 读 plugin_lottery_draws topic.content_after 非系统hook 否 1 - - - - 颜色说明:黄色表示有数据读写,红色表示 Hook 在系统循环中执行,浅蓝色表示有文件写入。“列表跳过”表示回调在列表路径会提前返回,“回调内”表示操作位于回调自身或其帮助函数的循环体内。“文件批处理”表示逐个处理用户一次提交的文件,属于必要操作,不标记修正。只有操作实际处于需要优化的循环路径时,才会标记“修正❗️”。
数据字典
plugin_lottery_draws字段 类型 可空 默认值 约束 topic_id uint 否 - 主键 creator_id uint 否 - - min_chars uint 否 5 - draw_at uint 否 0 - draw_count uint 否 0 - status key 否 'open' - entry_count uint 否 0 - commit_hash key 否 '' - seed key 否 '' - deposit_points uint 否 0 - min_entries uint 否 0 - insufficient_action key 否 'wait' - postpone_hours uint 否 24 - max_postpones uint 否 3 - postpone_count uint 否 0 - eligibility_groups string 否 '' - min_account_days uint 否 0 - min_user_points uint 否 0 - exclude_restricted uint 否 1 - claim_days uint 否 7 - rules_locked uint 否 0 - rules_hash key 否 '' - rules_snapshot text 是 - - drawn_at uint 否 0 - created_at uint 否 - - plugin_lottery_prizes字段 类型 可空 默认值 约束 id id 否 - 主键、自增 topic_id uint 否 - - name string 否 - - kind key 否 - - quantity uint 否 1 - unit_points uint 否 0 - sort_order uint 否 0 - plugin_lottery_entries字段 类型 可空 默认值 约束 id id 否 - 主键、自增 topic_id uint 否 - 唯一 user_id uint 否 - 唯一 reply_id uint 否 0 - created_at uint 否 - - plugin_lottery_winners字段 类型 可空 默认值 约束 id id 否 - 主键、自增 topic_id uint 否 - 唯一 prize_id uint 否 - - user_id uint 否 - 唯一 prize_name string 否 '' - created_at uint 否 - - plugin_lottery_point_ledger字段 类型 可空 默认值 约束 id id 否 - 主键、自增 topic_id uint 否 - - prize_id uint 否 0 - user_id uint 否 0 - kind key 否 - - points uint 否 0 - note string 否 '' - created_at uint 否 - - plugin_lottery_claims字段 类型 可空 默认值 约束 id id 否 - 主键、自增 topic_id uint 否 - 唯一 prize_id uint 否 - 唯一 user_id uint 否 - 唯一 status key 否 'pending' - deadline_at uint 否 0 - note string 否 '' - created_at uint 否 - - updated_at uint 否 - - 补充说明质量报告里
reply.after_save那条读 plugin_lottery_prizes 修正❗️。我照它查了三轮,标记没消掉,但每轮都是真实改进,已经发到 1.0.3:- 1.0.1:开奖函数里写的是
foreach (lottery_prizes($topic_id) as $prize),查询在foreach头部。虽然 PHP 只求值一次,但看着就像循环内查询,改成先取到变量再遍历。 - 1.0.2:怀疑是
lottery_cron_draw()在foreach里调lottery_execute_draw()。于是把奖品改成参数传进开奖函数,开奖函数里完全不含奖品查询;计划任务改为先收集到期主题 ID,再用一条IN (...)把所有到期抽奖的奖品一次读出来分发。这一版把计划任务从 N 条奖品查询降到 1 条,是这三轮里唯一有实际性能收益的改动。 - 1.0.3:把
lottery_prizes_map()里的查询彻底移出任何循环结构 —— 先fetchAll()到变量,映射骨架用array_fill_keys()建。
现在的运行时实测(v8.5.14 + SQLite,开
SQL_DEBUG_MODE数实际 SQL):场景 plugin_lottery_prizes读取次数已开奖的主题页 1 计划任务一次开完 2 个到期抽奖 1(一条 IN (...))单个主题的开奖 一辈子只发生一次 所以这条路径上没有 N+1。我的判断是剩下这个标记属于静态分析误报:
lottery_prizes_map()里仍然有一个foreach ($rows as $row),作用是把已经取回来的行按主题分组,查询在它之前、不在它里面。如果判定规则是「函数体内出现过循环就算循环内」,那这个标记只能靠把分组改写成array_reduce之类的写法来消——那样只会让代码更难读,我不打算为了消一个标记这么改。顺带一个观察,可能对调机器人有用:同一行里
写 plugin_lottery_winners确实是在「遍历奖品 + 遍历份数」的双层循环里逐条插入的(有几份就得写几行,无法合并),报告没有标它。也就是说现在的规则是只标读不标写 —— 我觉得这个取舍是对的,循环里的读才是典型的 N+1。如果你确认是误报,这条就当我没说;如果你希望不管代价一定要消掉这个标记,说一声我再改一版。
- 1.0.1:开奖函数里写的是
#4 两条都做了,已发 1.1.0。
发帖页入口(#3)
之前确实是我设计漏了:必须先把帖子发出来,再从主题操作栏点「设为抽奖帖」,发帖页上什么都没有。现在挂了
topic.form_extra,发帖时就有一个「设为抽奖帖」折叠区,里面能直接填参与门槛、定时开奖、满多少人开奖,以及第一个奖品(名称/类型/份数/每份积分),发布即生效,一步到位。topic.before_save先校验(开奖时间不能是过去、押金不够就直接拦),topic.after_save落库。编辑已有主题时这块会变成一个跳「抽奖设置」的链接,不重复放表单。后台管理页(#4)
新增
admin_tabs,「插件 → 抽奖」里可以设:- 押金积分 —— 发起抽奖时先扣,开奖或取消后退回,填 0 表示不收
- 允许发起抽奖的用户组 —— 勾选制,全不勾=所有用户组都可以,管理员始终可以
- 每人同时进行的抽奖上限 —— 填 0 不限,管理员不受限
- 默认参与门槛字数 —— 发帖页表单的默认值
页面上方还有总数/进行中/累计参与人次/当前押金四个数,下面列出进行中的抽奖并直达设置页,方便巡查。
押金实现上的两个细节
押金金额记在
plugin_lottery_draws.deposit_points里,按发起当时的配置存 —— 这样你中途改了押金金额,也不会退错数。新列用app_db_ensure_columns()补,市场在线更新会重跑install(plugin_market_install先把enabled置 0 再plugin_set_enabled(true)),所以已经装过的旧版本升级时会自动补列,不用手动改表。用户组限制是服务端强制的,不是只藏 UI:不允许的用户组在发帖页看不到抽奖区块,直接手搓 POST 也会被
lottery_before_save()拦下来。我用会员组的号实测过,两条路都拦住了,也没有产生残留的抽奖记录。实测记录(v8.5.14 + SQLite)
- 发帖页一次性创建:抽奖行、奖品、卡片都正确
- 押金:50 → 40(发起扣除)→ 50(开奖退回);取消那条也是 40 → 50
- 积分不足时发帖页显示「发起抽奖需要 10 积分押金,当前积分不足」,不给表单
- 回归:进行中/已开奖两种卡片、列表徽标、13 个主题的列表页仍然只有 1 条抽奖查询、满人数和定时两种自动开奖都正常
顺带回一下上面那条
读 plugin_lottery_prizes 修正❗️—— 我在 #2 里说的三轮排查结论还是没变,如果你看过觉得该按你的规则改,我照改。我更新了下插件。修改的时候,请重新下载下插件再修改
新的规则是:
积分奖池在抽奖发布或后续添加积分奖时预先从发起人扣除;定时开奖、满人数开奖和手动开奖都走同一个发放函数;发布后无论开奖、取消或删除奖品,预付积分都不退,避免作弊。现有固定“押金积分”配置会改为实际预付奖池金额,避免重复扣款。现在规则如下:
积分奖按“份数 × 每份积分”计算预付奖池。
发起人积分不足时无法发布抽奖帖。
后续添加积分奖也会立即扣除对应积分。
满人数、定时和手动开奖统一从预付奖池发放。
开奖状态、中奖记录和积分发放在同一事务内完成。
取消抽奖或删除奖品,预付积分均不退还。
后台原来的固定押金配置已移除,改为显示实际累计预付奖池。我按你修改后的 1.2.1 重新下载作为基线,已经更新到 1.3.1。
这次先补了三块底层:
- 积分奖池增加预付/发放/作废流水,取消抽奖不再删除记录,保留 cancelled 状态和审计快照。
- 承诺值从只锁随机种子扩展为 SHA256(seed + 完整规则快照);第一个有效参与者出现后锁定条件和奖品,不能中途删奖或改门槛。
- 参与登记与开奖在 MySQL/PostgreSQL 用行锁串行;SQLite 继续依赖 BEGIN IMMEDIATE。实测还发现 v8.5.15 的 SQLite 外层 tx() 里调用 user_points_change() 仍会触发 cannot start a transaction within a transaction,因此积分发放改成在开奖事务里直接更新用户积分并写通知,避免插件自动停用。
新增的可选功能:
- 参与资格:用户组、注册天数、最低积分、排除禁言/封禁用户;
- 最低参与人数:不足时等待手动处理、自动延期或按当前人数开奖;
- 兑换码/手工奖领取流程:待发奖、已发送、已确认、申诉和领取期限。
1.3.0 发布后的质量报告把同步开奖的系统表写入继续归到 reply.after_save,所以 1.3.1 又把满人数路径改为只推进 draw_at,实际开奖统一交给每分钟计划任务。现在质量报告已重新生成,reply.after_save 不再有“修正❗️”;满人数开奖最多延迟约一分钟。
谁主张谁举证=谁发起谁出积分
辛苦了
已更新到 1.4.0。
发帖页「设为抽奖帖」和高级规则里的 checkbox 现在固定为核心开关的 38×22px,
flex:0 0 38px,不会再被文字或网格挤压。同时一起修了 topic/338 反馈的参与人数少统计:原因是把已有回复的普通主题后来转换成抽奖帖时,旧回复不会触发
reply.after_save,所以转换前的参与者漏记。1.4.0 在转换时批量回溯已有回复,按用户去重并应用字数、用户组、注册天数、积分和禁言规则;升级时也会一次性校准全部进行中抽奖的entry_count。主题查看页面不增加查询。本地实测:已有主题先放入一条其他用户回复,再设为抽奖帖,保存后立即显示 1 人参与;开关计算宽度 38px。
不错👍
积分抽奖
不错👍
哇,这速度
不错
过时间了么
还不错。。。。
版本 1.4.0 更新:
优化小细节版本 1.4.1 更新:
增加版本号版本 1.5.0 更新:
在主程序升级到9.0之前,请不要更新此插件,避免兼容问题版本 1.5.8 更新:
优化UI版本 1.5.9 更新:
修改样式细节有个预览图最好了
版本 1.6.0 更新:
- 积分到账从「与开奖同一事务」变为「开奖提交后立即发放」; point_awards 通过 tx() 返回值传递,事务回滚时不会误发;
- 积分到账通知由内核生成(格式与原来一致), plugin_lottery_point_ledger 的 award 台账在积分实际入账成功后才写入;
版本 1.6.0 更新:
预览图版本 1.6.2 更新:
修正通知格式或者积分变动不进积分规则的问题版本 1.6.4 更新:
优化UI
