// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\service\bargain; use app\common\service\BaseService; /** * 砍价金额服务类 * Class Amount * @package app\api\service\bargain */ class Amount extends BaseService { /** * 砍价金额 * * @var float */ protected float $amount; /** * 砍价人数 * * @var int */ protected int $num; /** * 砍价的最小金额 * * @var float */ protected float $couponMin; /** * 砍价分配结果 * * @var array */ protected array $items = []; /** * 初始化 * @param float $amount 砍价金额(单位:元)最多保留2位小数 * @param int $num 砍价个数 * @param float $couponMin 每个至少领取的砍价金额 */ public function __construct(float $amount, int $num = 1, float $couponMin = 0.01) { parent::__construct(); $this->amount = $amount; $this->num = $num; $this->couponMin = $couponMin; } /** * 处理返回 * @return array * @throws \Exception */ public function handle() { // A. 验证 if ($this->amount < $validAmount = $this->couponMin * $this->num) { throw new \Exception('砍价总金额必须≥' . $validAmount . '元'); } // B. 分配砍价 $this->apportion(); return [ 'items' => $this->items, ]; } /** * 分配砍价 */ protected function apportion() { $num = $this->num; // 剩余可分配的砍价个数 $amount = $this->amount; //剩余可领取的砍价金额 while ($num >= 1) { // 剩余一个的时候,直接取剩余砍价 if ($num == 1) { $coupon_amount = $this->decimalNumber($amount); } else { $avg_amount = $this->decimalNumber($amount / $num); // 剩余的砍价的平均金额 $coupon_amount = $this->decimalNumber( $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->couponMin) { return $this->couponMin; } // 浮动计算 $coupon_amount = $this->decimalNumber($avg_amount * (1 + $this->apportionRandRatio())); // 如果低于最低金额或超过可领取的最大金额,则重新获取 if ($coupon_amount < $this->couponMin || $coupon_amount > $this->calcCouponAmountMax($amount, $num) ) { return $this->calcCouponAmount($avg_amount, $amount, $num); } return (float)$coupon_amount; } /** * 计算分配的砍价金额-可领取的最大金额 * @param $amount * @param $num * @return float|int */ protected function calcCouponAmountMax($amount, $num) { return $this->couponMin + $amount - $num * $this->couponMin; } /** * 砍价金额浮动比例 */ protected function apportionRandRatio() { // 60%机率获取剩余平均值的大幅度砍价(可能正数、可能负数) if (rand(1, 100) <= 60) { return rand(-70, 70) / 100; // 上下幅度70% } return rand(-30, 30) / 100; // 其他情况,上下浮动30%; } /** * 格式化金额,保留2位 * @param float $amount * @return string */ protected function decimalNumber(float $amount): string { return sprintf('%01.2f', round($amount, 2)); } }