本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/plugins/lucky_draw/说明.md。
幸运转盘(lucky_draw)插件说明
版本:1.0.1 | 作者:FlintHub | 状态:✅ 已启用(后台「插件」页可停用)
功能:消耗积分随机抽取奖品。管理员配置奖品池(名称 / 权重 / 库存 / 稀有度 N·R·SR·SSR),
用户在前台转盘页点击抽奖,
Points::deduct()原子扣分后按权重定奖,积分奖品即时到账。
一、功能简介
- 抽奖:
random_int()按权重累计抽取;并发下占用库存走
UPDATE … SET stock = CASE WHEN stock > 0 THEN stock - 1 ELSE stock END WHERE id = :id AND stock <> 0,
rowCount()=0 即被抢光 → 自动退分
- 积分:扣分走核心
Points::deduct()(条件 UPDATE 原子校验余额,余额不足返回 false);
积分奖品走 Points::award() 即时发放;扣分后任何失败都按「撤奖 → 还库存 → 退抽奖成本」逆序补偿
(插件独立库与核心库没有分布式事务)
- 限额:单次消耗积分(
0= 免费抽奖,不调用Points::deduct)、每日次数上限(0 = 不限)均在后台可配 - 稀有度:N / R / SR / SSR 四档,仅作展示徽章与转盘配色,不影响概率
- 库存:
-1= 不限库存(占用时原样保留,仍算占用成功);0= 暂时抽空(自动退出可抽集合) - 记录:每次抽奖写一条流水(冗余奖品名/稀有度/成本,历史记录不随奖品编辑而漂移)
- 前端:抽奖为 AJAX JSON 接口;转盘为 CSS
conic-gradient按权重生成扇区,
指针停靠位置由服务端定奖结果驱动(动画是表现,结果是权威);按钮禁用时给出具体原因,
抽完之后若余额不足或次数用尽,前端即时禁用按钮,不再让用户一路点进服务端错误
- 布局:对齐全站「积分类应用页」范式(
points_mall/task_center/community_bank)——
头部 h1.mn-fs-20.mn-fw-600 + 右侧积分 chip + 信息型说明句,正文拆成
转盘 / 奖品池 / 记录三张独立 .mn-section 卡片,分区块标题用核心 .mn-section-header
二、目录结构
plugins/lucky_draw/
├── plugin.json # 清单:4 钩子 + route:admin / system:settings 权限
├── Plugin.php # 主类:建表 DDL / 配置读写 / 奖品池 / 定奖 / 库存 / 记录 / 生命周期
│ # ⚠️ 文件末尾有 class_alias:目录名含下划线,不写会被核心静默跳过生命周期
├── AdminController.php # 后台:配置保存 + 奖品池增删改(PRG + session flash)
├── FrontController.php # 前台:转盘页 / 抽奖接口 / 记录页
├── hook/
│ ├── init_after.php # marker 版本化门控的惰性建表(data/.lucky_draw_schema)
│ ├── route_register.php # 前台路由
│ ├── admin_route_register.php# 后台路由(/admin 前缀由核心 group 自动补)
│ └── nav_plugin_links.php # 左栏「应用」+ 移动端「应用」下拉入口
├── views/
│ ├── admin.php # 后台:基本配置 Tab + 奖品池 Tab
│ ├── index.php # 前台:转盘 + 奖品池 + 最近记录
│ └── records.php # 前台:全部抽奖记录
├── assets/
│ ├── style.css # 转盘 / 徽章 / 表单样式(零内联)
│ └── script.js # 转盘生成与定奖动画、抽奖请求、后台 Tab、删除确认
├── data/ # 插件独立库(.htaccess / web.config 由核心自动补写)
└── lang/{zh,zh_tw,en}.php # 三语语言包(68 键,集合/键序一致)三、数据表(插件独立库 data/lucky_draw.sqlite)
| 表 | 用途 | 关键列 |
|---|---|---|
ld_prizes | 奖品池 | weight 权重 / stock 库存(-1 不限)/ rarity / prize_type(points\ |
ld_records | 抽奖流水 | user_id / prize_id / prize_name(冗余快照) / cost_points / created_at,索引 (user_id, created_at) |
全局设置(核心 settings 表,键前缀 lucky_draw_):lucky_draw_enabled / lucky_draw_cost / lucky_draw_daily_limit,缺省值收敛在 Plugin::DEFAULTS 一处。
四、安装 / 卸载
- 启用:后台 → 插件 → 幸运转盘 → 启用。首次任一请求会经
init_after(marker 门控)建表并写入默认配置 - 卸载:
uninstall()顺序 = 删两张表 → 删data/.lucky_draw_schemamarker → 清核心 settings 残留 → 双清缓存。
marker 必须与表同生共死:表删了 marker 还在的话,重装后 init_after 会判定「schema 已是最新」直接跳过建表,
之后整站 /lucky-draw 全是 no such table 的 500
- 若手工删过表,也要同步删
data/.lucky_draw_schema
五、管理入口
- 后台地址:
/admin/lucky-draw(基本配置 + 奖品池两个页内 Tab) - 前台入口:左栏「应用」面板 / 移动端「应用」下拉 →
/lucky-draw(未登录点击会先跳登录)
六、更新记录
| 版本 | 内容 |
|---|---|
| 1.0.1 | 修复「不限库存(stock=-1)的奖品永远抽不到」:扣库存语句 WHERE stock > 0 对 -1 的 rowCount() 恒为 0,被调用方判成「已被抢空」并退分,而后台新增奖品时 stock 默认值恰好就是 -1(开箱即坏)。改为 CASE 分支 + WHERE stock <> 0<br>修复「单次消耗配成 0 时每次抽奖都报积分不足」:Points::deduct() 首行 if ($points <= 0) return false;,免费抽奖必须跳过扣分<br>失败补偿改为「撤奖 → 还库存 → 退抽奖成本」逆序,堵住「记录写失败但奖品积分已到账」的白嫖路径<br>清页缓存改走核心 PageCache::invalidate():原先 glob(__DIR__.'/../data/runtime/pages/*.html') 指向插件自己的 data 目录,从未生效过<br>删除奖品的确认框从内联 onsubmit 改为 data-ld-confirm + script.js 绑定(审计 FH-INL-002)<br>抽奖按钮禁用时显示具体原因;抽完后余额不足/次数用尽即时禁用<br>卸载时同步删除 schema marker;后台 Tab 补齐 aria-selected / aria-controls;修复 ?edit= 找不到奖品时的未定义变量告警<br>前台布局对齐全站积分类应用页范式:头部改为 h1.mn-fs-20.mn-fw-600 + 图标 + 右侧积分 chip(原为居中自写 22px 标题),副标题换成信息型文案(「每次消耗 N 积分,今日还可抽 M 次」),stats 精简为两块,正文拆成转盘/奖品池/记录三张独立 .mn-section;抽奖按钮改用核心 mn-btn mn-btn-primary(前台没有 .btn 基类),警示条改用自定义 .ld-alert-warn(核心没有 .mn-alert-warning),「查看全部」改用 .mn-section-more |
| 1.0.0 | 首个版本:奖品池 / 权重定奖 / 库存 / 每日限额 / 中奖记录 / 三语 |