/**
* 根據權重數組返回中獎索引
* @param array $weights 索引=>權重值(整數),如 [100, 200, 300]
* @return int 中獎項的鍵名
*/
function getRand(array $weights) {
$total = array_sum($weights); // 權重總和
foreach ($weights as $key => $weight) {
$rand = mt_rand(1, $total); // 1 到總權重
if ($rand <= $weight) {
return $key; // 命中
}
$total -= $weight; // 未命中,縮小範圍
}
return array_key_last($weights); // 理論不可達,兜底返回最後一項
}
原理解析:將權重視為數軸區間長度。如權重 [10, 20, 30] 總和 60。隨機數落在 1-10 選第 0 項,11-30 選第 1 項,31-60 選第 2 項。每次未命中扣除當前權重,等價於在剩餘區間重新隨機。
$prizes = [
['id' => 1, 'name' => '平板電腦', 'weight' => 1, 'limit' => 10], // 總量限制
['id' => 2, 'name' => '數碼相機', 'weight' => 5, 'limit' => 50],
['id' => 3, 'name' => '音箱設備', 'weight' => 10, 'limit' => 200],
['id' => 4, 'name' => '4G 優盤', 'weight' => 12, 'limit' => 500],
['id' => 5, 'name' => '10 Q幣', 'weight' => 22, 'limit' => 1000],
['id' => 6, 'name' => '下次沒準就能中', 'weight' => 50, 'limit' => 0], // 0=無限
];
0 ? ($p['limit'] - getAwardedCount($p['id'])) : INF;
if ($remaining > 0) {
$weights[$p['id']] = $p['weight'];
$validPrizes[$p['id']] = $p;
}
}
// 3. 執行抽獎
if (empty($weights)) {
echo json_encode(['code' => 400, 'msg' => '獎品已發完']);
exit;
}
$wonId = getRand($weights);
$wonPrize = $validPrizes[$wonId];
// 4. 記錄中獎(資料庫事務)
$awarded = recordAward($wonPrize['id'], $_SESSION['user_id']);
if (!$awarded) {
echo json_encode(['code' => 500, 'msg' => '系統繁忙']);
exit;
}
// 5. 構建返回:中獎項 + 未中獎項(供前端展示)
unset($validPrizes[$wonId]);
$otherPrizes = array_values($validPrizes);
shuffle($otherPrizes); // 打亂順序
echo json_encode([
'code' => 200,
'win' => ['id' => $wonPrize['id'], 'name' => $wonPrize['name']],
'others' => array_map(fn($p) => ['id' => $p['id'], 'name' => $p['name']], $otherPrizes)
]);
// 用戶點擊「開始抽獎」
$('#lottery-btn').click(function() {
$(this).prop('disabled', true).text('抽獎中...');
$.post('ajax_lottery.php', {}, function(res) {
if (res.code !== 200) {
alert(res.msg);
$('#lottery-btn').prop('disabled', false).text('開始抽獎');
return;
}
// 動畫展示(簡化:直接顯示結果)
showResult(res.win, res.others);
$('#lottery-btn').prop('disabled', false).text('再來一次');
}, 'json');
});
function showResult(win, others) {
// 翻轉動畫、彈窗等 UI 邏輯
console.log('恭喜獲得:', win.name);
console.log('其他獎品:', others.map(o => o.name).join(', '));
}
function getRewardSimple($total = 10000) {
$pool = [];
// 按權重比例填充池
$pool = array_merge($pool, array_fill(0, 12, '平板電腦')); // 0.12%
$pool = array_merge($pool, array_fill(0, 300, '數碼相機')); // 3%
$pool = array_merge($pool, array_fill(0, 1200, '音箱設備')); // 12%
$pool = array_merge($pool, array_fill(0, 8488, '謝謝惠顧')); // 84.88%
shuffle($pool); // 打亂
return $pool[array_rand($pool)];
}
優點:代碼極簡、概率精確可控。
缺點:記憶體佔用大(百萬獎池需百萬元素數組)、動態調整權重需重建數組。
| 風險 | 對策 |
|---|---|
| 前端篡改請求參數 | 服務端驗證用戶身份、IP 頻率限制、Token 一次性驗證 |
| 內部人員操控權重 | 權重配置上鏈/存儲不可變日誌、多簽審批修改 |
| 機器人批量刷獎 | 驗證碼、設備指紋、行為分析、賬號等級門檻 |
| 中獎結果可預測 | 使用 CSPRNG (random_int) 替代 mt_rand、服務端種子加密 |
getRand() 高效、記憶體恆定、易擴展動態權重。