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

PHP 抽獎算法實現:概率控制、獎品庫存與防刷完整方案

     編輯:PHP 綜合知識

需求背景

營銷活動中抽獎功能需滿足:可配置中獎概率、真隨機性、獎品數量限制、用戶參與次數限制(基於 QQ/手機/IP)。本文基於 ThinkPHP 3.x 實現一套生產可用的階段式抽獎邏輯。

核心數據結構

獎品陣列按 中獎概率從小到大 排序,必須包含「不中獎」項,字段含義:

  • id:獎次序號
  • prize:獎品描述
  • v:權重值(相對概率)
  • num:庫存數量
$prize_arr = array(
  0 => array('id'=>1, 'prize'=>'44元購買1G/年空間', 'v'=>1,  'num'=>1),
  1 => array('id'=>2, 'prize'=>'55元購買1G/年空間', 'v'=>2,  'num'=>2),
  2 => array('id'=>3, 'prize'=>'66元購買1G/年空間', 'v'=>5,  'num'=>2),
  3 => array('id'=>4, 'prize'=>'77元購買1G/年空間', 'v'=>10, 'num'=>3),
  4 => array('id'=>5, 'prize'=>'88元購買1G/年空間', 'v'=>15, 'num'=>4),
  5 => array('id'=>6, 'prize'=>'99元購買1G/年空間', 'v'=>67, 'num'=>10), // 大獎/安慰獎
);

關鍵算法:按權重隨機抽取

private function get_rand($proArr) {
  $proSum = array_sum($proArr);           // 總權重
  foreach ($proArr as $key => $proCur) {
    $randNum = mt_rand(1, $proSum);       // 1~總權重
    if ($randNum <= $proCur) {
      return $key;                        // 命中
    }
    $proSum -= $proCur;                   // 權重前移
  }
  return ''; // 理論不會走到這裡
}

該算法時間複雜度 O(n),單次抽獎耗時微秒級,支撐高併發無壓力。

業務邏輯流程

  1. 時間窗口校驗:未開始/已結束直接攔截
  2. 用戶參與次數檢查:查詢數據庫該 QQ 記錄數 ≥ 3 則拒絕
  3. 概率抽取獎品 ID:$rid = $this->get_rand($arr)
  4. 庫存扣減校驗:該獎品已中出數量 ≥ num 則視為「獎品已抽完」,轉為不中獎
  5. 寫入記錄:保存 rid、獎品信息、QQ、時間戳
  6. 返回結果:AJAX 回傳中獎文案

擴展建議

  • 多時間段:將 begin_time/stop_time 改為陣列,支援每日定時開獎
  • 用戶體系整合:替換 QQ 為用戶 ID、手機號、OpenID 等
  • 防作弊加強:引入 Redis 計數器原子遞增、IP 限流、設備指紋
  • 數據一致性:高併發場景下建議使用事務或 Lua 腳本保證「抽獎-扣庫存」原子性

完整控制器代碼

<?php
class ChoujiangAction extends Action {
  var $begin_time = "2012-12-25 14:00:00";
  var $stop_time  = "0";
  var $prize_arr  = [ /* 如上 */ ];

  public function index() {
    $Choujiang = M('Choujiang');
    $this->assign('list', $Choujiang->where("rid>0")->order('id desc')->select());
    $this->assign('begin_time', $this->begin_time);
    $this->display();
  }

  public function make() {
    $qq_no = trim($_POST['qq_no']);
    import('ORG.Util.Input');
    $qq_no = Input::getVar($qq_no);
    if (empty($qq_no)) $this->ajaxReturn(1, '請正確填寫QQ號碼!');

    // 時間校驗
    if ($this->begin_time && time() < strtotime($this->begin_time))
      $this->ajaxReturn(1, '抽獎還沒有開始...');
    if ($this->stop_time && time() > strtotime($this->stop_time))
      $this->ajaxReturn(1, '本次抽獎已經結束...');

    $prize_arr = $this->prize_arr;
    foreach ($prize_arr as $k => $v) $arr[$v['id']] = $v['v'];
    $rid = $this->get_rand($arr);
    $str = $prize_arr[$rid - 1]['prize'];

    $Choujiang = M('Choujiang');
    // 次數限制
    if ($Choujiang->where("qq_no='{$qq_no}'")->count() >= 3) {
      $str = '您3次抽獎機會已經用完!'; $rid = 0;
    }
    // 庫存限制
    elseif ($Choujiang->where("rid={$rid}")->count() >= $prize_arr[$rid-1]['num']) {
      $str = '很抱歉,您所抽中的獎項已經中完!'; $rid = 0;
    }

    $data = array('rid'=>$rid, 'pop'=>$str, 'qq_no'=>$qq_no, 'input_time'=>time());
    $Choujiang->add($data);
    $this->ajaxReturn(1, $str);
  }

  private function get_rand($proArr) { /* 如上 */ }
}
?>

總結

這套方案邏輯清晰、擴展性強,通過「權重隨機 + 庫存校驗 + 次數限制」三重保障,滿足多數營銷抽獎場景。實際部署時請結合 Redis、資料庫事務進一步提升可靠性。

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