Amount.php 3.6 KB

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