You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yanzong/app/api/service/bargain/Amount.php

165 lines
4.8 KiB

<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
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));
}
}