Skip to content

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

Description

@awen1988

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

Activity

  1. awen1988 commented on Jun 2, 2021

    @awen1988
    Author

    简单的改成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  eric@zhu.email
     * @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学习

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions