使用PHP工厂模式实现阿里云通信短信的发送(ThinkPHP5)

  • A+
所属分类:PHP ThinkPHP5

在开发中,我们经常遇到手机号的验证,比如注册需要短信验证码,订单的处理需要短信通知用户等等,接下来,我们自己封装阿里云通信短信的发送。

首先,我们定义一个接口类,用来约束短信发送的一些必要方法,如发送(send)、发送成功回调(sendSuccess)、发送失败回调(sendFail)等。 因为在开发中,我们不仅仅会使用阿里云短信通知,可能有极光推送、钉钉推送、微信小程序订阅消息等,可用接口类,约束,这些实现接口内的方法,来保证调用一致,并且易于扩展。

SendMsgInterface类:

namespace app\common\tool\interfaces;

interface SendMsgInterface
{
    //发送
    public function send(array $message);
    //发送成功
    public function sendSuccess(array $sendData, array $sendRes);
    //发送失败
    public function sendFail(array $sendData, array $sendRes);
    //发送日志
    public function sendLog(array $sendData, array $sendRes);
}

然后定义阿里云短信类,来实现SendMsgInterface接口:

AliMsgSend类:

namespace app\common\tool\message;

use app\common\tool\Curl;
use app\common\tool\interfaces\SendMsgInterface;
use think\Config;
use think\Exception;

class AliMsgSend implements SendMsgInterface
{
     public function sned(array $message)
     {
         
     }   
     
     public function sendSuccess(array $sendData, array $sendRes)
     {
         
     }

     public function sendFail(array $sendData, array $sendRes)
     {
    
     }
     
     public function sendLog(array $sendData, array $sendRes)
     {
        
     }
}

我们先不用去实现sendSuccess、sendFail、sendLog 方法,先实现send(发送短信方法)

因为发送需要用到阿里云的一些配置信息,我们将其放入配置文件ali.php,如下:

<?php

    return [
        'AccessKeyId'     => '',
        'AccessKeySecret' => '',
        'domain'            => 'dysmsapi.aliyuncs.com',
        'security'          => false,
        'SignName'          => '',
        'SignatureMethod'   => 'HMAC-SHA1',
        'SignatureVersion'  => '1.0',
        'Version'           => '2017-05-25',
        'Format'            => 'JSON',
        'RegionId'          => 'cn-hangzhou',
        'Action'            => 'SendSms'
    ];

接下来,我们在构造方法内实现配置的获取:

namespace app\common\tool\message;

use app\common\tool\Curl;
use app\common\tool\interfaces\SendMsgInterface;
use think\Config;
use think\Exception;

class AliMsgSend implements SendMsgInterface
{
    protected $config = [];

    public function __construct()
    {
        $this->config = Config::get('ali');
    }

接下来实现 send方法:

   public function send(array $message)
    {
        $params["PhoneNumbers"] = $message['telephone'];//手机号

        $templateObject = $this->getTemplateClass(ucfirst(strtolower($message['type'])));


        $params["TemplateCode"] = $templateObject->getTemplateCode();
        if(isset($message['extra']))
        {
            $params['TemplateParam'] = $templateObject->getSendData($message['extra']);
            $params["TemplateParam"] = json_encode($params["TemplateParam"], JSON_UNESCAPED_UNICODE);
        }

        //生成签名
        $sign = $this->createSignature($params);
        $url = ($this->config['security'] ? 'https' : 'http')."://{$this->config['domain']}/";

        $res = Curl::curlRequest($url, 'POST', ["x-sdk-client" => "php/2.0.0"], $sign);
        $res = json_decode($res, true);
        if(false === $res || "OK" != $res['Message'])
        {
            $this->sendFail($message, $res);
        }
        $this->sendSuccess($message, $res);
    }

我们来这段代码, $templateObject = $this->getTemplateClass(ucfirst(strtolower($message['type'])));  ,因为阿里云的短信必须申请短信模板,因此我们将模板信息存储到子类中,通过getTemplateClass获取子类,并在子类中给一个统一的方法getSendData()来实现数据的拼装。

我们来看模板子类,我们先定义一个父类,AliMessageTemplate抽象类,实现getSendData(),并约束子类必须完成模板编号以及模板变量的返回。

namespace app\common\tool\message;


abstract class AliMessageTemplate
{
    public function __construct()
    {

    }

    public function getSendData($templateData): array
    {
        $data = [];

        foreach ($this->getTemplateInfo() as $key)
        {
            $data[$key] = $templateData[$key];
        }

        return $data;
    }

    abstract public function getTemplateCode(): string;

    abstract public function getTemplateInfo(): array;
}

我们以短信通知为例,继承模板父类以及实现 getTemplateCode() 和 getTemplateInfo() 方法。

namespace app\common\tool\message\aliSmsTemplate;


use app\common\tool\message\AliMessageTemplate;

class NoticeTemplate extends AliMessageTemplate
{
    public function getTemplateCode(): string
    {
        return 'SMS_177350420';
    }

    public function getTemplateInfo(): array
    {
        return ['code'];
    }
}

因此我们在send()方法中用到的getTemplateClass()方法,可以通过以下方式实现:

private function getTemplateClass($templateName)
    {
        $templateClassName = '\\app\\common\\tool\\message\\aliSmsTemplate\\' . $templateName . 'Template';
        return new $templateClassName();
    }

这样,在我们就实现了一个简单的工厂模式,在有其他模板需要发送短信时,只需要建立模板类并继承AliMessageTemplate,实现getTemplateCode()方法和getTemplateInfo()方法即可。

下面附下阿里云短信签名的生成方法 createSignature():

 protected function createSignature($params ,$method = 'POST')
 
    {
        $signParams = array_merge([
            "SignatureMethod" => $this->config['SignatureMethod'],
            "SignatureNonce" => uniqid(mt_rand(0,0xffff), true),
            "SignatureVersion" => $this->config['SignatureVersion'],
            "AccessKeyId" => $this->config['AccessKeyId'],
            "Timestamp" => gmdate("Y-m-d\TH:i:s\Z"),
            "Format" => $this->config['Format'],
            'RegionId' => $this->config['RegionId'],
            'Action' => $this->config['Action'],
            'Version' => $this->config['Version'],
            'SignName' => $this->config['SignName']
        ], $params);
        ksort($signParams);

        $sortedQueryStringTmp = "";
        foreach ($signParams as $key => $value) {
            $sortedQueryStringTmp .= "&" . $this->encode($key) . "=" . $this->encode($value);
        }

        $stringToSign = "${method}&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1));
        $sign = base64_encode(hash_hmac("sha1", $stringToSign, 'I14BUBB23vp4mKNNXVbDglcU0U4pIP' . "&",true));
        $signature = $this->encode($sign);
        return  "Signature={$signature}{$sortedQueryStringTmp}";
    }


    private function getTemplateClass($templateName)
    {
        $templateClassName = '\\app\\common\\tool\\message\\aliSmsTemplate\\' . $templateName . 'Template';
        return new $templateClassName();
    }


    private function encode($str)
    {
        $res = urlencode($str);
        $res = preg_replace("/\+/", "%20", $res);
        $res = preg_replace("/\*/", "%2A", $res);
        $res = preg_replace("/%7E/", "~", $res);
        return $res;
    }

附Curl请求方法:

namespace app\common\tool;


class Curl
{
    static final function curlRequest($url, $method = 'GET', $headers = '', $params = [])
    {
        if (is_array($params)) {
            $requestString = http_build_query($params);
        } else {
            $requestString = $params ?: '';
        }
        if (empty($headers)) {
            $headers = array('Content-type: text/json');
        } elseif (!is_array($headers)) {
            parse_str($headers, $headers);
        }
        // setting the curl parameters.
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_VERBOSE, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        // turning off the server and peer verification(TrustManager Concept).
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        // setting the POST FIELD to curl
        switch ($method) {
            case "GET" :
                curl_setopt($ch, CURLOPT_HTTPGET, 1);
                $url = $url.'?'.$requestString;
                curl_setopt($ch, CURLOPT_URL, $url);
                break;
            case "POST":
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $requestString);
                break;
            case "PUT" :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
                curl_setopt($ch, CURLOPT_POSTFIELDS, $requestString);
                break;
            case "DELETE":
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
                curl_setopt($ch, CURLOPT_POSTFIELDS, $requestString);
                break;
        }
        // getting response from server
        $response = curl_exec($ch);
        //close the connection
        curl_close($ch);

        //return the response
        if (stristr($response, 'HTTP 404') || $response == '') {
            return array('Error' => '请求错误');
        }
        return $response;

avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: