Amount.php 3.9 KB

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