失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > tp5使用workerman实现微信扫码登录 微信公众号关注登录

tp5使用workerman实现微信扫码登录 微信公众号关注登录

时间:2019-05-16 13:30:19

相关推荐

tp5使用workerman实现微信扫码登录 微信公众号关注登录

基于公众号实现微信扫码登录,扫码登录同时关注公众号

引言

本篇介绍的二维码登录不是微信开发平台的二维码登录,而是利用微信公众号临时二维码扫码事件关注公众号进行登录注册,登录的同时关注了公众号,一举两得

浏览器判断扫码状态有两种方式,

第一种是ajax每隔一秒进行轮询,如果用户扫码了则后台给个成功状态

第二种是进入页面后链接websocket等待服务器主动通知,

优缺点分析:

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。

优点:后端程序编写比较容易。

缺点:请求中有大半是无用,浪费带宽和服务器资源。

websocket

优点:在无消息的情况下不会频繁的请求,耗费资源小。

缺点:服务器报纸连接会消耗资源

视频教程

/provider/480000002210444/index.htm?share=2&shareId=480000002210444

简单流程

后台监听websocket浏览器请求登录页--后台生成微信零时二维码(带参数uid)--浏览器显示二维码并连接websocket---用户扫码微信服务器通知业务服务器服务器获取到用户信息进行注册并生成token通过websocket通知浏览器登陆成功浏览器携带token跳转到登陆成功页面

实现

1.后台开启websocket

这里以tp5集成的workman为例进行设置,跟自己下载的基本相同,其他框架或自己安装的请参考workman官方文档

1.安装workerman

tp5文档:/manual/thinkphp5_1/354134

composer require topthink/think-worker=2.0.*

2.配置 config/worker_server.php

因为tp5配置方式不支持自定义函数(或许是我没找到),这里使用自定义类作为Worker服务入口文件类,在配置文件里填写服务类名即可开启,

// 扩展自身需要的配置'protocol' => 'websocket', // 协议 支持 tcp udp unix http websocket text'host' => '0.0.0.0', // 监听地址'port' => 2345, // 监听端口'socket' => '', // 完整监听地址'context' => [], // socket 上下文选项'worker_class' => 'app\http\Worker', // 自定义Workerman服务类名 支持数组定义多个服务// 支持workerman的所有配置参数'name' => 'thinkphp','count'=> 4,'daemonize'=> false,'pidFile' => Env::get('runtime_path') . 'worker.pid',

Worker.php

ps:这里把浏览器第一次发来的信息作为uid保存,为了后面主动给浏览器发送消息,

必须设置心跳,客户端每隔一定时间向服务器发送消息,不然超过超时时间服务器将断开连接,避免客户端异常时浪费资源

$inner_text_worker内部服务是为了后面扫码成功后接收通知,然后向特定客户端发送消息用

详细请参考:/question/508

<?phpnamespace app\http;use think\worker\Server;use Workerman\Lib\Timer;use Workerman\Worker as W;class Worker extends Server{protected $socket = 'websocket://0.0.0.0:2345';/*** 每个进程启动* @param $worker*/public function onWorkerStart($worker){// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符$inner_text_worker = new W('Text://0.0.0.0:5678');$inner_text_worker->onMessage = function($connection, $buffer){// $data数组格式,里面有uid,表示向那个uid的页面推送数据$data = json_decode($buffer, true);$uid = $data['uid'];// 通过workerman,向uid的页面推送数据$ret = $this->sendMessageByUid($uid, $buffer);if ($ret){$msg['error'] = 0;$msg['msg'] = 'ok';}else{$msg['error'] = 1;$msg['msg'] = '异常';}$msg = json_encode($msg);// 返回推送结果$connection->send($msg);};$inner_text_worker->listen();// 心跳间隔55秒define('HEARTBEAT_TIME', 55);Timer::add(1, function()use($worker){$time_now = time();foreach($worker->connections as $connection) {// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间if (empty($connection->lastMessageTime)) {$connection->lastMessageTime = $time_now;continue;}// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {$connection->close();}}});}public function onMessage($connection,$data){global $worker;// 判断当前客户端是否已经验证,即是否设置了uidif(!isset($connection->uid)){// 没验证的话把第一个包当做uid(这里为了方便演示,没做真正的验证)$connection->uid = $data;/* 保存uid到connection的映射,这样可以方便的通过uid查找connection,* 实现针对特定uid推送数据*/$worker->uidConnections[$connection->uid] = $connection;//return $connection->send('login success, your uid is ' . $connection->uid);}// 给connection临时设置一个lastMessageTime属性,用来记录上次收到消息的时间$connection->lastMessageTime = time();//$connection->send('receive success');echo $data;echo "\n";}public function onConnect($connection){}/*** 当连接断开时触发的回调函数* @param $connection*/public function onClose($connection){global $worker;if(isset($connection->uid)){// 连接断开时删除映射unset($worker->uidConnections[$connection->uid]);}}/*** 当客户端的连接上发生错误时触发* @param $connection* @param $code* @param $msg*/public function onError($connection, $code, $msg){echo "error $code $msg\n";}// 针对uid推送数据public function sendMessageByUid($uid, $message){global $worker;if(isset($worker->uidConnections[$uid])){$connection = $worker->uidConnections[$uid];$connection->send($message);return true;}return false;}}

启动服务器 如下图

php think worker:server

2.生成微信临时二维码 (这里使用easywechat,详细请看我其他相关博文)

生成唯一uid,并把uid当做二维码的场景值生成二维码

public function index(){$uid = make_uid();//$ticket = Cache::store('redis')->get('login_ticket');$ticket = $this->loginCode($uid);Cache::store('redis')->set('login'.$uid,1,600);$data = array('uid' => $uid,'ticket' => $ticket);return view('index',$data);}public function loginCode($uid){$config = ['app_id' => Option::get('app_id')->option_value,'secret' => Option::get('app_secret')->option_value,'token' => Option::get('app_token')->option_value,'response_type' => 'array','log' => ['level' => 'debug','file' => Env::get('runtime_path').'/wechat.log',],'oauth' => ['scopes' => ['snsapi_userinfo'],'callback' => '/index/login/oauth_callback',],];$app = Factory::officialAccount($config);$result = $app->qrcode->temporary($uid, 600);$url = $app->qrcode->url($result['ticket']);return $url;}/*** Notes:生成UID* @auther: xxf* Date: /7/17* Time: 17:28* @return string*/function make_uid(){@date_default_timezone_set("PRC");//号码主体(YYYYMMDDHHIISSNNNNNNNN)$order_id_main = date('YmdHis') . rand(10000000,99999999);$order_id_len = strlen($order_id_main);$order_id_sum = 0;for($i=0; $i<$order_id_len; $i++){$order_id_sum += (int)(substr($order_id_main,$i,1));}//唯一号码(YYYYMMDDHHIISSNNNNNNNNCC)$uid = $order_id_main . str_pad((100 - $order_id_sum % 100) % 100,2,'0',STR_PAD_LEFT);return $uid;}

3.浏览器显示二维码并向后台发送uid

前端页面index.html

ps:每隔30秒向服务器发送一条数据,避免超时,

onmessage里,后台返回token说明登录成功,进行跳转

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" type="text/css" href="__CSS__/common.css"><link rel="stylesheet" type="text/css" href="__CSS__/weLogin.css" /><title>微信登录</title></head><body><div class="login block"><div class="we-login"><p class="login-title">微信登录</p><div class="login-img"><img src="{$ticket}" /></div><div class="login-desc"><p><span>扫码</span><span>></span><span>关注</span><span>></span><span>登录</span></p></div></div></div><script>var wsServer = 'ws://127.0.0.1:2345';var websocket = new WebSocket(wsServer);websocket.onopen = function (evt) {console.log("Connected to WebSocket server.");//ws.send("Hello WebSockets!");websocket.send("{$uid}");//设置心跳,避免服务器断开setInterval(function () {websocket.send("{$uid}");}, 30000)};websocket.onclose = function (evt) {console.log("Disconnected");};websocket.onmessage = function (evt) {console.log('Retrieved data from server: ' + evt.data);var res = JSON.parse(evt.data);if(res.token !== '' && res.token !== undefined){window.location='/index/login/login?token='+res.token;}};websocket.onerror = function (evt, e) {console.log('Error occured: ' + evt.data);};</script></body></html>

4.用户扫码,后台进行注册与通知

微信消息处理请看:/flysnownet/article/details/90239582

<?phpnamespace app\api\controller;use app\common\model\Option;use EasyWeChat\Kernel\Messages\News;use EasyWeChat\Kernel\Messages\NewsItem;use think\Controller;use think\facade\Env;use think\Request;use EasyWeChat\Factory;use think\facade\Session;use message\MessageHandler;/**微信消息处理* Class Message* @package app\api\controller*/class Message extends Controller{public function __construct(Request $request){global $_W;$_W['config'] = ['app_id' => Option::get('app_id')->option_value,'secret' => Option::get('app_secret')->option_value,'token' => Option::get('app_token')->option_value,'response_type' => 'array','log' => ['level' => 'debug','file' => Env::get('runtime_path').'/wechat.log',],'oauth' => ['scopes' => ['snsapi_userinfo'],'callback' => '/index/login/oauth_callback',],];//微信首次接入验证if (!empty($_GET['echostr']) && $this->checkSignature($_W['config']['token'])) {header('content-type:text');echo $_GET['echostr'];exit;}}public function index(Request $request){global $_W;$app = Factory::officialAccount($_W['config']);$app->server->push(function ($message) {// $message['FromUserName'] // 用户的 openid// $message['MsgType'] // 消息类型:event, text....$handler = new MessageHandler($message);switch ($message['MsgType']) {case 'event'://return '收到事件消息';return $handler->eventHandler($message['FromUserName']);break;case 'text'://return '收到文字消息';return $handler->textHandler($message['FromUserName']);break;case 'image':return '收到图片消息';break;case 'voice':return '收到语音消息';break;case 'video':return '收到视频消息';break;case 'location'://return '收到坐标消息';return $handler->test();break;case 'link':return '收到链接消息';break;case 'file':return '收到文件消息';// ... 其它消息default:return '收到其它消息';break;}});$response = $app->server->serve();$response->send();//return $response;}/** 接入验签*/private function checkSignature($token){$signature = $_GET["signature"];$timestamp = $_GET["timestamp"];$nonce = $_GET["nonce"];$tmpArr = array($token, $timestamp, $nonce);sort($tmpArr);$tmpStr = implode($tmpArr);$tmpStr = sha1($tmpStr);if ($tmpStr == $signature) {return true;} else {return false;}}}

<?phpnamespace message;use app\common\model\Users;use EasyWeChat\Kernel\Messages\Link;use EasyWeChat\Kernel\Messages\Message;use EasyWeChat\Kernel\Messages\Text;use EasyWeChat\Kernel\Messages\News;use EasyWeChat\Kernel\Messages\NewsItem;use think\Exception;use think\facade\Cache;use think\facade\Log;use EasyWeChat\Factory;use Token\Token;/**微信消息* Class MessageHandler* @package message*/class MessageHandler{/** 消息对象*/private $message;private $user_model;public function __construct($message){$this->message = $message;$this->user_model = new Users();}/** 事件响应函数*/public function eventHandler(){// $message['FromUserName'] // 用户的 openid// $message['MsgType'] // 消息类型:event, text....global $_W;switch ($this->message['Event']) {//关注事件case 'subscribe':if (!empty($this->message['EventKey'])) {$uid = substr($this->message['EventKey'],8);$res = $this->loginEvent($uid);return $res;}return '欢迎关注';break;//取消关注事件case 'unsubscribe':return $this->unSubscribe();break;//点击事件case 'CLICK':return '点击';break;//扫描事件case 'SCAN':$res = $this->loginEvent($this->message['EventKey']);return $res;//return '取关';break;default:return '收到其它消息';break;}}public function LoginEvent($uid){global $_W;//注册$openid = $this->message['FromUserName'];$user_id = $this->addMember($openid);$jwtToken = new Token();$tokenData = array('user_id' => $user_id,);$token = $jwtToken->createToken($tokenData, 86400)['token'];$time_out = Cache::store('redis')->get('login'.$uid);if (empty($time_out))return "二维码过期,请重新登陆";$data = array('uid' => $uid,'token' => $token);$res = $this->sendSocket($data);if($res)return "登录成功";elsereturn '登录异常';}private function addMember($openid){global $_W;$user_info = $this->user_model->getInfoByOpenId($openid);if (empty($user_info)){$app = Factory::officialAccount($_W['config']);$user_detail = $app->user->get($openid);$data = array('nickname' => $user_detail['nickname'] ?? '','openid' => $openid,'gender' => $user_detail['sex'] ?? 0,'avatar' => $user_detail['headimgurl'] ?? '',);$user_id = $this->user_model->addUser($data);}else{$user_id = $user_info->id;}return $user_id;}/*** Notes:取消关注事件* Date: /6/19* Time: 14:11* @return bool*/private function unSubscribe(){global $_W;$member_model = new Members();if ($_W['user']['id'] > 0)$member_model->unSubscribe($_W['user']['id']);return true;}public function sendSocket($data){try{// 建立socket连接到内部推送端口$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);// 推送的数据,包含uid字段,表示是给这个uid推送//$data = array('uid'=>'07181703404867264713', 'percent'=>'88%');// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符fwrite($client, json_encode($data)."\n");// 读取推送结果$res = fread($client, 8192);fclose($client);$res = json_decode($res,true);if($res['error'] == 0){return true;}else{return false;}}catch (\Exception $e){return $e->getMessage();}}}

ps:

sendSocket() :注册成功后给内部服务推送消息,告知给$uid的客户端发送token,workerman收到推送后发送消息给浏览器

5.浏览器收到token,进行跳转

6.登陆成功

public function login(Request $request){$token = $request->param('token', 0);if (!empty($token)) {$jwtToken = new Token();$checkToken = $jwtToken->checkToken($token);$data = (array)$checkToken['data']['data'];$uid = $data['uid'] ?? 0;$user_id = $data['user_id'] ?? 0;Session::set('user_id', $user_id);$this->error('登陆成功', 'index/index/index');}}

如果觉得《tp5使用workerman实现微信扫码登录 微信公众号关注登录》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。