• 233
    抽奖帖
    ID lottery版本 1.6.4插件制作者 233免费1331 行 / 92.1 KBHook / 路由 / 后台页
    把帖子变成抽奖:读者回复即参与,到时间或满人数自动开奖,中奖名单公开且可复核。
    app/plugins/lottery/plugin.php
    开发日志已有 8 条

    个人独立维护项目。

    • bbs1org
      bbs1org
      版本 1.6.4 fd6ae948475b 更新:优化UIrelease
    • bbs1org
      bbs1org
      版本 1.6.2 dce9e75a8290 更新:修正通知格式或者积分变动不进积分规则的问题
    • bbs1org
      bbs1org
      版本 1.6.0 83cbbc5b98f7 更新:预览图
    • bbs1org
      bbs1org
      版本 1.5.9 4cc1199129b6 更新:修改样式细节
    • bbs1org
      bbs1org
      版本 1.5.8 e3bb77d3601a 更新:优化UI
    • bbs1org
      bbs1org
      版本 1.5.0 64d142bee276 更新:在主程序升级到9.0之前,请不要更新此插件,避免兼容问题
    • bbs1org
      bbs1org
      版本 1.4.1 5a411abc46d3 更新:增加版本号
    • bbs1org
      bbs1org
      版本 1.4.0 63198ce47d38 更新:优化小细节
    主楼
  • 质量报告

    插件质量报告

    插件:lottery

    Hook功能范围循环使用频率文件读写修改系统表读写系统表读写自己的表
    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_usersapp_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_iduint否-主键
    creator_iduint否--
    min_charsuint否5-
    draw_atuint否0-
    draw_countuint否0-
    statuskey否'open'-
    entry_countuint否0-
    commit_hashkey否''-
    seedkey否''-
    deposit_pointsuint否0-
    min_entriesuint否0-
    insufficient_actionkey否'wait'-
    postpone_hoursuint否24-
    max_postponesuint否3-
    postpone_countuint否0-
    eligibility_groupsstring否''-
    min_account_daysuint否0-
    min_user_pointsuint否0-
    exclude_restricteduint否1-
    claim_daysuint否7-
    rules_lockeduint否0-
    rules_hashkey否''-
    rules_snapshottext是--
    drawn_atuint否0-
    created_atuint否--

    plugin_lottery_prizes

    字段类型可空默认值约束
    idid否-主键、自增
    topic_iduint否--
    namestring否--
    kindkey否--
    quantityuint否1-
    unit_pointsuint否0-
    sort_orderuint否0-

    plugin_lottery_entries

    字段类型可空默认值约束
    idid否-主键、自增
    topic_iduint否-唯一
    user_iduint否-唯一
    reply_iduint否0-
    created_atuint否--

    plugin_lottery_winners

    字段类型可空默认值约束
    idid否-主键、自增
    topic_iduint否-唯一
    prize_iduint否--
    user_iduint否-唯一
    prize_namestring否''-
    created_atuint否--

    plugin_lottery_point_ledger

    字段类型可空默认值约束
    idid否-主键、自增
    topic_iduint否--
    prize_iduint否0-
    user_iduint否0-
    kindkey否--
    pointsuint否0-
    notestring否''-
    created_atuint否--

    plugin_lottery_claims

    字段类型可空默认值约束
    idid否-主键、自增
    topic_iduint否-唯一
    prize_iduint否-唯一
    user_iduint否-唯一
    statuskey否'pending'-
    deadline_atuint否0-
    notestring否''-
    created_atuint否--
    updated_atuint否--
    #1
  • 233

    补充说明质量报告里 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。

    如果你确认是误报,这条就当我没说;如果你希望不管代价一定要消掉这个标记,说一声我再改一版。

    #2
  • bbs1org

    插件启用后,发帖页面没有入口。修正一下bug

    #3
  • bbs1org

    要有管理页面,设置参数。比如扣除押金积分,可用用户组等

    #4
  • one

    ding,禁言后的用户怎么恢复?

    #5
  • 233

    #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 里说的三轮排查结论还是没变,如果你看过觉得该按你的规则改,我照改。

    #6
  • bbs1org

    积分中奖后,积分是自动给中奖者?那积分从哪里出?

    #7
  • one

    你出。🤓

    #8
  • bbs1org

    我更新了下插件。修改的时候,请重新下载下插件再修改
    新的规则是:
    积分奖池在抽奖发布或后续添加积分奖时预先从发起人扣除;定时开奖、满人数开奖和手动开奖都走同一个发放函数;发布后无论开奖、取消或删除奖品,预付积分都不退,避免作弊。现有固定“押金积分”配置会改为实际预付奖池金额,避免重复扣款。

    现在规则如下:
    积分奖按“份数 × 每份积分”计算预付奖池。
    发起人积分不足时无法发布抽奖帖。
    后续添加积分奖也会立即扣除对应积分。
    满人数、定时和手动开奖统一从预付奖池发放。
    开奖状态、中奖记录和积分发放在同一事务内完成。
    取消抽奖或删除奖品,预付积分均不退还。
    后台原来的固定押金配置已移除,改为显示实际累计预付奖池。

    #10
  • 233

    我按你修改后的 1.2.1 重新下载作为基线,已经更新到 1.3.1。

    这次先补了三块底层:

    1. 积分奖池增加预付/发放/作废流水,取消抽奖不再删除记录,保留 cancelled 状态和审计快照。
    2. 承诺值从只锁随机种子扩展为 SHA256(seed + 完整规则快照);第一个有效参与者出现后锁定条件和奖品,不能中途删奖或改门槛。
    3. 参与登记与开奖在 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 不再有“修正❗️”;满人数开奖最多延迟约一分钟。

    #11
  • x

    辛苦了

    #12
  • uscncn

    谁主张谁举证=谁发起谁出积分

    #9
  • Frey

    辛苦了

    #13
  • bbs1org

    @233 设为抽奖帖 表单ui优化。checkbox 被挤压
    paste-20260811-123048.png

    #14
  • 233

    已更新到 1.4.0。

    发帖页「设为抽奖帖」和高级规则里的 checkbox 现在固定为核心开关的 38×22px,flex:0 0 38px,不会再被文字或网格挤压。

    同时一起修了 topic/338 反馈的参与人数少统计:原因是把已有回复的普通主题后来转换成抽奖帖时,旧回复不会触发 reply.after_save,所以转换前的参与者漏记。1.4.0 在转换时批量回溯已有回复,按用户去重并应用字数、用户组、注册天数、积分和禁言规则;升级时也会一次性校准全部进行中抽奖的 entry_count。主题查看页面不增加查询。

    本地实测:已有主题先放入一条其他用户回复,再设为抽奖帖,保存后立即显示 1 人参与;开关计算宽度 38px。

    #19
  • axdd

    不错👍

    #15
  • baymax

    积分抽奖

    #16
  • idc

    不错👍

    #17
  • Moo

    哇,这速度

    #18
  • 8

    不错

    #20
  • mixfix

    过时间了么

    #21
  • code

    还不错。。。。

    #22
  • bbs1org

    版本 1.4.0 更新:
    优化小细节

    #23
  • bbs1org

    版本 1.4.1 更新:
    增加版本号

    #24
  • bbs1org

    版本 1.5.0 更新:
    在主程序升级到9.0之前,请不要更新此插件,避免兼容问题

    #25
  • bbs1org

    版本 1.5.8 更新:
    优化UI

    #26
  • bbs1org

    版本 1.5.9 更新:
    修改样式细节

    #27
  • awyme

    有个预览图最好了

    #28
  • bbs1org

    版本 1.6.0 更新:

    • 积分到账从「与开奖同一事务」变为「开奖提交后立即发放」; point_awards 通过 tx() 返回值传递,事务回滚时不会误发;
    • 积分到账通知由内核生成(格式与原来一致), plugin_lottery_point_ledger 的 award 台账在积分实际入账成功后才写入;
    #29
  • bbs1org

    版本 1.6.0 更新:
    预览图

    #30
  • bbs1org

    版本 1.6.2 更新:
    修正通知格式或者积分变动不进积分规则的问题

    #31
  • bbs1org

    版本 1.6.4 更新:
    优化UI

    #32

发表回复

登录后回复