引用短信


  阿里短信上来就索要钱财,申请还麻烦.又一款不错的第三方短信平台云之讯.之前注册过就直接用啦~进入thinkphpextend目录创建ucpass.将下载的PHP包扔进去~

  创建application/sms/Ucpass.php

namespace app\sms;
class Ucpaas
{
    //API请求地址
    const BaseUrl = "https://open.ucpaas.com/ol/sms/";

    //开发者账号ID。由32个英文字母和阿拉伯数字组成的开发者账号唯一标识符。
    private $accountSid;

    //开发者账号TOKEN
    private $token;

    public function  __construct($options)
    {
        if (is_array($options) && !empty($options)) {
            $this->accountSid = isset($options['accountsid']) ? $options['accountsid'] : '';
            $this->token = isset($options['token']) ? $options['token'] : '';
        } else {
            throw new Exception("非法参数");
        }
    }

    private function getResult($url, $body = null, $method)
    {
        $data = $this->connection($url,$body,$method);
        if (isset($data) && !empty($data)) {
            $result = $data;
        } else {
            $result = '没有返回数据';
        }
        return $result;
    }

    /**
     * @param $url    请求链接
     * @param $body   post数据
     * @param $method post或get
     * @return mixed|string
     */

    private function connection($url, $body,$method)
    {
        if (function_exists("curl_init")) {
            $header = array(
                'Accept:application/json',
                'Content-Type:application/json;charset=utf-8',
            );
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
            if($method == 'post'){
                curl_setopt($ch,CURLOPT_POST,1);
                curl_setopt($ch,CURLOPT_POSTFIELDS,$body);
            }
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            $result = curl_exec($ch);
            curl_close($ch);
        } else {
            $opts = array();
            $opts['http'] = array();
            $headers = array(
                "method" => strtoupper($method),
            );
            $headers[]= 'Accept:application/json';
            $headers['header'] = array();
            $headers['header'][]= 'Content-Type:application/json;charset=utf-8';

            if(!empty($body)) {
                $headers['header'][]= 'Content-Length:'.strlen($body);
                $headers['content']= $body;
            }

            $opts['http'] = $headers;
            $result = file_get_contents($url, false, stream_context_create($opts));
        }
        return $result;
    }

    /**
    单条发送短信的function,适用于注册/找回密码/认证/操作提醒等单个用户单条短信的发送场景
     * @param $appid        应用ID
     * @param $mobile       接收短信的手机号码
     * @param $templateid   短信模板,可在后台短信产品→选择接入的应用→短信模板-模板ID,查看该模板ID
     * @param null $param   变量参数,多个参数使用英文逗号隔开(如:param=“a,b,c”)
     * @param $uid            用于贵司标识短信的参数,按需选填。
     * @return mixed|string
     * @throws Exception
     */
    public function SendSms($appid,$templateid,$param=null,$mobile,$uid){
        $url = self::BaseUrl . 'sendsms';
        $body_json = array(
            'sid'=>$this->accountSid,
            'token'=>$this->token,
            'appid'=>$appid,
            'templateid'=>$templateid,
            'param'=>$param,
            'mobile'=>$mobile,
            'uid'=>$uid,
        );
        $body = json_encode($body_json);
        $data = $this->getResult($url, $body,'post');
        return $data;
    }

    /**
    群发送短信的function,适用于运营/告警/批量通知等多用户的发送场景
     * @param $appid        应用ID
     * @param $mobileList   接收短信的手机号码,多个号码将用英文逗号隔开,如“18088888888,15055555555,13100000000”
     * @param $templateid   短信模板,可在后台短信产品→选择接入的应用→短信模板-模板ID,查看该模板ID
     * @param null $param   变量参数,多个参数使用英文逗号隔开(如:param=“a,b,c”)
     * @param $uid            用于贵司标识短信的参数,按需选填。
     * @return mixed|string
     * @throws Exception
     */
    public function SendSms_Batch($appid,$templateid,$param=null,$mobileList,$uid){
        $url = self::BaseUrl . 'sendsms_batch';
        $body_json = array(
            'sid'=>$this->accountSid,
            'token'=>$this->token,
            'appid'=>$appid,
            'templateid'=>$templateid,
            'param'=>$param,
            'mobile'=>$mobileList,
            'uid'=>$uid,
        );
        $body = json_encode($body_json);
        $data = $this->getResult($url, $body,'post');
        return $data;
    }
}

  创建application/sms/UcpassConf.php

namespace app\sms;

use app\sms\Ucpaas;

class UcpaasConf
{
    static public function send($mobile,$param)
    {
        //初始化必填
        // 填写在开发者控制台首页上的Account Sid
        $options['accountsid'] = "xxxxxxx";
        // 填写在开发者控制台首页上的Auth Token
        $options['token'] = "xxxxxxx";
        // 初始化 $options必填
        $ucpass = new Ucpaas($options);
        $appid = "xxxxxxx";    //应用的ID,可在开发者控制台内的短信产品下查看
        $templateid = "272661";    //可在后台短信产品→选择接入的应用→短信模板-模板ID,查看该模板ID
        // $param = $_POST['yzm']; //多个参数使用英文逗号隔开(如:param=“a,b,c”),如为参数则留空
        // $mobile = $_POST['yzmtel'];
        // $param = 1324;
        // $mobile = 13501294164;
        $uid = "";
        // 70字内(含70字)计一条,超过70字,按67字/条计费,超过长度短信平台将会自动分割为多条发送。分割后的多条短信将按照具体占用条数计费。
        return $ucpass->SendSms($appid, $templateid, $param, $mobile, $uid);
    }
}

登录逻辑

配置文件

  为了规范编写代码一定要养成良好的编码习惯.tp5.0.2后的配置可以在application目录下创建extra目录然后存放自己编辑的配置文件,例如:在application/config.php中添加

# 配置文件名称
'extra_config_list' => ['code','redis','web','Upy'],

  创建配置文件application/extra/code.php

return [
    'success' => 0,
    'error_phone_empty' => 100001,
    'error_phone_type' => 100002,
    'error_code_empty' => 100003,
    'error_code' => 100004,
    'error_sms_error' => 100005,
    'error_phone_code_empty' => 100006,
    'error_login_set' => 100007,
    'error_upload_image' => 100008,
    'error_perfect' => 100009,  //更爱个人信息失败
    'error_perfect_token' => 100010, //更改个人信息token错误
    'error_perfect_empty' => 100010, //更改个人信息参数错误
    'error_admin_game_id_empty' => 100011, //直播数据gameId为空
    'error_admin_game_data_empty' => 100012, //直播数据gameId为空
];

  获取code配置文件

// 获取code所以配置
print_r(Config::get('code'));
// 获取code下success的值
print_r(Config::get('code.success'));

发送验证码

  创建application/index/controller/Send.php

namespace app\index\controller;

use app\command\Util;
use app\sms\Ucpaas;
use app\sms\UcpaasConf;
use think\Config;
use app\command\Redis;

class Send
{
    /*
     * 发送验证码
     * */
    public function index()
    {
        /*
         * 获取手机号
         * 默认参数是0。将传来的自负转改成整型
         * */
        $phoneNum = request()->get('phone_num', 0, 'intval');
        if (empty($phoneNum)) {
            return Util::show(Config::get('code.error_phone_empty'), '手机号不能为空 ');
        }
        $code = rand(1000, 9999);
        try {
            $json_rst = UcpaasConf::send($phoneNum, $code);
        } catch (\Exception $e) {
            return Util::show(Config::get('code.error_sms_error'), '云之信短信异常');
        }
                /*判断云之讯断线是否发送成功*/
        $rst = json_decode($json_rst,true);
        $rst['code'] = '000000';
        if ($rst['code'] === '000000') {
            /*将验证码存储到redis  存储可以异步。验证必须要同步的 */
            go(function () use ($phoneNum, $code) {
                $redis_server = new \Swoole\Coroutine\Redis();
                $link = $redis_server->connect(Config::get('redis.host'), Config::get('redis.port'));
                $redis_server->set(Redis::smsKey($phoneNum),$code,Config::get('redis.over_time'));
            });
            return Util::show(Config::get('code.success'), '发送成功');
        }
    }
}

  发送验证码成功啦.但是每次设置redis的时候都会去链接redis.没有这个必要的,对redis代码进行优化,在application/command/Predis.php这是之前自己配置链接redis类.优化主要是思想,没必要所有代码都一样.

namespace app\command;

use think\Config;

class Predis
{
    /*
     * 定义单例模式
     * 多次使用redis但是只链接一次redis*/
    public $redis = '';

    static private $_instance = null;

    private function __construct()
    {
        $this->redis = new \Redis();
        $link = $this->redis->connect(Config::get('redis.host'), Config::get('redis.port'), Config::get('redis.link_time_out'));
        if (!$link) {
            throw new \Exception('redis connect error');
        }
    }

    static public function getIntance()
    {
        if (empty(self::$_instance)) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    public function __call($name, $arguments)
    {
        return $this->client->$name(...$arguments);
    }

}

  单例模式:当请求getIntance() 会对成员变量$_instance进行判断,如果为null说明没有链接,自己实例化进入__construct链接redis,下次再来发现链接啦redis直接返回链接的redis这样节省了时间和资源~

  __call为魔术方法,当调用不存在的方法会进入__call.关于用法建议看之前的文章

优化验证码类

try {
    $json_rst = UcpaasConf::send($phoneNum, $code);
} catch (\Exception $e) {
    return Util::show(Config::get('code.error_sms_error'), '云之信短信异常');
}

  这是向云之讯发送请求通过第三方去发送验证码,这里要注意:我们不要太过于相信第三方的东西,因为他们的服务也有可能挂掉,比如请求了好几分也没有完成.那我们下面的代码会一直等待无法继续执行,用户体验感会很差!.像这样极其耗时的逻辑可以用Task异步任务来处理.

$code = rand(1000, 9999);

// $taskData = [
//     'phone' => $phoneNum,
//     'code' => $code
// ];
$taskData = [
    'method' => 'sendSms',
    'data' => [
        'phone' => $phoneNum,
        'code' => $code
    ]
];
$_POST['http_server']->task($taskData);
return Util::show(Config::get('code.success'), '发送成功');

  去掉原来的发送验证码逻辑.获取server/http.php中的$_POST['http_server'].将手机号和验证码传给Task.直接返回发送成功.编辑server/http.php中的onTask方法.

public function onTask($server,$taskId,$workerId,$data)
{
    try {
        $obj = new app\sms\UcpaasConf();
        $json_rst = $obj::send($data['phone'], $data['code']);
    } catch (\Exception $e) {
        // 建议写日志,发警报~不建议直接输出错误
        echo $e->getMessage();
        // return Util::show(Config::get('code.error_sms_error'), '云之信短信异常');
    }
    return "on task finish";   
}

// Task执行成功,会进入该方法
public function onFinish($server,$taskId,$data)
{
    echo "finish-data-success:{$data}\n";
}

  这样测试结果是没有问题的~

工厂模式

  上面的代码虽然成功.但是你会发现非常的乱~如果以后有更多的Task任务,这里的代码会非常臃肿也不好维护.建立一个工厂模式,将它们分离出来.

  修改application/index/controller/Send.php部分代码

//$taskData = [
//     'phone' => $phoneNum,
//     'code' => $code
// ];
$taskData = [
    'method' => 'sendSms',
    'data' => [
        'phone' => $phoneNum,
        'code' => $code
    ]
];
$_POST['http_server']->task($taskData);

  创建application/command/Task.php

namespace app\command;

use app\sms\UcpaasConf;
use think\Config;
use app\command\Predis;


class Task
{
    /*异步发送验证*/
    public function sendSms($data)
    {
        /*发送验证码*/
        try {
            $json_rst = UcpaasConf::send($data['phone'], $data['code']);
        } catch (\Exception $e) {
            return false;
        }
        /*验证*/
        /*判断云之讯断线是否发送成功*/
        $rst = json_decode($json_rst, true);
        $rst['code'] = '000000';
        if ($rst['code'] === '000000') {
            /*将验证码存储到redis  存储可以异步。验证必须要同步的 */
            //  go(function () use ($data) {
            // $redis_server = new \Swoole\Coroutine\Redis();
            // $link = $redis_server->connect(Config::get('redis.host'), Config::get('redis.port'));
            // $redis_server->set(Redis::smsKey($data['phone']),$data['code'],Config::get('redis.over_time'));
        // });
            // return Util::show(Config::get('code.success'), '发送成功');
            Predis::getIntance()->set(Redis::smsKey($data['phone']), $data['code'], Config::get('redis.over_time'));
            return true;
        } else {
            return false;
        }
    }
}

  修改server/http.phpTask任务

public function onTask($server,$taskId,$workerId,$data)
{
    /*分发task任务 让不同任务走不同逻辑*/
    $obj = new app\command\Task();
    $method = $data['method'];
    $flag = $obj->$method($data['data']);
    return $flag;
}

  ok~

JWT用户认证

  Http协定是不储存状态的(stateless),这意味着当我们通过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了,于是程序就不知道谁是谁,就要再验证一次.所以为了保证系统安全,就需要验证用户否处于登录状态.

  1. 服务端不需要保存传统会话信息,没有跨域传输问题,减小服务器开销.
  2. jwt构成简单,占用很少的字节,便于传输.
  3. json格式通用,不同语言之间都可以使用.

安装JWT

composer require firebase/php-jwt

修改源码

  咱也是被逼的,一使用就报一堆的错误.返回格式的问题.修改vendor/firebase/php-jwt/src/JWT.phpdecode方法

public static function decode($jwt, $key, array $allowed_algs = array())
{
    $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;

    if (empty($key)) {
        return 'Key may not be empty';
    }
    $tks = explode('.', $jwt);
    if (count($tks) != 3) {
        return 'Wrong number of segments';
    }
    list($headb64, $bodyb64, $cryptob64) = $tks;
    if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
        return 'Invalid header encoding';
    }
    if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
        return 'Invalid claims encoding';
    }
    if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
        return 'Invalid signature encoding';
    }
    if (empty($header->alg)) {
        return 'Empty algorithm';
    }
    if (empty(static::$supported_algs[$header->alg])) {
        return 'Algorithm not supported';
    }
    if (!in_array($header->alg, $allowed_algs)) {
        return 'Algorithm not allowe';
    }
    if (is_array($key) || $key instanceof \ArrayAccess) {
        if (isset($header->kid)) {
            if (!isset($key[$header->kid])) {
                return '"kid" invalid, unable to lookup correct key';
            }
            $key = $key[$header->kid];
        } else {
            return '"kid" empty, unable to lookup correct key';
        }
    }

    // Check the signature
    if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
        return 'Signature verification failed';
    }

    // Check if the nbf if it is defined. This is the time that the
    // token can actually be used. If it's not yet that time, abort.
    if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
        return 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf);
    }

    // Check that this token has been created before 'now'. This prevents
    // using tokens that have been created for later use (and haven't
    // correctly used the nbf claim).
    if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
        return 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat);
    }

    // Check if this token has expired.
    if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
        $data['error'] = 'expired';
        return $data;
    }

    return $payload;
}

  自行测试哈~我改完后就万事大吉啦.需要将用户的token信息存储到浏览器中,有个很好用的cookie插件.

Last modification:March 18th, 2020 at 02:41 pm