SignHelper.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <?php
  2. namespace common\components\delivery\helpers;
  3. class SignHelper
  4. {
  5. /**
  6. * 通用签名算法入口
  7. * @param array $params 待签名参数
  8. * @param string $secret 密钥
  9. * @param string $method 签名算法:md5 / hmac_sha256
  10. * @param bool $sort 是否需要按 key 排序
  11. * @param string $platform 默认通用(common),其它请用平台名
  12. */
  13. public static function makeSign(array $params, string $secret, string $method = 'md5', bool $sort = true, string $platform = 'common')
  14. {
  15. if ($sort) {
  16. ksort($params);
  17. }
  18. // 拼接 query 字符串(过滤空值)
  19. $str = '';
  20. foreach ($params as $k => $v) {
  21. //if ($v === '' || $v === null) continue;
  22. if (is_array($v)) {
  23. // 关键:在 JSON 编码前对数组进行递归排序
  24. //$v = self::sortArrayRecursive($v);
  25. $v = json_encode($v, JSON_UNESCAPED_UNICODE);
  26. }
  27. if ($platform == 'shansong') {
  28. $str .= "{$k}{$v}";
  29. //$str .= "{$k}={$v}&";
  30. } else {
  31. $str .= "{$k}={$v}&";
  32. }
  33. }
  34. $str = rtrim($str, '&');
  35. switch (strtolower($method)) {
  36. case 'hmac_sha256':
  37. $sign = hash_hmac('sha256', $str, $secret);
  38. break;
  39. case 'sha256':
  40. if ($platform == 'fengniao') {
  41. $sign = $secret . $str;
  42. } else {
  43. $sign = $str . $secret;
  44. }
  45. $sign = hash('sha256', $sign);
  46. break;
  47. case 'md5':
  48. default:
  49. if ($platform == 'shansong') {
  50. $sign = $secret . $str;
  51. } else {
  52. $sign = $str . $secret;
  53. }
  54. $sign = md5($sign);
  55. break;
  56. }
  57. return strtoupper($sign);
  58. }
  59. /**
  60. * 验证签名(用于第三方回调)
  61. */
  62. public static function verify(array $params, string $secret, string $signKey = 'sign', string $method = 'md5')
  63. {
  64. if (!isset($params[$signKey])) return false;
  65. $sign = $params[$signKey];
  66. unset($params[$signKey]);
  67. $expect = self::makeSign($params, $secret, $method);
  68. return strtoupper($sign) === strtoupper($expect);
  69. }
  70. /**
  71. * 递归过滤数组中的空值(null、空字符串、空数组)
  72. * 确保签名时不包含空值参数
  73. *
  74. * @param array $data 需要过滤的数组
  75. * @return array 过滤后的数组
  76. */
  77. private static function filterEmptyValues($data)
  78. {
  79. $filtered = [];
  80. foreach ($data as $key => $value) {
  81. // 过滤掉 null、空字符串、空数组
  82. if ($value === null || $value === '' || (is_array($value) && empty($value))) {
  83. continue;
  84. }
  85. // 如果是数组,递归处理
  86. if (is_array($value)) {
  87. $value = self::filterEmptyValues($value);
  88. // 递归过滤后如果变成空数组,也要跳过
  89. if (empty($value)) {
  90. continue;
  91. }
  92. }
  93. $filtered[$key] = $value;
  94. }
  95. return $filtered;
  96. }
  97. /**
  98. * 递归对数组进行排序(包括嵌套数组)
  99. * 确保 JSON 编码时键的顺序一致
  100. *
  101. * @param array $arr 需要排序的数组
  102. * @return array 排序后的数组
  103. */
  104. private static function sortArrayRecursive($arr)
  105. {
  106. if (!is_array($arr)) {
  107. return $arr;
  108. }
  109. // 对当前数组进行排序
  110. ksort($arr);
  111. // 递归处理嵌套数组
  112. foreach ($arr as $key => $value) {
  113. if (is_array($value)) {
  114. $arr[$key] = self::sortArrayRecursive($value);
  115. }
  116. }
  117. return $arr;
  118. }
  119. }