111
liyaozhi
2025-11-09 c13b8914228e6a404bd60ee36bf2479383da8f23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?php
 
namespace app\api\service\bargain;
 
class Amount
{
    /**
     * 砍价金额
     */
    protected $amount;
 
    /**
     * 砍价人数
     */
    protected $num;
 
    /**
     * 砍价的最小金额
     */
    protected $coupon_min;
 
    /**
     * 砍价分配结果
     */
    protected $items = [];
 
    /**
     * 初始化
     * @param float $amount 砍价金额(单位:元)最多保留2位小数
     * @param int $num 砍价个数
     * @param float $coupon_min 每个至少领取的砍价金额
     */
    public function __construct($amount, $num = 1, $coupon_min = 0.01)
    {
        $this->amount = $amount;
        $this->num = $num;
        $this->coupon_min = $coupon_min;
    }
 
    /**
     * 处理返回
     */
    public function handle()
    {
        // 验证
        if ($this->amount < $validAmount = $this->coupon_min * $this->num) {
            throw new \Exception('砍价总金额必须≥' . $validAmount . '元');
        }
        // 分配砍价
        $this->apportion();
        return [
            'items' => $this->items,
        ];
    }
 
    /**
     * 分配砍价
     */
    protected function apportion()
    {
        $num = $this->num;  // 剩余可分配的砍价个数
        $amount = $this->amount;  //剩余可领取的砍价金额
        while ($num >= 1) {
            // 剩余一个的时候,直接取剩余砍价
            if ($num == 1) {
                $coupon_amount = $this->decimal_number($amount);
            } else {
                $avg_amount = $this->decimal_number($amount / $num);  // 剩余的砍价的平均金额
                $coupon_amount = $this->decimal_number(
                    $this->calcCouponAmount($avg_amount, $amount, $num)
                );
            }
            $this->items[] = $coupon_amount; // 追加分配
            $amount -= $coupon_amount;
            --$num;
        }
        shuffle($this->items);  // 随机打乱
    }
 
    /**
     * 计算分配的砍价金额
     * @param float $avg_amount 每次计算的平均金额
     * @param float $amount 剩余可领取金额
     * @param int $num 剩余可领取的砍价个数
     *
     * @return float
     */
    protected function calcCouponAmount($avg_amount, $amount, $num)
    {
        // 如果平均金额小于等于最低金额,则直接返回最低金额
        if ($avg_amount <= $this->coupon_min) {
            return $this->coupon_min;
        }
        // 浮动计算
        $coupon_amount = $this->decimal_number($avg_amount * (1 + $this->apportionRandRatio()));
        // 如果低于最低金额或超过可领取的最大金额,则重新获取
        if ($coupon_amount < $this->coupon_min
            || $coupon_amount > $this->calcCouponAmountMax($amount, $num)
        ) {
            return $this->calcCouponAmount($avg_amount, $amount, $num);
        }
        return $coupon_amount;
    }
 
    /**
     * 计算分配的砍价金额-可领取的最大金额
     */
    protected function calcCouponAmountMax($amount, $num)
    {
        return $this->coupon_min + $amount - $num * $this->coupon_min;
    }
 
    /**
     * 砍价金额浮动比例
     */
    protected function apportionRandRatio()
    {
        // 60%机率获取剩余平均值的大幅度砍价(可能正数、可能负数)
        if (rand(1, 100) <= 60) {
            return rand(-70, 70) / 100; // 上下幅度70%
        }
        return rand(-30, 30) / 100; // 其他情况,上下浮动30%;
    }
 
    /**
     * 格式化金额,保留2位
     */
    protected function decimal_number($amount)
    {
        return sprintf('%01.2f', round($amount, 2));
    }
}