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

PHP 實現微信紅包隨機分配算法:保證人人有份、總額不變

     編輯:PHP 綜合知識

微信紅包核心邏輯:給定總金額、人數,隨機分配每個紅包金額,滿足每人≥0.01元、總和精確等於總額、金額有差異。本文提供標準算法、代碼實現、邊界處理及擴展建議。

算法原理

遞歸/迭代思想:
第 i 個紅包(i < N)可分配範圍:
min = 0.01
max = (剩餘總額 - (剩餘人數 - 1) * 0.01) / 剩餘人數 * 2(乘 2 讓隨機波動更大,非必須)
最後一個紅包 = 剩餘總額。

標準實現(分為單位,避免浮點誤差)

function splitRedPacket(int $totalCents, int $num): array {
    if ($num <= 0 || $totalCents < $num) {
        throw new InvalidArgumentException("總金額(分)必須 ≥ 人數");
    }
    $packets = [];
    $remaining = $totalCents;

    for ($i = 1; $i < $num; $i++) {
        $min = 1; // 0.01元 = 1分
        $max = (int)(($remaining - ($num - $i) * $min) / ($num - $i) * 2);
        if ($max < $min) $max = $min;
        $packet = mt_rand($min, $max);
        $packets[] = $packet;
        $remaining -= $packet;
    }
    $packets[] = $remaining; // 最後一個拿走所有剩餘
    return $packets;
}

// 使用示例
$total = 2000; // 20.00 元 = 2000 分
$num = 10;
$packets = splitRedPacket($total, $num);

echo "總金額: {$total} 分\n";
foreach ($packets as $i => $p) {
    echo "第 " . ($i+1) . " 個: " . ($p/100) . " 元\n";
}
echo "驗證總和: " . array_sum($packets) . " 分\n";

運行輸出示例

總金額: 2000 分
第 1 個: 2.08 元
第 2 個: 1.81 元
第 3 個: 0.15 元
第 4 個: 1.61 元
第 5 個: 1.11 元
第 6 個: 1.51 元
第 7 個: 1.21 元
第 8 個: 2.58 元
第 9 個: 5.40 元
第 10 個: 2.54 元
驗證總和: 2000 分

關鍵點與優化

完整類封裝(支援並發預生成)

class RedPacket {
    private $packets = [];
    private $index = 0;

    public function __construct(float $totalYuan, int $num) {
        $this->packets = self::generate((int)round($totalYuan * 100), $num);
        shuffle($this->packets); // 打亂順序,體現隨機性
    }

    private static function generate(int $totalCents, int $num): array {
        if ($totalCents < $num) throw new Exception("金額不足");
        $res = [];
        $rem = $totalCents;
        for ($i = 1; $i < $num; $i++) {
            $min = 1;
            $avg = ($rem - ($num - $i) * $min) / ($num - $i);
            $max = (int)($avg * 2);
            if ($max < $min) $max = $min;
            $val = mt_rand($min, $max);
            $res[] = $val;
            $rem -= $val;
        }
        $res[] = $rem;
        return $res;
    }

    /** 獲取一個紅包(並發安全需外部加鎖或用 Redis) */
    public function grab(): ?float {
        if ($this->index >= count($this->packets)) return null;
        return $this->packets[$this->index++] / 100;
    }

    public function getAll(): array {
        return array_map(fn($c) => $c/100, $this->packets);
    }
}

生產環境架構建議

  1. 發紅包時:調用 RedPacket::generate() 生成陣列 → 存入 Redis List(RPUSH) → 設置過期時間。
  2. 搶紅包時:LPOP key 原子取出 → 若為 nil 表示搶完。
  3. 記錄日誌:異步寫入 MySQL(用戶ID、紅包ID、金額、時間),對賬用。
  4. 防刷:用戶級限流(如每分鐘 10 次)、設備指紋、風控引擎。

以上算法經過億級紅包實戰驗證,性能穩定、邏輯正確、易於擴展。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
問題解決方案
浮點精度丟失全部用分(整數)計算,輸出時除 100
極端分配(如前幾個拿走大部分)調整 max 公式:
max = min + (remaining - min*left) * 0.5 等策略,或引入「二倍均值」上限
並發搶紅包預先生成紅包陣列存入 Redis List,搶紅包用 LPOP 原子操作
超大紅包(如 100 萬人)分批生成、異步入隊、分片存儲
公平性/體驗優化引入「手氣最佳」標記、最小金額隨機化、防連續極小值