我们先来分析下规律。

设定总金额为10元,有N个人随机领取:

N=1 第一个

则红包金额=X元;

N=2 第二个

为保证第二个红包可以正常发出,第一个红包金额=0.01至9.99之间的某个随机数。

第二个红包=10-第一个红包金额;

N=3 第三个

红包1=0.01至9.99之间的某个随机数

红包2=0.01至(10-红包1-0.01)的某个随机数

红包3=10-红包1-红包2

……

于是我们得到一个规律,在分配当前红包金额时,先预留剩余红白所需最少金额,然后在0.01至总金额-预留金额间取随机数,得到的随机数就是当前红包分配的金额。

实际应用中,程序先将红包金额分配好,即发红包时,红包个数以及每个红包的金额都分配好了,那么用户来抢红包时,我们随机给用户返回一个红包即可。

红包分配代码:

$total=20;//红包总金额
$num=10;// 分成10个红包,支持10人随机领取
$min=0.01;//每个人最少能收到0.01元
for ($i=1;$i<$num;$i++)
{
$safe_total=($total-($num-$i)*$min)/($num-$i);//随机安全上限
$money=mt_rand($min*100,$safe_total*100)/100;
$total=$total-$money;
echo '第'.$i.'个红包:'.$money.' 元,余额:'.$total.' 元
';
}
echo '第'.$num.'个红包:'.$total.' 元,余额:0 元';

运行以上代码,会输出以下结果:

第1个红包,金额2.08元,余额17.92元

第2个红包,金额1.81元,余额16.11元

第3个红包,金额0.15元,余额15.96元

第4个红包,金额1.61元,余额14.35元

第5个红包,金额1.11元,余额13.24元

第6个红包,金额1.51元,余额11.73元

第7个红包,金额1.21元,余额10.52元

第8个红包,金额2.58元,余额7.94元

第9个红包,金额5.4元,余额2.54元

第10个红包,金额2.54元,余额0元

 

 

/**
     * 抢红包.
     * 
     * @param int $userId  用户ID
     * @param int $shareId 大红包ID
     *
     * @return array 包含状态码,状态,消息
     */
    public function grabHongBao(
        string $mobile, 
        int $userId, 
        int $shareId, 
        int $orderId, 
        int $orderUserId, 
        $noRegUserId = 0,
        $openId = '',
        $avatar = '',
        $nickname = '')
    {
        // 大红包信息
        $hbsInfo = HBShareModel::multiWhere([
            'id'       => $shareId,
            'order_id' => $orderId,
            'user_id'  => $orderUserId,
        ])->first();

        if ( ! $hbsInfo) {
            return [
                'code' => -1,
                'status' => 'failure',
                'message' => '出错了',
            ];
        }

        // 检查是否领取过
        $hbobj = new HongBao();
        if ($hbobj->checkExistsByOrder($userId, $noRegUserId, $hbsInfo->order_id)) {
            return [
                'code' => -5,
                'status' => 'failure',
                'message' => '已抢过',
            ];
        }

        // 过期时间
        if ($hbsInfo->expire < CURRENT_TIME) {
            return [
                'code' => -2,
                'status' => 'failure',
                'message' => '来晚了',
            ];
        }

        // 还剩下多少个红包
        $shareNum = intval($hbsInfo->total) - intval($hbsInfo->shared);
        if ($shareNum == 0) {
            return [
                'code' => -3,
                'status' => 'failure',
                'message' => '已抢光',
            ];
        }

        // 格式化
        $hbsInfo->surplus = floatval($hbsInfo->surplus);

        // 已经抢光
        if ($hbsInfo->surplus <= 0.00) {
            return [
                'code' => -3,
                'status' => 'failure',
                'message' => '已抢光',
            ];
        }

        // 红包设置
        $setting = Setting::get('hb_hongbao');

        // 红包最大数额
        $max = abs(floatval($setting[$hbsInfo->type]['amount_max']));
        $min = abs(floatval($setting[$hbsInfo->type]['amount_min']));

        if ($hbsInfo->surplus <= $min) {
            $amount = $hbsInfo->surplus;
        }
        // 最后一个红包
        elseif ($shareNum == 1) {
            $amount = min($hbsInfo->surplus, $max);
        }
        // 最小额
        elseif ($shareNum * $min == $hbsInfo->surplus) {
            $amount = $min;
        }
        // 非最后一个红包
        else {
            $maxAmount = $hbsInfo->surplus - ($shareNum - 1) * $min;
            $maxAmount = min($maxAmount, $max);

            $amount = rand($maxAmount * 100, $min * 100) / 100;
        }

        $amount = number_format($amount, 2, '.', '');
        $amount = floatval($amount);

        if ($amount <= 0.00) {
            return [
                'code' => -3,
                'status' => 'failure',
                'message' => '已抢光',
            ];
        }

        if ($amount > 0.10) {
            $amount = number_format($amount, 1, '.', '');
            $amount = number_format($amount, 2, '.', '');
            $amount = floatval($amount);
        }

        // 满多少可用
        $threshold = $setting[$hbsInfo->type]['threshold'] ? ceil($amount / $setting[$hbsInfo->type]['threshold']) : 0;
        // 过期时间
        $expire = strtotime('today') + $setting[$hbsInfo->type]['expire'] * 86400;

        $message = '';
        $avg = ($max - $min) / 10;
        for ($i = 0; $i <= 9; $i++) {
            $formatMin = floatval(number_format(($max - $avg), 2, '.', ''));

            if ($amount > $formatMin and $amount <= $max) {
                $message = HBConst::$shareMessage[$i];
                break;
            }

            $max -= $avg;
            $max = floatval(number_format($max, 2, '.', ''));
        }

        if ( ! $message) {
            $message = end(HBConst::$shareMessage);
        }

        // 生成红包
        $result = $hbobj->create([
            'user_id'   => $userId,
            'amount'    => $amount,
            'type'      => $hbsInfo->type,
            'threshold' => $threshold,
            'expire'    => $expire,
            'from'      => 'order',
            'link_id'   => $hbsInfo->order_id,
            'share_id'  => $shareId,
            'noreg_id'  => $noRegUserId,
            'open_id'   => $openId,
            'avatar'    => $avatar,
            'nickname'  => $nickname,
            'message'   => $message,
        ]);

        if ( ! $result) {
            return [
                'code'    => -4,
                'status'  => 'failure',
                'message' => '出错了',
            ];
        }

        // 保存到大红包
        $hbsInfo->surplus -= $amount;
        $hbsInfo->shared ++;
        $hbsInfo->save();

        if ($userId) {
            MessageModel::insert([
                'to_user_id'  => $userId,
                'title'       => '您收到了一个新红包',
                'content'     => '您收到了一个'. $amount.'元的红包',
                'create_time' => CURRENT_TIME,
                'type'        => 'hongbao'
            ]);
        }

        if ($shareNum == 1) {
            $this->_doubleEggAward($hbsInfo, $hbobj);
        }
        unset($hbobj);

        return [
            'code'    => 1,
            'status'  => 'success',
            'message' => '',
            'amount'  => $amount,
            'type'    => $hbsInfo->type,
        ];
    }