记一次编写PHP抽奖功能的实现

作者: 乘风御上者 分类: MySQL,PHP 发布时间: 2020-07-18 16:05

最近项目需要一个抽奖活动,因为以前没有搞过,所以对抽奖功能使用的核心算法不知道哪种更保险。通过几次测试验证觉得还不错,在此记录一下。

抽奖功能前提的数据表:

# 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;
    }

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

发表回复