AmountValidator.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. namespace App\Module\Transfer\Validators;
  3. use UCore\Validation\ValidatorCore;
  4. /**
  5. * 金额验证器
  6. */
  7. class AmountValidator extends ValidatorCore
  8. {
  9. private string $amount;
  10. private int $maxDecimalPlaces;
  11. private string $minAmount;
  12. private string $maxAmount;
  13. public function __construct(
  14. string $amount,
  15. int $maxDecimalPlaces = 10,
  16. string $minAmount = '0.0000000001',
  17. string $maxAmount = '999999999.9999999999'
  18. ) {
  19. $this->amount = $amount;
  20. $this->maxDecimalPlaces = $maxDecimalPlaces;
  21. $this->minAmount = $minAmount;
  22. $this->maxAmount = $maxAmount;
  23. }
  24. /**
  25. * 执行验证
  26. */
  27. public function validate(): bool
  28. {
  29. // 验证基本格式
  30. if (!$this->validateFormat()) {
  31. return false;
  32. }
  33. // 验证数值范围
  34. if (!$this->validateRange()) {
  35. return false;
  36. }
  37. // 验证小数位数
  38. if (!$this->validateDecimalPlaces()) {
  39. return false;
  40. }
  41. // 验证特殊值
  42. if (!$this->validateSpecialValues()) {
  43. return false;
  44. }
  45. return true;
  46. }
  47. /**
  48. * 验证基本格式
  49. */
  50. private function validateFormat(): bool
  51. {
  52. // 检查是否为空
  53. if (empty($this->amount)) {
  54. $this->addError('金额不能为空');
  55. return false;
  56. }
  57. // 检查是否为数字格式
  58. if (!is_numeric($this->amount)) {
  59. $this->addError('金额格式无效');
  60. return false;
  61. }
  62. // 检查是否包含非法字符
  63. if (!preg_match('/^-?\d+(\.\d+)?$/', $this->amount)) {
  64. $this->addError('金额只能包含数字和小数点');
  65. return false;
  66. }
  67. return true;
  68. }
  69. /**
  70. * 验证数值范围
  71. */
  72. private function validateRange(): bool
  73. {
  74. // 检查是否为负数
  75. if (bccomp($this->amount, '0', $this->maxDecimalPlaces) < 0) {
  76. $this->addError('金额不能为负数');
  77. return false;
  78. }
  79. // 检查最小值
  80. if (bccomp($this->amount, $this->minAmount, $this->maxDecimalPlaces) < 0) {
  81. $this->addError("金额不能小于 {$this->minAmount}");
  82. return false;
  83. }
  84. // 检查最大值
  85. if (bccomp($this->amount, $this->maxAmount, $this->maxDecimalPlaces) > 0) {
  86. $this->addError("金额不能大于 {$this->maxAmount}");
  87. return false;
  88. }
  89. return true;
  90. }
  91. /**
  92. * 验证小数位数
  93. */
  94. private function validateDecimalPlaces(): bool
  95. {
  96. // 检查小数位数
  97. $parts = explode('.', $this->amount);
  98. if (count($parts) > 2) {
  99. $this->addError('金额格式无效');
  100. return false;
  101. }
  102. if (count($parts) === 2) {
  103. $decimalPart = $parts[1];
  104. if (strlen($decimalPart) > $this->maxDecimalPlaces) {
  105. $this->addError("金额小数位数不能超过 {$this->maxDecimalPlaces} 位");
  106. return false;
  107. }
  108. // 检查小数部分是否全为0(如果是,建议使用整数格式)
  109. if (preg_match('/^0+$/', $decimalPart)) {
  110. // 这不是错误,但可以优化
  111. }
  112. }
  113. return true;
  114. }
  115. /**
  116. * 验证特殊值
  117. */
  118. private function validateSpecialValues(): bool
  119. {
  120. // 检查是否为0
  121. if (bccomp($this->amount, '0', $this->maxDecimalPlaces) === 0) {
  122. $this->addError('金额不能为0');
  123. return false;
  124. }
  125. // 检查是否为无穷大或NaN
  126. if (!is_finite((float) $this->amount)) {
  127. $this->addError('金额值无效');
  128. return false;
  129. }
  130. // 检查科学计数法
  131. if (strpos(strtolower($this->amount), 'e') !== false) {
  132. $this->addError('金额不支持科学计数法格式');
  133. return false;
  134. }
  135. return true;
  136. }
  137. /**
  138. * 格式化金额
  139. */
  140. public function format(): string
  141. {
  142. // 移除前导零和尾随零
  143. $formatted = rtrim(rtrim($this->amount, '0'), '.');
  144. // 如果结果为空,返回0
  145. if (empty($formatted) || $formatted === '.') {
  146. return '0';
  147. }
  148. return $formatted;
  149. }
  150. /**
  151. * 转换为标准格式
  152. */
  153. public function toStandardFormat(): string
  154. {
  155. return number_format((float) $this->amount, $this->maxDecimalPlaces, '.', '');
  156. }
  157. /**
  158. * 验证金额是否足够支付手续费
  159. */
  160. public function validateWithFee(string $feeAmount): bool
  161. {
  162. if (bccomp($this->amount, $feeAmount, $this->maxDecimalPlaces) <= 0) {
  163. $this->addError("金额必须大于手续费 {$feeAmount}");
  164. return false;
  165. }
  166. return true;
  167. }
  168. /**
  169. * 静态验证方法
  170. */
  171. public static function isValid(string $amount, int $maxDecimalPlaces = 10): bool
  172. {
  173. $validator = new self($amount, $maxDecimalPlaces);
  174. return $validator->validate();
  175. }
  176. /**
  177. * 比较两个金额
  178. */
  179. public static function compare(string $amount1, string $amount2, int $precision = 10): int
  180. {
  181. return bccomp($amount1, $amount2, $precision);
  182. }
  183. /**
  184. * 金额加法
  185. */
  186. public static function add(string $amount1, string $amount2, int $precision = 10): string
  187. {
  188. return bcadd($amount1, $amount2, $precision);
  189. }
  190. /**
  191. * 金额减法
  192. */
  193. public static function subtract(string $amount1, string $amount2, int $precision = 10): string
  194. {
  195. return bcsub($amount1, $amount2, $precision);
  196. }
  197. /**
  198. * 金额乘法
  199. */
  200. public static function multiply(string $amount, string $multiplier, int $precision = 10): string
  201. {
  202. return bcmul($amount, $multiplier, $precision);
  203. }
  204. /**
  205. * 金额除法
  206. */
  207. public static function divide(string $amount, string $divisor, int $precision = 10): string
  208. {
  209. if (bccomp($divisor, '0', $precision) === 0) {
  210. throw new \InvalidArgumentException('除数不能为0');
  211. }
  212. return bcdiv($amount, $divisor, $precision);
  213. }
  214. }