- A+
在开发中,我们经常遇到手机号的验证,比如注册需要短信验证码,订单的处理需要短信通知用户等等,接下来,我们自己封装阿里云通信短信的发送。
首先,我们定义一个接口类,用来约束短信发送的一些必要方法,如发送(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;