Amount.php 4.1 KB

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