sms icon indicating copy to clipboard operation
sms copied to clipboard

测试发现百度云已经不支持了?

Open awen1988 opened this issue 4 years ago • 1 comments

简单测试了一下百度云的短信好像不支持了,看了一下驱动源码,用了很老的接口,百度云已经更新至V3了,可能还是可以用,我的测试方法没对 然后我感觉如果要实现动态配置实现多平台,多签名,多模板用这类的集合组件可能不是一个很好的选择

awen1988 avatar Jun 02 '21 07:06 awen1988

简单的改成V3支持方法:

1、config\autoload\sms.php增加v3的配置,ak、sk我放在了env中设置,也可以直接写,但是不要提交到托管平台避免泄露

// ……其他配置
'baidu_cloud_v3' => [
            'driver' => \cms\sms\drivers\BaiduCloudV3Driver::class,
            'config' => [
                'ak' => env('SMS_BAIDU_CLOUD_V3_AK', ''),
                'sk' => env('SMS_BAIDU_CLOUD_V3_SK', ''),
                'signature_id' => env('SMS_BAIDU_CLOUD_V3_SIGN', ''),  //公用的短信签名
            ],
        ],

2、创建百度云短信v3的驱动,内容跟原驱动一模一样,只是把接口地址,参数修改了,也可以做在配置中,我这里直接写到代码中了,因为一般不会换,换了就意味着版本升级,之前的代码肯定不能用(文件位置随便放,能够加载就行)

<?php
declare(strict_types=1);

namespace cms\sms\drivers;

use HyperfExt\Sms\Contracts\SmsableInterface;
use HyperfExt\Sms\Drivers\AbstractDriver;
use HyperfExt\Sms\Exceptions\DriverErrorException;


class BaiduCloudV3Driver extends AbstractDriver
{
    protected const ENDPOINT_HOST = 'smsv3.bj.baidubce.com';

    protected const ENDPOINT_URI = '/api/v3/sendSms';

    protected const BCE_AUTH_VERSION = 'bce-auth-v1';

    protected const DEFAULT_EXPIRATION_IN_SECONDS = 1800; //签名有效期默认1800秒

    protected const SUCCESS_CODE = 1000;

    public function send(SmsableInterface $smsable): array
    {
        $params = [
            // 手机号码
            'mobile' => $smsable->to->getNationalNumber(),
            // 短信模板
            'template' => $smsable->template,
            // 签名
            'signatureId' => $smsable->signature ?: $this->config->get('signature_id'),
            // 模板变量
            'contentVar' => $smsable->data,
            // 自定义参数,暂不支持
            // 'custom' => '',
            // 自定义扩展码,暂不支持
            // 'userExtId' => '',
        ];

        $datetime = gmdate('Y-m-d\TH:i:s\Z');

        $headers = [
            'host' => self::ENDPOINT_HOST,
            'content-type' => 'application/json',
            'x-bce-date' => $datetime,
            // 'x-bce-content-sha256' => hash('sha256', json_encode($params)),
        ];
        //获得需要签名的数据
        $signHeaders = $this->getHeadersToSign($headers, ['host', 'x-bce-date']);

        $headers['Authorization'] = $this->generateSign($signHeaders, $datetime);
        $response = $this->client->postJson($this->buildEndpoint(), $params, $headers);

        $result = $response->toArray();

        if ($result['code'] != self::SUCCESS_CODE) {
            throw new DriverErrorException($result['message'], $result['code'], $response);
        }

        return $result;
    }

    protected function buildEndpoint(): string
    {
        return 'http://' . $this->config->get('domain', self::ENDPOINT_HOST) . self::ENDPOINT_URI;
    }

    protected function generateSign(array $signHeaders, string $datetime): string
    {
        // 生成 authString
        $authString = self::BCE_AUTH_VERSION . '/' . $this->config->get('ak') . '/'
            . $datetime . '/' . self::DEFAULT_EXPIRATION_IN_SECONDS;

        // 使用 sk 和 authString 生成 signKey
        $signingKey = hash_hmac('sha256', $authString, $this->config->get('sk'));
        // 生成标准化 URI
        // 根据 RFC 3986,除了:1.大小写英文字符 2.阿拉伯数字 3.点'.'、波浪线'~'、减号'-'以及下划线'_' 以外都要编码
        $canonicalURI = str_replace('%2F', '/', rawurlencode(self::ENDPOINT_URI));

        // 生成标准化 QueryString
        $canonicalQueryString = ''; // 此 api 不需要此项。返回空字符串

        // 整理 headersToSign,以 ';' 号连接
        $signedHeaders = empty($signHeaders) ? '' : strtolower(trim(implode(';', array_keys($signHeaders))));

        // 生成标准化 header
        $canonicalHeader = $this->getCanonicalHeaders($signHeaders);

        // 组成标准请求串
        $canonicalRequest = "POST\n{$canonicalURI}\n{$canonicalQueryString}\n{$canonicalHeader}";

        // 使用 signKey 和标准请求串完成签名
        $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);

        // 组成最终签名串
        return "{$authString}/{$signedHeaders}/{$signature}";
    }

    protected function getCanonicalHeaders(array $headers): string
    {
        $headerStrings = [];
        foreach ($headers as $name => $value) {
            //trim后再encode,之后使用':'号连接起来
            $headerStrings[] = rawurlencode(strtolower(trim($name))) . ':' . rawurlencode(trim($value));
        }

        sort($headerStrings);

        return implode("\n", $headerStrings);
    }

    protected function getHeadersToSign(array $headers, array $keys): array
    {
        return array_intersect_key($headers, array_flip($keys));
    }
}

3、使用方式与文档中的示例一致,不过经实测发现,百度云的短信就算是普通营销短信,模板变量也必须要传?随便传一个都可以,不传要报错

控制器中示例:

<?php
declare(strict_types=1);
namespace cms\sms\api_controller;

use cms\sms\service\VerificationCodeService;
use Hyperf\HttpServer\Annotation\AutoController;
use HyperfExt\Sms\Sms;

/**
 * Class TestController
 * @package cms\sms\api_controller
 * @AutoController(prefix="api/sms/test")
 */
class TestController extends CommonController
{
    public function t001()
    {
        $a = Sms::sender('baidu_cloud_v3')->to('1898047xxxx', '86')->send(new VerificationCodeService());
        var_dump($a);
        return 'ok';
    }
}

消息类中示例(这里要注意,使用gen:sms命令生成的代码默认使用了队列,根据自己情况来):

<?php

declare(strict_types=1);
/**
 * This file is part of hyperf-ext/sms.
 *
 * @link     https://github.com/hyperf-ext/sms
 * @contact  [email protected]
 * @license  https://github.com/hyperf-ext/sms/blob/master/LICENSE
 */
namespace cms\sms\service;

// use HyperfExt\Contract\ShouldQueue;
use HyperfExt\Sms\Contracts\SenderInterface;
use HyperfExt\Sms\Smsable;

class VerificationCodeService extends Smsable
{
    /**
     * Create a new SMS message instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Build the SMS message.
     */
    public function build(SenderInterface $sender): void
    {
        // 不带签名使用配置
        // $this->template('sms-tmpl-TuEOTX86xxxx')->with(['name' => 'awen']);
        // 带签名
        $this->template('sms-tmpl-ZHFXcnxxx')->with(['name' => 'awen'])->signature('sms-sign-RmFfnu564xxx');
        // 不带签名,模板没有变量也要传变量?
        // $this->template('sms-tmpl-AIgLRm0xxx')->with('now', '');
    }
}

后续想要实现的功能:

1、需要支持数据库配置,多模板,多签名,多服务商 有一个hyperf的代码学习仓库:Hyperf学习

awen1988 avatar Jun 02 '21 10:06 awen1988