首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 轉盤抽獎系統實作:機率控制與資料庫原子操作完整指南

     編輯:PHP 相關知識

轉盤抽獎是行銷活動常見功能,核心挑戰在於:機率精確控制、獎品庫存扣減原子性、防刷與狀態一致性。本文提供生產級 PHP 實作方案,涵介面設計、機率演算法、資料庫事務與併發控制。

系統架構設計

核心需求

  • 可配置獎品區塊與機率權重
  • 獎品庫存即時扣減,防止超發
  • 用戶積分驗證與扣除
  • 中獎記錄永久保存
  • 高併發下資料一致性

資料表結構

-- 獎品庫存表
CREATE TABLE prize_surplus (
    id INT PRIMARY KEY AUTO_INCREMENT,
    prize VARCHAR(50) NOT NULL,
    surplus INT DEFAULT 0,
    UNIQUE KEY uk_prize (prize)
);

-- 話費專用庫存(按日期)
CREATE TABLE telephone_charge_surplus (
    id INT PRIMARY KEY AUTO_INCREMENT,
    date DATE NOT NULL,
    charge_surplus INT DEFAULT 0,
    UNIQUE KEY uk_date (date)
);

-- 用戶積分表
CREATE TABLE zaszh_user (
    id INT PRIMARY KEY,
    answer_points INT DEFAULT 0
);

-- 積分消耗記錄
CREATE TABLE zaszh_answer_points_consume (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    points_consume INT,
    consume_for VARCHAR(20),
    create_date INT
);

-- 中獎記錄
CREATE TABLE zaszh_award (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    prize VARCHAR(50),
    create_date INT
);

核心實作:lottery_get.php

<?php
// 1. 參數驗證與初始化
$user_id = 1; // 實際應從 Session/Token 獲取

// 2. 獎品區塊配置(角度範圍 + 獎品名稱)
$arr_area = [
    0 => ['min_angle' => 1,   'max_angle' => 45,  'prize' => '小米手機'],
    1 => ['min_angle' => 46,  'max_angle' => 90,  'prize' => '拍立得'],
    2 => ['min_angle' => 91,  'max_angle' => 135, 'prize' => '10元話費'],
    3 => ['min_angle' => 136, 'max_angle' => 180, 'prize' => '5元話費'],
    4 => ['min_angle' => 181, 'max_angle' => 225, 'prize' => '謝謝參與'],
    5 => ['min_angle' => 226, 'max_angle' => 270, 'prize' => '謝謝參與'],
    6 => ['min_angle' => 271, 'max_angle' => 315, 'prize' => '謝謝參與'],
    7 => ['min_angle' => 316, 'max_angle' => 360, 'prize' => '謝謝參與'],
];

// 3. 機率抽獎演算法(兩階段權重)
$area_selected = [];
$num_rand = mt_rand(1, 10); // 1-10 權重基數

switch ($num_rand) {
    case 1: $area_selected = $arr_area[0]; break; // 10% 小米
    case 2: $area_selected = $arr_area[1]; break; // 10% 拍立得
    case 3: $area_selected = $arr_area[2]; break; // 10% 10元話費
    case 4: $area_selected = $arr_area[3]; break; // 10% 5元話費
    default: // 60% 謝謝參與(再細分4個區塊)
        $sub = mt_rand(1, 4);
        $area_selected = $arr_area[3 + $sub];
        break;
}

// 4. 資料庫連線
require('connect_database.php'); // 應回傳 $mysqli 實例

// 5. 事務處理:積分扣除 + 獎品發放
try {
    $mysqli->begin_transaction();
    
    // 5.1 扣除積分(原子操作:條件更新)
    $sql = "UPDATE zaszh_user 
            SET answer_points = answer_points - 5 
            WHERE id = ? AND answer_points >= 5";
    $stmt = $mysqli->prepare($sql);
    $stmt->bind_param('i', $user_id);
    $stmt->execute();
    
    if ($mysqli->affected_rows === 0) {
        throw new Exception('積分不足');
    }
    
    // 5.2 記錄積分消耗
    $sql = "INSERT INTO zaszh_answer_points_consume 
            (user_id, points_consume, consume_for, create_date) 
            VALUES (?, 5, 'lottery', UNIX_TIMESTAMP())";
    $stmt = $mysqli->prepare($sql);
    $stmt->bind_param('i', $user_id);
    $stmt->execute();
    
    // 5.3 根據獎品類型處理庫存與發放
    $prize = $area_selected['prize'];
    $awarded = false;
    
    switch ($prize) {
        case '小米手機':
        case '拍立得':
            // 實物獎品:扣減 prize_surplus
            $sql = "UPDATE prize_surplus 
                    SET surplus = surplus - 1 
                    WHERE prize = ? AND surplus > 0";
            $stmt = $mysqli->prepare($sql);
            $stmt->bind_param('s', $prize);
            $stmt->execute();
            
            if ($mysqli->affected_rows > 0) {
                $awarded = recordAward($mysqli, $user_id, $prize);
            } else {
                $prize = '謝謝參與'; // 庫存不足降級
            }
            break;
            
        case '10元話費':
        case '5元話費':
            // 話費:扣減 telephone_charge_surplus(按日期)
            $amount = ($prize === '10元話費') ? 10 : 5;
            $sql = "UPDATE telephone_charge_surplus 
                    SET charge_surplus = charge_surplus - ? 
                    WHERE date = CURDATE() AND charge_surplus >= ?";
            $stmt = $mysqli->prepare($sql);
            $stmt->bind_param('ii', $amount, $amount);
            $stmt->execute();
            
            if ($mysqli->affected_rows > 0) {
                $awarded = recordAward($mysqli, $user_id, $prize);
            } else {
                $prize = '謝謝參與';
            }
            break;
            
        default:
            $awarded = true; // 謝謝參與無需庫存
            break;
    }
    
    $mysqli->commit();
    
    // 6. 回傳結果
    echo json_encode([
        'status' => 'success',
        'msg' => $prize,
        'awarded' => $awarded
    ]);
    
} catch (Exception $e) {
    $mysqli->rollback();
    echo json_encode([
        'status' => 'error',
        'msg' => $e->getMessage()
    ]);
} finally {
    $mysqli->close();
}

// 輔助函數:記錄中獎
function recordAward($mysqli, $user_id, $prize) {
    $sql = "INSERT INTO zaszh_award (user_id, prize, create_date) 
            VALUES (?, ?, UNIX_TIMESTAMP())";
    $stmt = $mysqli->prepare($sql);
    $stmt->bind_param('is', $user_id, $prize);
    return $stmt->execute() && $mysqli->affected_rows > 0;
}
?>

關鍵技術深度解析

1. 機率演算法設計

採用兩階段權重:

  • 第一層:10% 大獎、10% 二獎、10% 三獎、10% 四獎、60% 謝謝參與
  • 第二層:謝謝參與再均分 4 個區塊(各 15%)
  • 對應前端轉盤角度區塊,確保視覺與機率一致

2. 原子性庫存扣減

-- 關鍵:WHERE 條件包含庫存檢查,單條 SQL 完成「判斷+扣減」
UPDATE prize_surplus SET surplus = surplus - 1 WHERE prize = '小米手機' AND surplus > 0

優勢:

  • 無競態條件:資料庫鎖機制保證原子性
  • 無需應用層鎖:簡化併發控制
  • 效能最佳:單次資料庫往返

3. 事務邊界設計

事務包含:積分扣除 → 消耗記錄 → 獎品庫存扣減 → 中獎記錄。任一步驟失敗全數回滾,確保:

  • 積分不會無故扣除
  • 庫存不會異常扣減
  • 中獎記錄與實際發放一致

進階優化方案

1. Redis 預扣庫存(高併發場景)

<?php
// 活動開始前將庫存載入 Redis
$redis->set('prize:xiaomi:stock', 100);
$redis->set('prize:polaroid:stock', 50);

// 抽獎時原子遞減
$remaining = $redis->decr('prize:xiaomi:stock');
if ($remaining < 0) {
    $redis->incr('prize:xiaomi:stock'); // 回滾
    // 庫存不足處理
}
?>

2. 機率配置資料庫化

CREATE TABLE lottery_config (
    id INT PRIMARY KEY,
    prize_name VARCHAR(50),
    weight INT,           -- 權重
    min_angle INT,
    max_angle INT,
    daily_limit INT,      -- 每日限量
    total_limit INT       -- 總限量
);

3. 防刷機制

  • 用戶每日抽獎次數限制
  • IP 頻率限制(Redis 計數器)
  • 裝置指紋識別
  • 驗證碼/滑塊驗證

前端整合建議

轉盤角度計算

// 後端回傳獎品對應角度範圍
$angle = mt_rand($area_selected['min_angle'], $area_selected['max_angle']);
// 前端 CSS 動畫
transform: rotate(${angle}deg);

介面回傳格式

{
    "status": "success",
    "msg": "小米手機",
    "angle": 23,
    "awarded": true,
    "remaining_points": 95
}

總結

PHP 轉盤抽獎系統核心在於:

  1. 機率演算法:權重配置與角度映射分離,易維護
  2. 原子操作:單條 SQL 條件更新保證庫存不超發
  3. 事務一致性:積分、庫存、記錄三位一體
  4. 擴展性:Redis 預扣、配置資料庫化支撐高併發

實戰中需根據流量規模選擇適當架構:中小型活動純 MySQL 事務足夠;大型高併發需引入 Redis 預扣庫存 + 異步寫入資料庫。安全性方面,務必加入防刷、簽名驗證與敏感操作審計日誌。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved