记录一下.

对接阿里短信

参考与魔改的官方dome.

定义一个抽象类

文件路径app/lib/sms/SmsAbstract.php

<?php
namespace app\lib\sms;
abstract class SmsAbstract
{
    abstract public function sendVerifyCode($code, $phone);
}

定义阿里云驱动类(主要)

文件路径app/lib/sms/driver/Aliyun.php
这里继承上面的抽象类,此时必须实现sendVerifyCode方法

<?php
namespace app\lib\sms\driver;

use app\lib\sms\SmsAbstract;
use Exception;
use stdClass;
use think\facade\Env;

class Aliyun extends SmsAbstract
{
    public function sendVerifyCode($code, $phone)
    {
        $params = [];

        // 必填:是否启用https
        $security = false;

        // 必填
        $accessKeyId = Env::get('SMS_ALIYUN_ACCESSKEY_ID');
        $accessKeySecret = Env::get('SMS_ALIYUN_ACCESSKEY_SECRET');

        // 必填: 短信接收号码
        $params["PhoneNumbers"] = $phone;

        // 必填: 短信签名
        $params["SignName"] = Env::get('SMS_ALIYUN_SIGNNAME');

        // 必填: 短信模板Code
        $params["TemplateCode"] = Env::get('SMS_ALIYUN_TEMPLATECODE');

        // 可选: 设置模板参数, 假如模板中存在变量需要替换则为必填项
        $params['TemplateParam'] = [
            "code" => $code
        ];

        // *** 需用户填写部分结束, 以下代码若无必要无需更改 ***
        if (!empty($params["TemplateParam"]) && is_array($params["TemplateParam"])) {
            $params["TemplateParam"] = json_encode($params["TemplateParam"], JSON_UNESCAPED_UNICODE);
        }

        $params = array_merge($params, ["RegionId" => "cn-hangzhou", "Action" => "SendSms", "Version" => "2017-05-25"]);
        // 此处可能会抛出异常,注意catch
        return $this->request($accessKeyId, $accessKeySecret, "dysmsapi.aliyuncs.com", $params, $security);
    }

    /**
     * 生成签名并发起请求
     *
     * @param $accessKeyId string AccessKeyId (https://ak-console.aliyun.com/)
     * @param $accessKeySecret string AccessKeySecret
     * @param $domain string API接口所在域名
     * @param $params array API具体参数
     * @param $security boolean 使用https
     * @param string $method boolean 使用GET或POST方法请求,VPC仅支持POST
     * @return bool|stdClass 返回API接口调用结果,当发生错误时返回false
     */
    public function request(string $accessKeyId, string $accessKeySecret, string $domain, array $params, $security = false, $method = 'POST')
    {
        $apiParams = array_merge([
            "SignatureMethod" => "HMAC-SHA1",
            "SignatureNonce" => uniqid(mt_rand(0, 0xffff), true),
            "SignatureVersion" => "1.0",
            "AccessKeyId" => $accessKeyId,
            "Timestamp" => gmdate("Y-m-d\TH:i:s\Z"),
            "Format" => "JSON",
        ], $params);
        ksort($apiParams);

        $sortedQueryStringTmp = "";
        foreach ($apiParams 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, $accessKeySecret . "&", true));

        $signature = $this->encode($sign);

        $url = ($security ? 'https' : 'http') . "://{$domain}/";

        try {
            $content = $this->fetchContent($url, $method, "Signature={$signature}{$sortedQueryStringTmp}");
            return json_decode($content);
        } catch (Exception $e) {
            return false;
        }
    }

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

    private function fetchContent($url, $method, $body)
    {
        $ch = curl_init();

        if ($method == 'POST') {
            curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        } else {
            $url .= '?' . $body;
        }

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            "x-sdk-client" => "php/2.0.0"
        ));

        if (substr($url, 0, 5) == 'https') {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        $rtn = curl_exec($ch);

        if ($rtn === false) {
            // 大多由设置等原因引起,一般无法保障后续逻辑正常执行,
            // 所以这里触发的是E_USER_ERROR,会终止脚本执行,无法被try...catch捕获,需要用户排查环境、网络等故障
            trigger_error("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch), E_USER_ERROR);
        }
        curl_close($ch);

        return $rtn;
    }
}

定义工厂模式类

文件路径app/lib/Sms.php

<?php
namespace app\lib;

use app\lib\sms\SmsAbstract;
use think\Container;
use think\facade\Env;

class Sms
{
    public static function sendCode($code, $phone, $driver = '')
    {
        //获取驱动名字.此处.env里面定义的是ALIYUN.
        $driver = $driver ?: Env::get('SMS_DRIVER');
        //此处拼接完整的驱动类名,以后扩展别的短信平台,只需要再写一个驱动类,然后修改一下.env里面定义的驱动,或者直接使用这个方法的时候通过$driver变量指定
        $class = '\\app\\lib\\sms\\driver\\' . ucfirst(strtolower($driver));
        //此处把类放入使用Thinkphp的容器,相当于单例模式,没有的话会new一个然后保存,有的话直接返回出来.
        $obj = Container::getInstance()->make($class);
        //这里判断是否是抽象类的子类
        if ($obj instanceof SmsAbstract) {
            //抽象类的子类必须实现sendVerifyCode方法,此处可放心调用
            return $obj->sendVerifyCode($code, $phone);
        }
        return false;
    }

    public static function getCode($length = 6): string
    {
        $code = '';
        $chars = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
        while (strlen($code) < $length) {
            shuffle($chars);
            $code .= current($chars);
        }
        return $code;
    }
}

使用方法

<?php
namespace app\controller;

use app\lib\Sms;
use think\facade\Request;
use think\facade\Cache;

class Api
{
    public function send_sms()
    {
        $phone = Request::post('phone');

        if (!preg_match("/^1[3456789]\d{9}$/", $phone)) {
            return $this->error('非法手机号');
        }

        if (!empty(Cache::get('login' . $phone))) {
            return $this->error('请勿频繁请求');
        }

        //生成验证码
        $code = Sms::getCode();
        //发送短信
        Sms::sendCode($code, $phone);

        //验证码存入缓存10分钟
        Cache::set($phone, $code, 600);
        //缓存60秒,防止频繁请求API发送短信
        Cache::set('login' . $phone, 'send sms', 60);

        return $this->success('验证码发送成功');
    }
}

Composer 加载endroid/qr-code 生成二维码

毕竟Composer更优雅一些(相对于单文件生成直接exit结束后续操作)
很简单的东西,但是百度了一圈,好多过时错了,这里记一下

composer require endroid/qr-code

Builder是这个类Endroid\QrCode\Builder\Builder;

use Endroid\QrCode\Builder\Builder;

        //获取数据,这里如果是网址的话,get请求不能直接传,所以解码一下
        $data = Request::get('data', '', 'urldecode');
        if (empty($data)) {
            return Json::error('缺少必要参数{data}');
        }

        //此处返回的是data:image/png;base64,iVBORw0KGgoAAA...这种格式的字符串,前端src直接引用也能显示图片,并不是直接的文件
        //在此链式操作中最后一步是获取生成的数据,有4个方法getDataUri(),getString()直接成文件流设置一下header为图片就能直接返回图片资源,saveToFile()顾名思义应该是保存生成的二维码为图片,getMimeType()获取生成图片的MimeType比如'"image/png"'
        $src = Builder::create()->size(200)->data($data)->build()->getDataUri();
        return Json::success('', ['src' => $src]);

详细别的操作可访问https://github.com/endroid/qr-code

标签: PHP, thinkphp, 二维码, Aliyun, 短信, QrCode

已有 8 条评论

  1. gaqpaolcgx gaqpaolcgx

    操作步骤清晰,指导性强,易于实践。

  2. wkqltrwheu wkqltrwheu

    技术原理阐述透彻,配图辅助理解到位。

  3. bkkhyvyfxi bkkhyvyfxi

    案例丰富且贴合主题,论证逻辑环环相扣。

  4. mxmspsmngk mxmspsmngk

    独特的构思和新颖的观点,让这篇文章在众多作品中脱颖而出。

  5. fsrnqgvnwd fsrnqgvnwd

    作者的才华横溢,让这篇文章成为了一篇不可多得的艺术品。

  6. oheceebuvg oheceebuvg

    作者的布局谋篇匠心独运,让读者在阅读中享受到了思维的乐趣。

  7. fefvzyvcya fefvzyvcya

    文章中的实用建议和操作指南,让读者受益匪浅,值得珍藏。

  8. mhsatxnakx mhsatxnakx

    这篇文章不错!

添加新评论

Loading...
Fullscreen Image