BaiduGateway.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. /*
  3. * This file is part of the overtrue/easy-sms.
  4. *
  5. * (c) overtrue <i@overtrue.me>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Overtrue\EasySms\Gateways;
  11. use Overtrue\EasySms\Contracts\MessageInterface;
  12. use Overtrue\EasySms\Contracts\PhoneNumberInterface;
  13. use Overtrue\EasySms\Exceptions\GatewayErrorException;
  14. use Overtrue\EasySms\Support\Config;
  15. use Overtrue\EasySms\Traits\HasHttpRequest;
  16. /**
  17. * Class BaiduGateway.
  18. *
  19. * @see https://cloud.baidu.com/doc/SMS/API.html
  20. */
  21. class BaiduGateway extends Gateway
  22. {
  23. use HasHttpRequest;
  24. const ENDPOINT_HOST = 'sms.bj.baidubce.com';
  25. const ENDPOINT_URI = '/bce/v2/message';
  26. const BCE_AUTH_VERSION = 'bce-auth-v1';
  27. const DEFAULT_EXPIRATION_IN_SECONDS = 1800; //签名有效期默认1800秒
  28. const SUCCESS_CODE = 1000;
  29. /**
  30. * Send message.
  31. *
  32. * @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
  33. * @param \Overtrue\EasySms\Contracts\MessageInterface $message
  34. * @param \Overtrue\EasySms\Support\Config $config
  35. *
  36. * @return array
  37. *
  38. * @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
  39. */
  40. public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
  41. {
  42. $params = [
  43. 'invokeId' => $config->get('invoke_id'),
  44. 'phoneNumber' => $to->getNumber(),
  45. 'templateCode' => $message->getTemplate($this),
  46. 'contentVar' => $message->getData($this),
  47. ];
  48. $datetime = gmdate('Y-m-d\TH:i:s\Z');
  49. $headers = [
  50. 'host' => self::ENDPOINT_HOST,
  51. 'content-type' => 'application/json',
  52. 'x-bce-date' => $datetime,
  53. 'x-bce-content-sha256' => hash('sha256', json_encode($params)),
  54. ];
  55. //获得需要签名的数据
  56. $signHeaders = $this->getHeadersToSign($headers, ['host', 'x-bce-content-sha256']);
  57. $headers['Authorization'] = $this->generateSign($signHeaders, $datetime, $config);
  58. $result = $this->request('post', self::buildEndpoint($config), ['headers' => $headers, 'json' => $params]);
  59. if (self::SUCCESS_CODE != $result['code']) {
  60. throw new GatewayErrorException($result['message'], $result['code'], $result);
  61. }
  62. return $result;
  63. }
  64. /**
  65. * Build endpoint url.
  66. *
  67. * @param \Overtrue\EasySms\Support\Config $config
  68. *
  69. * @return string
  70. */
  71. protected function buildEndpoint(Config $config)
  72. {
  73. return 'http://'.$config->get('domain', self::ENDPOINT_HOST).self::ENDPOINT_URI;
  74. }
  75. /**
  76. * Generate Authorization header.
  77. *
  78. * @param array $signHeaders
  79. * @param int $datetime
  80. * @param \Overtrue\EasySms\Support\Config $config
  81. *
  82. * @return string
  83. */
  84. protected function generateSign(array $signHeaders, $datetime, Config $config)
  85. {
  86. // 生成 authString
  87. $authString = self::BCE_AUTH_VERSION.'/'.$config->get('ak').'/'
  88. .$datetime.'/'.self::DEFAULT_EXPIRATION_IN_SECONDS;
  89. // 使用 sk 和 authString 生成 signKey
  90. $signingKey = hash_hmac('sha256', $authString, $config->get('sk'));
  91. // 生成标准化 URI
  92. // 根据 RFC 3986,除了:1.大小写英文字符 2.阿拉伯数字 3.点'.'、波浪线'~'、减号'-'以及下划线'_' 以外都要编码
  93. $canonicalURI = str_replace('%2F', '/', rawurlencode(self::ENDPOINT_URI));
  94. // 生成标准化 QueryString
  95. $canonicalQueryString = ''; // 此 api 不需要此项。返回空字符串
  96. // 整理 headersToSign,以 ';' 号连接
  97. $signedHeaders = empty($signHeaders) ? '' : strtolower(trim(implode(';', array_keys($signHeaders))));
  98. // 生成标准化 header
  99. $canonicalHeader = $this->getCanonicalHeaders($signHeaders);
  100. // 组成标准请求串
  101. $canonicalRequest = "POST\n{$canonicalURI}\n{$canonicalQueryString}\n{$canonicalHeader}";
  102. // 使用 signKey 和标准请求串完成签名
  103. $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
  104. // 组成最终签名串
  105. return "{$authString}/{$signedHeaders}/{$signature}";
  106. }
  107. /**
  108. * 生成标准化 http 请求头串.
  109. *
  110. * @param array $headers
  111. *
  112. * @return string
  113. */
  114. protected function getCanonicalHeaders(array $headers)
  115. {
  116. $headerStrings = [];
  117. foreach ($headers as $name => $value) {
  118. //trim后再encode,之后使用':'号连接起来
  119. $headerStrings[] = rawurlencode(strtolower(trim($name))).':'.rawurlencode(trim($value));
  120. }
  121. sort($headerStrings);
  122. return implode("\n", $headerStrings);
  123. }
  124. /**
  125. * 根据 指定的 keys 过滤应该参与签名的 header.
  126. *
  127. * @param array $headers
  128. * @param array $keys
  129. *
  130. * @return array
  131. */
  132. protected function getHeadersToSign(array $headers, array $keys)
  133. {
  134. return array_intersect_key($headers, array_flip($keys));
  135. }
  136. }