利用redis队列抢红包

发布时间 2023-10-11 08:44:27作者: 叫我亚庆
   /**
     * 发放红包
     * @param $money //金额
     * @param $num //数量
    * @param $packet //群组id确保key的唯一性
    * @param $user_id //发放人
     * @return array
     */
    public function deliver($num,$packet,$money,$user_id){

        //业务逻辑
        $user = User::get($user_id);
        if($user['money'] < $money){
            $this->error('金额不足');
        }

        //获取金额
        $money_arr = $this->redAlgorithm($money,$num);
        if(!$money_arr){
            $this->error('单个红包金额不可低于0.01');
        }

        //发放红包  入库操作


        $key = 'packet'.$packet;
        $this->redis->lPush($key,0);
        foreach ($money_arr as $k=>$v){
            $this->redis->lPush($key,$k+1);
            //记录num个红包的金额

        }

        //扣除金额



        //加入流水



        // 延时队列,24小时没有抢则退还 86400
       //  todo 未测时效性(redis不行可切mysql)
      //  Queue::later('86390','app\job\Robexpire',['id'=>$id],'Robexpire');

        $this->success('发放成功')

    }    

  

    /**
     * 获取红包金额
     * @param $money  //金额
     * @param $count  //数量
     * @return array
     */
    function redAlgorithm($money, $count)
    {
        // 参数校验
        if ($count * 0.01 > $money) {
            $this->error('单个红包金额不可低于0.01');
        }
        // 存放随机红包
        $redpack = [];
        // 未分配的金额
        $surplus = $money;
        for ($i = 1; $i <= $count; $i++) {
            // 安全金额
            $safeMoney = $surplus - ($count - $i) * 0.01;
            // 平均金额
            $avg = $i == $count ? $safeMoney : bcdiv($safeMoney, ($count - $i), 2);
            // 随机红包
            $rand = $avg > 0.01 ? mt_rand(1, $avg * 100) / 100 : 0.01;
            // 剩余红包
            $surplus = bcsub($surplus, $rand, 2);
            $redpack[] = $rand;
        }
        // 平分剩余红包
        $avg = bcdiv($surplus, $count, 2);
        for ($n = 0; $n < count($redpack); $n++) {
            $redpack[$n] = bcadd($redpack[$n], $avg, 2);
            $surplus = bcsub($surplus, $avg, 2);
        }
        // 如果还有红包没有分配完时继续分配
        if ($surplus > 0) {
            // 随机抽取分配好的红包,将剩余金额分配进去
            $keys = array_rand($redpack, $surplus * 100);
            // array_rand 第二个参数为 1 时返回的是下标而不是数组
            $keys = is_array($keys) ? $keys : [$keys];
            foreach ($keys as $key) {
                $redpack[$key] = bcadd($redpack[$key], 0.01, 2);
                $surplus = bcsub($surplus, 0.01, 2);
            }
        }
        // 红包分配结果
        return $redpack;
    }

  

    /**
     * 抢红包
     * @param $user_id //抢红包id
     * @param $packet //群组id,获取key
     * @param $boid //红包id,用来获取已分好金额
     * @return array
     */   
    public function rob($user_id,$packet,$boid)
    {


        $key = 'packet'.$packet;

        //已抢购用户队列
        $userBuyKey = 'user_buy'.$packet.$user_id;

        $userBuyStatus = $this->redis->sismember($userBuyKey,$user_id);

        if($userBuyStatus){
            //该用户已抢过
            $this->error('您已抢过');
        }

        //$count为第几个
        $count = $this->redis->lpop($key);
        if(!$count){
            //已抢完
            $this->error('红包已抢完');
        }

        //加入已抢
        $this->redis->sadd($userBuyKey,$user_id);

        //抢成功, 操作数据库


 
            //增加金额



            //加入流水
           
           $this->success('抢成功');
        
    }