引用短信
阿里短信上来就索要钱财,申请还麻烦.又一款不错的第三方短信平台
云之讯
.之前注册过就直接用啦~进入thinkphp
的extend
目录创建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.php
的Task
任务
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
请求时它就把刚刚的资料忘了,于是程序就不知道谁是谁,就要再验证一次.所以为了保证系统安全,就需要验证用户否处于登录状态.
- 服务端不需要保存传统会话信息,没有跨域传输问题,减小服务器开销.
jwt
构成简单,占用很少的字节,便于传输.json
格式通用,不同语言之间都可以使用.
安装JWT
composer require firebase/php-jwt
修改源码
咱也是被逼的,一使用就报一堆的错误.返回格式的问题.修改vendor/firebase/php-jwt/src/JWT.php
的decode
方法
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
插件.