记一次编写PHP抽奖功能的实现
最近项目需要一个抽奖活动,因为以前没有搞过,所以对抽奖功能使用的核心算法不知道哪种更保险。通过几次测试验证觉得还不错,在此记录一下。
抽奖功能前提的数据表:
# type字段必须设置一个空奖类型(此处设置type=0空奖)
# val字段是项目关联到其他表的奖品,用不用看个人项目需要
CREATE TABLE `prize` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '奖品名称',
`image` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '奖品图片',
`rate` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '中奖概率(万分之几)',
`number` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '库存',
`sort` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序',
`type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '类型:0=空,1=积分,2=充值卡,3=优惠券',
`val` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '对应类型值(type=1积分值,type=2充值卡ID,type=3优惠券ID)',
`create_time` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
)
然后在模型中编写相关业务逻辑即可:
/**
* 抽奖对应的模型类
* # ThinkPHP框架
*/
class PrizeModel
{
// 转盘奖品数量
const MAX_NUM = 8;
// 概率总值
const RATE_VAL = 10000;
// 奖品缓存KEY
const PRIZE_LIST_KEY = '_PRIZE_LIST_';
// 空奖缓存KEY
const PRIZE_NULL_KEY = '_PRIZE_NULL_ID_';
/**
* 抽奖算法
* @return mixed 奖品信息
* @throws Exception 该方法放入try中执行
*/
public function toPrize()
{
// 获取所有奖品
$prizeList = Cache::get(self::PRIZE_LIST_KEY);
if (!$prizeList) {
$prizeList = $this->where('id', '>', 0)->order('rate asc')->column('*', 'id');
Cache::set(self::PRIZE_LIST_KEY, $prizeList);
}
// 默认一个空奖ID
$nullId = Cache::get(self::PRIZE_NULL_KEY);
if (!$nullId) {
// 找一个空奖
foreach ($prizeList as $id => $prize) {
if ($prize['type'] < 1) {
$nullId = $id;
break;
}
}
if (!$nullId) {
throw new Exception('奖品已被抽空,正在补货中...');
}
Cache::set(self::PRIZE_NULL_KEY, $nullId);
}
// 有库存的商品参与抽奖
$usableList = [];
foreach ($prizeList as $id => $prize) {
if ($prize['number'] > 0) {
$usableList[$id] = $prize['rate'];
}
};
// 计算可用奖品剩余中奖概率
$sumRate = array_sum($usableList);
// 总中奖概率
$totalRate = self::RATE_VAL;
// 不满100%,将剩余概率加入空奖中
if ($sumRate < $totalRate) {
$surplusRate = $totalRate - $sumRate;
$usableList[$nullId] = isset($usableList[$nullId]) ? ($usableList[$nullId] + $surplusRate) : $surplusRate;
}
// 中奖奖品ID,开始默认为空奖
$winId = $nullId;
//概率数组总精度
foreach ($usableList as $goodsId => $goodsRate) {
$randRate = mt_rand(1, $totalRate);
if ($randRate <= $goodsRate) {
$winId = $goodsId;
break;
} else {
$totalRate -= $goodsRate;
}
}
// 中奖商品
$winGoods = $prizeList[$winId];
// 若中奖奖品为非空奖,则减少库存。(空奖无需减少库存)
if ($winGoods['type'] > 0) {
$num = ($winGoods['number'] > 0) ? $winGoods['number'] - 1 : 0;
$prizeList[$winId]['number'] = $num;
Cache::set(self::PRIZE_LIST_KEY, $prizeList);
// 数据库减少库存
$this->where('id', $winId)->update(['number' => $num]);
}
return $winGoods;
}