本文目录导读:

我来设计一个完整的PHP红包算法,包含多种实现方案和完整示例代码。
基础红包算法(随机分配)
普通随机算法(可能不够均匀)
<?php
/**
* 简单随机红包算法
* @param int $totalAmount 总金额(分)
* @param int $num 红包个数
* @return array 每个红包金额(分)
*/
function simpleRedPacket($totalAmount, $num) {
$result = [];
$remaining = $totalAmount;
for ($i = 0; $i < $num - 1; $i++) {
// 随机分配,但确保每个人至少1分钱
$max = $remaining - ($num - $i - 1); // 预留其他人的最少金额
$amount = rand(1, $max);
$result[] = $amount;
$remaining -= $amount;
}
// 最后一个红包拿剩下的
$result[] = $remaining;
return $result;
}
二倍均值法(微信红包算法)
<?php
/**
* 二倍均值法红包算法(更均匀)
* @param int $totalAmount 总金额(分)
* @param int $num 红包个数
* @return array 每个红包金额(分)
*/
function averageRedPacket($totalAmount, $num) {
$result = [];
$remaining = $totalAmount;
$remainingNum = $num;
for ($i = 0; $i < $num; $i++) {
if ($remainingNum == 1) {
$amount = $remaining;
} else {
// 最大金额 = 剩余金额 / 剩余个数 * 2
$max = (int)($remaining / $remainingNum * 2);
// 随机取 [1, max] 范围
$amount = rand(1, $max);
}
$result[] = $amount;
$remaining -= $amount;
$remainingNum--;
}
return $result;
}
高级红包算法(可配置参数)
<?php
class RedPacket {
private $totalAmount; // 总金额(分)
private $num; // 红包个数
private $minAmount; // 最小金额(分)
private $maxAmount; // 最大金额(分)
public function __construct($totalAmount, $num, $minAmount = 1, $maxAmount = null) {
$this->totalAmount = $totalAmount;
$this->num = $num;
$this->minAmount = $minAmount;
$this->maxAmount = $maxAmount ?? $totalAmount;
// 验证参数
if ($totalAmount < $num * $minAmount) {
throw new Exception("金额不足以分配");
}
}
/**
* 生成红包
* @return array
*/
public function generate() {
$result = [];
$remaining = $this->totalAmount;
$remainingNum = $this->num;
for ($i = 0; $i < $this->num; $i++) {
if ($remainingNum == 1) {
$amount = $remaining;
} else {
// 计算最大可分配金额
$maxAllocatable = $remaining - ($remainingNum - 1) * $this->minAmount;
$maxAmount = min($maxAllocatable,
(int)($remaining / $remainingNum * 2),
$this->maxAmount);
// 计算最小可分配金额
$minAmount = max($this->minAmount,
$remaining - ($remainingNum - 1) * $this->maxAmount);
if ($maxAmount <= $minAmount) {
$amount = $minAmount;
} else {
$amount = rand($minAmount, $maxAmount);
}
}
$result[] = $amount;
$remaining -= $amount;
$remainingNum--;
}
shuffle($result); // 打乱顺序
return $result;
}
}
手气红包(有运气成分)
<?php
/**
* 手气红包算法(有些人会拿到大的)
* @param int $totalAmount 总金额(分)
* @param int $num 红包个数
* @param float $luckFactor 运气因子(0.5-2)
* @return array
*/
function luckyRedPacket($totalAmount, $num, $luckFactor = 1.5) {
$result = [];
$remaining = $totalAmount;
for ($i = 0; $i < $num; $i++) {
if ($i == $num - 1) {
$result[] = $remaining;
break;
}
// 动态调整系数,让后面的红包更难拿大的
$factor = $luckFactor * (1 - $i / $num * 0.5);
$max = (int)($remaining / ($num - $i) * $factor);
// 确保每个红包至少1分
$min = max(1, $remaining - ($num - $i - 1));
$amount = rand($min, min($max, $remaining - ($num - $i - 1)));
$result[] = $amount;
$remaining -= $amount;
}
return $result;
}
完整示例和测试
<?php
// 测试代码
function testRedPacket() {
// 配置
$totalAmount = 10000; // 100元(以分为单位)
$num = 10; // 10个红包
echo "=== 测试各种红包算法 ===\n";
echo "总金额:{$totalAmount}分(" . ($totalAmount/100) . "元)\n";
echo "红包数:{$num}个\n\n";
// 测试二倍均值法
$result1 = averageRedPacket($totalAmount, $num);
echo "二倍均值法结果:\n";
printResult($result1);
// 测试高级算法
$redPacket = new RedPacket($totalAmount, $num, 1, 2000);
$result2 = $redPacket->generate();
echo "高级算法结果(限制最高20元):\n";
printResult($result2);
// 测试手气红包
$result3 = luckyRedPacket($totalAmount, $num, 1.8);
echo "手气红包结果(运气因子1.8):\n";
printResult($result3);
}
function printResult($result) {
$total = 0;
foreach ($result as $i => $amount) {
$yuan = $amount / 100;
echo "第" . ($i+1) . "个:{$yuan}元\n";
$total += $amount;
}
echo "总计:{$total}分(" . ($total/100) . "元)\n\n";
}
// 运行测试
testRedPacket();
完整的生产环境代码
<?php
class RedPacketService {
private $db; // 数据库连接
public function __construct($db) {
$this->db = $db;
}
/**
* 发红包
* @param int $userId 用户ID
* @param float $amount 金额(元)
* @param int $num 红包个数
* @param string $message 祝福语
* @return array
*/
public function sendRedPacket($userId, $amount, $num, $message = '恭喜发财') {
// 参数验证
if ($amount <= 0 || $num <= 0) {
throw new Exception("参数错误");
}
// 转换为分
$amountInCents = (int)round($amount * 100);
// 生成红包
$packetId = uniqid('rp_', true);
$amounts = $this->generateAmounts($amountInCents, $num);
// 保存到数据库(事务)
$this->db->beginTransaction();
try {
// 插入红包记录
$this->db->insert('red_packets', [
'id' => $packetId,
'user_id' => $userId,
'total_amount' => $amountInCents,
'remaining_amount' => $amountInCents,
'num' => $num,
'remaining_num' => $num,
'message' => $message,
'created_at' => date('Y-m-d H:i:s')
]);
// 插入红包详情
foreach ($amounts as $i => $amount) {
$this->db->insert('red_packet_items', [
'packet_id' => $packetId,
'amount' => $amount,
'order_no' => $i,
'status' => 0 // 未领取
]);
}
$this->db->commit();
return [
'success' => true,
'packet_id' => $packetId,
'message' => '红包发送成功'
];
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
/**
* 领取红包
*/
public function receiveRedPacket($packetId, $userId) {
// 开启事务
$this->db->beginTransaction();
try {
// 获取红包信息
$packet = $this->db->selectOne("SELECT * FROM red_packets WHERE id = ? FOR UPDATE", [$packetId]);
if (!$packet) {
throw new Exception("红包不存在");
}
if ($packet['remaining_num'] <= 0) {
throw new Exception("红包已被抢完");
}
// 检查是否已经领取过
$received = $this->db->selectOne("SELECT * FROM red_packet_records WHERE packet_id = ? AND user_id = ?",
[$packetId, $userId]);
if ($received) {
throw new Exception("您已经领过了");
}
// 获取一个未领取的红包
$item = $this->db->selectOne(
"SELECT * FROM red_packet_items WHERE packet_id = ? AND status = 0 ORDER BY RAND() LIMIT 1 FOR UPDATE",
[$packetId]
);
if (!$item) {
throw new Exception("红包内部错误");
}
// 更新红包状态
$this->db->update("UPDATE red_packets SET remaining_num = remaining_num - 1 WHERE id = ?", [$packetId]);
$this->db->update("UPDATE red_packet_items SET status = 1, receive_user_id = ?, receive_time = ? WHERE id = ?",
[$userId, date('Y-m-d H:i:s'), $item['id']]);
// 记录领取日志
$this->db->insert('red_packet_records', [
'packet_id' => $packetId,
'user_id' => $userId,
'amount' => $item['amount'],
'received_at' => date('Y-m-d H:i:s')
]);
// 增加用户余额
$this->db->update("UPDATE users SET balance = balance + ? WHERE id = ?",
[$item['amount'] / 100, $userId]);
$this->db->commit();
return [
'success' => true,
'amount' => $item['amount'] / 100,
'message' => '领取成功'
];
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
/**
* 生成红包金额
*/
private function generateAmounts($totalAmount, $num) {
$result = [];
$remaining = $totalAmount;
// 确保最小金额为1分
$minAmount = 1;
for ($i = 0; $i < $num; $i++) {
if ($i == $num - 1) {
$result[] = $remaining;
break;
}
// 二倍均值法
$max = (int)($remaining / ($num - $i) * 2);
$min = max($minAmount, $remaining - ($num - $i - 1));
$amount = rand($min, min($max, $remaining - ($num - $i - 1)));
$result[] = $amount;
$remaining -= $amount;
}
// 打乱顺序
shuffle($result);
return $result;
}
}
算法对比表
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 随机算法 | 简单快速 | 可能太均匀 | 小场景 |
| 二倍均值法 | 较均匀 | 可能过于平均 | 微信红包 |
| 高级配置算法 | 灵活性高 | 参数配置复杂 | 需要控制的场景 |
| 手气红包 | 有惊喜感 | 可能差异太大 | 促销活动 |
使用建议
- 金额单位:建议使用分(整数)避免浮点数精度问题
- 数据库事务:复杂的红包操作必须使用事务
- 并发处理:使用数据库锁或Redis解决并发抢红包
- 日志记录:所有操作都要记录日志便于审计
- 性能优化:可以使用Redis预生成红包队列
这个设计已经包含了算法、并发处理、数据库操作等生产环境需要的要素。