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 GuzzleHttp\Exception\RequestException; |
15
|
|
|
use Overtrue\EasySms\Contracts\MessageInterface; |
16
|
|
|
use Overtrue\EasySms\Contracts\PhoneNumberInterface; |
17
|
|
|
use Overtrue\EasySms\Exceptions\GatewayErrorException; |
18
|
|
|
use Overtrue\EasySms\Exceptions\InvalidArgumentException; |
19
|
|
|
use Overtrue\EasySms\Support\Config; |
20
|
|
|
use Overtrue\EasySms\Traits\HasHttpRequest; |
21
|
|
|
|
22
|
|
|
class HuaweiGateway extends Gateway |
23
|
|
|
{ |
24
|
|
|
use HasHttpRequest; |
25
|
|
|
|
26
|
|
|
const ENDPOINT_HOST = 'https://api.rtc.huaweicloud.com:10443'; |
27
|
|
|
|
28
|
|
|
const ENDPOINT_URI = '/sms/batchSendSms/v1'; |
29
|
|
|
|
30
|
|
|
const SUCCESS_CODE = '000000'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* 发送信息. |
34
|
|
|
* |
35
|
|
|
* @param PhoneNumberInterface $to |
36
|
|
|
* @param MessageInterface $message |
37
|
|
|
* @param Config $config |
38
|
|
|
* |
39
|
|
|
* @return array |
|
|
|
|
40
|
|
|
* |
41
|
|
|
* @throws GatewayErrorException |
42
|
|
|
* @throws InvalidArgumentException |
43
|
|
|
*/ |
44
|
2 |
|
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config) |
45
|
|
|
{ |
46
|
2 |
|
$appKey = $config->get('app_key'); |
|
|
|
|
47
|
2 |
|
$appSecret = $config->get('app_secret'); |
|
|
|
|
48
|
2 |
|
$channels = $config->get('from'); |
|
|
|
|
49
|
2 |
|
$statusCallback = $config->get('callback', ''); |
50
|
|
|
|
51
|
2 |
|
$endpoint = $this->getEndpoint($config); |
52
|
2 |
|
$headers = $this->getHeaders($appKey, $appSecret); |
|
|
|
|
53
|
|
|
|
54
|
2 |
|
$templateId = $message->getTemplate($this); |
|
|
|
|
55
|
2 |
|
$messageData = $message->getData($this); |
56
|
|
|
|
57
|
|
|
// 短信签名通道号码 |
58
|
2 |
|
$from = 'default'; |
59
|
2 |
|
if (isset($messageData['from'])) { |
60
|
1 |
|
$from = $messageData['from']; |
61
|
1 |
|
unset($messageData['from']); |
62
|
1 |
|
} |
63
|
2 |
|
$channel = isset($channels[$from]) ? $channels[$from] : ''; |
64
|
|
|
|
65
|
2 |
|
if (empty($channel)) { |
66
|
|
|
throw new InvalidArgumentException("From Channel [{$from}] Not Exist"); |
|
|
|
|
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
$params = [ |
70
|
2 |
|
'from' => $channel, |
71
|
2 |
|
'to' => $to->getUniversalNumber(), |
72
|
2 |
|
'templateId' => $templateId, |
73
|
2 |
|
'templateParas' => json_encode($messageData), |
74
|
2 |
|
'statusCallback' => $statusCallback, |
75
|
2 |
|
]; |
76
|
|
|
|
77
|
|
|
try { |
78
|
2 |
|
$result = $this->request('post', $endpoint, [ |
79
|
2 |
|
'headers' => $headers, |
80
|
2 |
|
'form_params' => $params, |
81
|
|
|
//为防止因HTTPS证书认证失败造成API调用失败,需要先忽略证书信任问题 |
82
|
2 |
|
'verify' => false, |
83
|
2 |
|
]); |
84
|
2 |
|
} catch (RequestException $e) { |
85
|
|
|
$result = $this->unwrapResponse($e->getResponse()); |
|
|
|
|
86
|
|
|
} |
87
|
|
|
|
88
|
2 |
|
if (self::SUCCESS_CODE != $result['code']) { |
89
|
2 |
|
throw new GatewayErrorException($result['description'], ltrim($result['code'], 'E'), $result); |
90
|
|
|
} |
91
|
|
|
|
92
|
2 |
|
return $result; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* 构造 Endpoint. |
97
|
|
|
* |
98
|
|
|
* @param Config $config |
99
|
|
|
* |
100
|
|
|
* @return string |
101
|
|
|
*/ |
102
|
3 |
|
protected function getEndpoint(Config $config) |
103
|
|
|
{ |
104
|
3 |
|
$endpoint = rtrim($config->get('endpoint', self::ENDPOINT_HOST), '/'); |
105
|
|
|
|
106
|
3 |
|
return $endpoint.self::ENDPOINT_URI; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* 获取请求 Headers 参数. |
111
|
|
|
* |
112
|
|
|
* @param string $appKey |
113
|
|
|
* @param string $appSecret |
114
|
|
|
* |
115
|
|
|
* @return array |
116
|
|
|
*/ |
117
|
2 |
|
protected function getHeaders($appKey, $appSecret) |
118
|
|
|
{ |
119
|
|
|
return [ |
120
|
2 |
|
'Content-Type' => 'application/x-www-form-urlencoded', |
121
|
2 |
|
'Authorization' => 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"', |
122
|
2 |
|
'X-WSSE' => $this->buildWsseHeader($appKey, $appSecret), |
123
|
2 |
|
]; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* 构造X-WSSE参数值 |
128
|
|
|
* |
129
|
|
|
* @param string $appKey |
130
|
|
|
* @param string $appSecret |
131
|
|
|
* |
132
|
|
|
* @return string |
133
|
|
|
*/ |
134
|
2 |
|
protected function buildWsseHeader($appKey, $appSecret) |
135
|
|
|
{ |
136
|
2 |
|
$now = date('Y-m-d\TH:i:s\Z'); |
|
|
|
|
137
|
2 |
|
$nonce = uniqid(); |
|
|
|
|
138
|
2 |
|
$passwordDigest = base64_encode(hash('sha256', ($nonce.$now.$appSecret))); |
139
|
|
|
|
140
|
2 |
|
return sprintf('UsernameToken Username="%s",PasswordDigest="%s",Nonce="%s",Created="%s"', |
141
|
2 |
|
$appKey, $passwordDigest, $nonce, $now); |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
This check compares the return type specified in the
@return
annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.