Amount.php 3.7 KB

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