提交 b606a7c2 编写于 作者: M milkmeowo 提交者: 安正超

增加华为云支持 (#148)

* 增加华为云 SMS 支持

* 文档更新支持华为云 SMS

* fix styleCI
上级 2a7e47f0
......@@ -520,6 +520,48 @@ $easySms->send(13188888888, $message);
],
```
### [华为云 SMS](https://www.huaweicloud.com/product/msgsms.html)
短信内容使用 `template` + `data`
```php
'huawei' => [
'endpoint' => '', // APP接入地址
'app_key' => '', // APP KEY
'app_secret' => '', // APP SECRET
'from' => [
'default' => '1069012345', // 默认使用签名通道号
'custom' => 'csms12345', // 其他签名通道号 可以在 data 中定义 from 来指定
'abc' => 'csms67890', // 其他签名通道号
...
],
'callback' => '' // 短信状态回调地址
],
```
使用默认签名通道 `default`
```php
$easySms->send(13188888888, [
'template' => 'SMS_001',
'data' => [
6379
],
]);
```
使用指定签名通道
```php
$easySms->send(13188888888, [
'template' => 'SMS_001',
'data' => [
6379,
'from' => 'custom' // 对应 config 中的 from 数组中 custom
],
]);
```
## License
MIT
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\RequestException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
class HuaweiGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_HOST = 'https://api.rtc.huaweicloud.com:10443';
const ENDPOINT_URI = '/sms/batchSendSms/v1';
const SUCCESS_CODE = '000000';
/**
* 发送信息.
*
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
* @throws InvalidArgumentException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$appKey = $config->get('app_key');
$appSecret = $config->get('app_secret');
$channels = $config->get('from');
$statusCallback = $config->get('callback', '');
$endpoint = $this->getEndpoint($config);
$headers = $this->getHeaders($appKey, $appSecret);
$templateId = $message->getTemplate($this);
$messageData = $message->getData($this);
// 短信签名通道号码
$from = 'default';
if (isset($messageData['from'])) {
$from = $messageData['from'];
unset($messageData['from']);
}
$channel = isset($channels[$from]) ? $channels[$from] : '';
if (empty($channel)) {
throw new InvalidArgumentException("From Channel [{$from}] Not Exist");
}
$params = [
'from' => $channel,
'to' => $to->getUniversalNumber(),
'templateId' => $templateId,
'templateParas' => json_encode($messageData),
'statusCallback' => $statusCallback,
];
try {
$result = $this->request('post', $endpoint, [
'headers' => $headers,
'form_params' => $params,
//为防止因HTTPS证书认证失败造成API调用失败,需要先忽略证书信任问题
'verify' => false,
]);
} catch (RequestException $e) {
$result = $this->unwrapResponse($e->getResponse());
}
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['description'], ltrim($result['code'], 'E'), $result);
}
return $result;
}
/**
* 构造 Endpoint.
*
* @param Config $config
*
* @return string
*/
protected function getEndpoint(Config $config)
{
$endpoint = rtrim($config->get('endpoint', self::ENDPOINT_HOST), '/');
return $endpoint.self::ENDPOINT_URI;
}
/**
* 获取请求 Headers 参数.
*
* @param string $appKey
* @param string $appSecret
*
* @return array
*/
protected function getHeaders($appKey, $appSecret)
{
return [
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"',
'X-WSSE' => $this->buildWsseHeader($appKey, $appSecret),
];
}
/**
* 构造X-WSSE参数值
*
* @param string $appKey
* @param string $appSecret
*
* @return string
*/
protected function buildWsseHeader($appKey, $appSecret)
{
$now = date('Y-m-d\TH:i:s\Z');
$nonce = uniqid();
$passwordDigest = base64_encode(hash('sha256', ($nonce.$now.$appSecret)));
return sprintf('UsernameToken Username="%s",PasswordDigest="%s",Nonce="%s",Created="%s"',
$appKey, $passwordDigest, $nonce, $now);
}
}
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Tests\Gateways;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Gateways\HuaweiGateway;
use Overtrue\EasySms\Message;
use Overtrue\EasySms\PhoneNumber;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Tests\TestCase;
class HuaweiGatewayTest extends TestCase
{
/**
* 测试 发送华为短信
*/
public function testSend()
{
$config = [
'endpoint' => 'mock-endpoint',
'app_key' => 'mock-app-key',
'app_secret' => 'mock-app-secret',
'from' => [
'default' => 'mock-default-from',
],
'callback' => 'mock-callback',
];
$expectedParams = [
'from' => 'mock-default-from',
'to' => '13800138000',
'templateId' => 'mock-tpl-id',
'templateParas' => '["mock-data-1","mock-data-2"]',
'statusCallback' => 'mock-callback',
];
$gateway = \Mockery::mock(HuaweiGateway::class.'[request]', [$config])->shouldAllowMockingProtectedMethods();
$gateway->shouldReceive('request')
->with('post',
\Mockery::on(function ($endpoint) use ($config) {
return $config['endpoint'].'/sms/batchSendSms/v1' === $endpoint;
}),
\Mockery::on(function ($params) use ($expectedParams) {
ksort($params['form_params']);
ksort($expectedParams);
return $params['form_params'] == $expectedParams;
})
)->andReturn(
['code' => HuaweiGateway::SUCCESS_CODE, 'description' => 'Success', 'result' => 'mock-result'],
['code' => 'E200037', 'description' => 'The SMS fails to be sent. For details, see status']
)->twice();
$message = new Message([
'template' => 'mock-tpl-id',
'data' => ['mock-data-1', 'mock-data-2'],
]);
$config = new Config($config);
$phoneNum = new PhoneNumber(13800138000);
$expectedSuccessResult = ['code' => HuaweiGateway::SUCCESS_CODE, 'description' => 'Success', 'result' => 'mock-result'];
$this->assertSame($expectedSuccessResult, $gateway->send($phoneNum, $message, $config));
$this->expectException(GatewayErrorException::class);
$this->expectExceptionCode(200037);
$this->expectExceptionMessage('The SMS fails to be sent. For details, see status');
$gateway->send($phoneNum, $message, $config);
}
/**
* 测试 自定义签名通道.
*/
public function testMultiFrom()
{
$config = [
'endpoint' => 'mock-endpoint',
'app_key' => 'mock-app-key',
'app_secret' => 'mock-app-secret',
'from' => [
'default' => 'mock-default-from',
'custom' => 'mock-custom-from', // 配置自定义签名通道
],
'callback' => 'mock-callback',
];
$expectedParams = [
'from' => 'mock-custom-from',
'to' => '13800138000',
'templateId' => 'mock-tpl-id',
'templateParas' => '["mock-data-1","mock-data-2"]',
'statusCallback' => 'mock-callback',
];
$gateway = \Mockery::mock(HuaweiGateway::class.'[request]', [$config])->shouldAllowMockingProtectedMethods();
$gateway->shouldReceive('request')
->with('post',
\Mockery::on(function ($endpoint) use ($config) {
return $config['endpoint'].'/sms/batchSendSms/v1' === $endpoint;
}),
\Mockery::on(function ($params) use ($expectedParams) {
ksort($params['form_params']);
ksort($expectedParams);
return $params['form_params'] == $expectedParams;
})
)->andReturn(
['code' => HuaweiGateway::SUCCESS_CODE, 'description' => 'Success', 'result' => 'mock-result'],
['code' => 'E200037', 'description' => 'The SMS fails to be sent. For details, see status']
)->twice();
$message = new Message([
'template' => 'mock-tpl-id',
'data' => [
'mock-data-1',
'mock-data-2',
'from' => 'custom', // 设置自定义签名通道
],
]);
$config = new Config($config);
$phoneNum = new PhoneNumber(13800138000);
$expectedSuccessResult = ['code' => HuaweiGateway::SUCCESS_CODE, 'description' => 'Success', 'result' => 'mock-result'];
$this->assertSame($expectedSuccessResult, $gateway->send($phoneNum, $message, $config));
$this->expectException(GatewayErrorException::class);
$this->expectExceptionCode(200037);
$this->expectExceptionMessage('The SMS fails to be sent. For details, see status');
$gateway->send($phoneNum, $message, $config);
}
/**
* 测试 endpoint.
*
* @throws \ReflectionException
*/
public function testGetEndpoint()
{
$method = new \ReflectionMethod(HuaweiGateway::class, 'getEndpoint');
$method->setAccessible(true);
$gateway = \Mockery::mock(HuaweiGateway::class.'[request]', [[]])->shouldAllowMockingProtectedMethods();
$defaultEndpoint = 'https://api.rtc.huaweicloud.com:10443/sms/batchSendSms/v1';
$this->assertSame($defaultEndpoint, $method->invoke($gateway, new Config()));
$config = new Config([
'endpoint' => 'mock-endpoint',
]);
$endpoint = 'mock-endpoint/sms/batchSendSms/v1';
$this->assertSame($endpoint, $method->invoke($gateway, $config));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册