最近帮朋友调试一个双十一抽奖系统时,发现他们设置的「华为Mate60手机」奖品被同个用户抽中三次——这要真上线了,估计财务部得连夜追杀技术部。今天就和大家聊聊在ThinkPHP框架里,怎么像超市大妈管控特价鸡蛋那样牢牢管住奖品数量。
一、奖品池的数据库设计
就像在菜市场买菜得先准备零钱,咱们得先在数据库里搭建好奖品池。建议在奖品表里增加这几个关键字段:
- total_num:奖品总库存
- day_num:每日限量(适合长期活动)
- user_day_limit:用户每日限抽次数
- probability:中奖概率(建议用万分制)
// 迁移文件示例 Schema::create('prizes', function (Blueprint $table) { $table->integer('total_num')->comment('总库存'); $table->integer('day_num')->default(0)->comment('日库存'); $table->integer('user_day_limit')->default(1)->comment('用户日限抽'); });
1.1 库存更新的原子操作
记得用「库存卫士」守护你的奖品数量,就像超市收银员扫码那样严谨:
Db::name('prize') ->where('id', $prizeId) ->where('total_num', '>', 0) ->dec('total_num') ->update;
二、多维度限制策略
根据《电商促销系统设计规范》,完整的限制策略应该像洋葱有多层防护:
限制维度 | 适用场景 | 实现方式 | 数据来源 |
全局总量 | 稀缺奖品(如手机) | 数据库原子操作 | 《ThinkPHP数据库操作手册》 |
用户日次数 | 防羊毛党 | Redis计数器 | 《Redis实战》第3章 |
活动时段 | 限时抢购 | Crontab定时任务 | Linux系统管理指南 |
2.1 中间件拦截器
就像小区门禁系统,在请求进入核心逻辑前先做过滤:
class LotteryLimit public function handle($request, \\Closure $next) $userId = Session::get('user_id'); if(Redis::get("user:{$userId}:today_count") >= 3){ return json(['code'=>400,'msg'=>'今日机会已用完']); return $next($request);
三、概率算法的艺术
根据《游戏数值设计入门》里的建议,别直接用rand函数,试试这个经典算法:
function getPrize($prizes){ $proArr = array_column($prizes, 'probability'); $result = null; // 概率数组循环 $proSum = array_sum($proArr); $randNum = mt_rand(1, $proSum); foreach ($prozes as $key => $proVal) { if ($randNum <= $proVal) { $result = $key; break; } else { $randNum -= $proVal; return $result;
调试的时候发现个有趣现象——把谢谢惠顾的概率设为9990,实际测试时前100次抽奖竟然中了两次大奖。后来改用分段累进算法才解决这个问题,具体可参考《概率论在游戏中的应用》。
四、应急熔断机制
就像电热水壶的自动断电保护,我们在控制器里加了这么个钩子:
// 当某奖品库存低于5%时触发预警 if($prize['total_num'] < ($prize['init_num']0.05)){ Mail::send('[email protected]','库存预警邮件',"奖品{$prize['name']}即将售罄");
上周做压力测试时,这个机制成功拦截了某Bug导致的「无限抽奖」事故。当时模拟500并发用户,库存1000的奖品在3秒内就被抢光,但系统及时锁死了该奖品的抽取通道。
4.1 数据统计看板
用ECharts做了个实时监控面板,老板最喜欢看那个动态更新的「已抽中/剩余」柱状图。核心SQL大概是这样的:
SELECT DATE_FORMAT(create_time,'%Y-%m-%d') as day, prize_id, count as total FROM lottery_records GROUP BY day,prize_id ORDER BY day DESC
调试抽奖系统就像在游乐场维护旋转木马——既要保证运转顺畅,又要防止有人从马上摔下来。上周五下班前,运营小妹跑来说想给VIP用户增加抽奖次数,我顺手在Redis里加了条白名单规则:
if(in_array($userId, VIPUsers::getIds)){ $maxTimes = 5; // VIP每日5次 } else { $maxTimes = 3;
现在系统平稳运行了两个促销周期,财务部再也没举着计算器来找过我们。倒是市场部同事总来打听,能不能把某个奖品的概率「稍微」调高那么一点点...
评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。
网友留言(0)