SignHelper.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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 (is_array($v)) {
  22. // 关键:在 JSON 编码前对数组进行递归排序
  23. //$v = self::sortArrayRecursive($v);
  24. $v = json_encode($v, JSON_UNESCAPED_UNICODE);
  25. }
  26. if ($platform == 'shansong') {
  27. $str .= "{$k}{$v}";
  28. } else {
  29. $str .= "{$k}={$v}&";
  30. }
  31. }
  32. $str = rtrim($str, '&');
  33. switch (strtolower($method)) {
  34. case 'hmac_sha256':
  35. $sign = hash_hmac('sha256', $str, $secret);
  36. break;
  37. case 'sha256':
  38. if ($platform == 'fengniao') {
  39. $sign = $secret . $str;
  40. } else {
  41. $sign = $str . $secret;
  42. }
  43. $sign = hash('sha256', $sign);
  44. break;
  45. case 'md5':
  46. default:
  47. if ($platform == 'shansong') {
  48. $sign = $secret . $str;
  49. } else {
  50. $sign = $str . $secret;
  51. }
  52. $sign = md5($sign);
  53. break;
  54. }
  55. return strtoupper($sign);
  56. }
  57. /**
  58. * 验证签名(用于第三方回调)
  59. */
  60. public static function verify(array $params, string $secret, string $signKey = 'sign', string $method = 'md5')
  61. {
  62. if (!isset($params[$signKey])) return false;
  63. $sign = $params[$signKey];
  64. unset($params[$signKey]);
  65. $expect = self::makeSign($params, $secret, $method);
  66. return strtoupper($sign) === strtoupper($expect);
  67. }
  68. /**
  69. * 递归过滤数组中的空值(null、空字符串、空数组)
  70. * 确保签名时不包含空值参数
  71. *
  72. * @param array $data 需要过滤的数组
  73. * @return array 过滤后的数组
  74. */
  75. private static function filterEmptyValues($data)
  76. {
  77. $filtered = [];
  78. foreach ($data as $key => $value) {
  79. // 过滤掉 null、空字符串、空数组
  80. if ($value === null || $value === '' || (is_array($value) && empty($value))) {
  81. continue;
  82. }
  83. // 如果是数组,递归处理
  84. if (is_array($value)) {
  85. $value = self::filterEmptyValues($value);
  86. // 递归过滤后如果变成空数组,也要跳过
  87. if (empty($value)) {
  88. continue;
  89. }
  90. }
  91. $filtered[$key] = $value;
  92. }
  93. return $filtered;
  94. }
  95. /**
  96. * 递归对数组进行排序(包括嵌套数组)
  97. * 确保 JSON 编码时键的顺序一致
  98. *
  99. * @param array $arr 需要排序的数组
  100. * @return array 排序后的数组
  101. */
  102. private static function sortArrayRecursive($arr)
  103. {
  104. if (!is_array($arr)) {
  105. return $arr;
  106. }
  107. // 对当前数组进行排序
  108. ksort($arr);
  109. // 递归处理嵌套数组
  110. foreach ($arr as $key => $value) {
  111. if (is_array($value)) {
  112. $arr[$key] = self::sortArrayRecursive($value);
  113. }
  114. }
  115. return $arr;
  116. }
  117. }