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