抽奖转盘核心是概率分配与结果一致性,需用累积概率+二分查找优化性能,事务保障扣库存/记日志/更状态,redis限频+签名防刷缺一不可。

抽奖转盘的核心不是转盘动画,而是概率分配与结果一致性
ThinkPHP 本身不提供抽奖逻辑,所谓“ThinkPHP 实现抽奖转盘”,本质是用 ThinkPHP 控制器承接请求、调用自定义概率算法、查库校验、记录中奖日志,并返回结构化结果。前端转盘只是视觉层,后端必须保证:同一抽奖请求下,概率计算不可被预测、不可被重放、不可因并发错乱。常见翻车点是直接用 mt_rand() + 静态权重数组,没做原子扣减或防刷校验,导致高并发时重复中奖或库存超发。
用「累积概率 + 二分查找」替代简单遍历,避免权重膨胀问题
当奖品超过 10 个、权重粒度细(如 0.5%、3.7%),用 foreach 累加判断会变慢且难维护。推荐预计算累积概率数组,再用二分法定位中奖项——时间复杂度从 O(n) 降到 O(log n),且便于缓存。
示例逻辑(放在模型或服务类中):
// $prizes 是数据库查出的奖品列表,含 id/name/probability(单位:万分比,整数)
// 如:[['id'=>1,'name'=>'谢谢参与','probability'=>8500], ['id'=>2,'name'=>'一等奖','probability'=>5]]
$sum = 0;
$cumulative = [];
foreach ($prizes as $p) {
$sum += $p['probability'];
$cumulative[] = ['end' => $sum, 'prize' => $p];
}
$rand = mt_rand(1, $sum);
// 二分查找 $cumulative 中第一个 end >= $rand 的项
$left = 0; $right = count($cumulative) - 1;
while ($left <p>注意:<code>probability</code> 必须为整数、总和等于 10000(或你约定的基数),不能用浮点数存库,否则累积误差会导致漏奖或越界。</p><h3>必须在事务中完成「扣库存 + 记录中奖 + 更新用户状态」</h3><p>抽奖不是只算出结果就完事。尤其对实物奖品或限次奖品,以下三步必须包裹在数据库事务里,且加行锁防并发:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5233" title="btpanel phpsite 宝塔面板PHP网站"><img
src="https://img.php.cn/upload/skill/000/000/081/179040786932301.jpg" alt="btpanel phpsite 宝塔面板PHP网站" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill5233" title="btpanel phpsite 宝塔面板PHP网站" class="overflowclass">btpanel phpsite 宝塔面板PHP网站</a>
<p class="overflowclass">宝塔面板 PHP 网站管理:站点创建、删除、启停、PHP 版本切换、域名管理、SSL证书管理、伪静态管理、数据库管理</p>
</div>
<a rel="nofollow" href="/xiazai/skill5233" title="btpanel phpsite 宝塔面板PHP网站" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 查
prize_stock表当前剩余库存(SELECT ... FOR UPDATE) - 若库存 > 0,则
UPDATE prize_stock SET stock = stock - 1 WHERE id = ? AND stock > 0(WHERE 条件带 stock > 0 是关键) - 插入中奖记录到
user_prize_log,同时更新用户抽奖次数字段(如user->last_lottery_time)
如果用 ThinkPHP 的 Db::transaction(),记得捕获异常并回滚;更稳妥的做法是在 SQL 层用存储过程封装这三步,减少网络往返和 PHP 层逻辑泄露风险。
防刷和限频不能只靠前端或 session
用户用 Postman 或脚本反复请求 /api/lottery,session 和前端按钮禁用形同虚设。必须服务端强制校验:
- 用 Redis 记录
lottery:uid:{user_id}最近一次抽奖时间,限制 24 小时最多 3 次(key 过期设为 86400 秒) - 对高频 IP(如 1 分钟内 > 10 次)写入黑名单,用
cache('ip_block:'.$ip, true, 3600) - 每次请求校验
sign参数:将 timestamp + user_id + 随机字符串用私钥签名,防止参数篡改
别省略签名验证——很多团队只做频率限制,结果被批量脚本绕过,奖品全被羊毛党扫光。
概率算法本身不难,难的是让每一次抽奖都真实、可追溯、不可抵赖。库存扣减、事务边界、防刷签名,这三个环节漏掉任意一个,上线后基本等于送钱。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










