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

2023-10-12 06:20

本文主要是介绍tp5使用workerman实现微信扫码登录,微信公众号关注登录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

引言

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

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

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

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

优缺点分析:

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。 
优点:后端程序编写比较容易。 
缺点:请求中有大半是无用,浪费带宽和服务器资源。 

websocket

优点:在无消息的情况下不会频繁的请求,耗费资源小。 
缺点:服务器报纸连接会消耗资源

视频教程

https://study.163.com/provider/480000002210444/index.htm?share=2&shareId=480000002210444

简单流程

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

实现

1.后台开启websocket

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

1.安装workerman     

  1. tp5文档:https://www.kancloud.cn/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内部服务是为了后面扫码成功后接收通知,然后向特定客户端发送消息用

详细请参考:https://wenda.workerman.net/question/508

<?php
namespace 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: 2019/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.用户扫码,后台进行注册与通知

微信消息处理请看:https://blog.csdn.net/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: 2019/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'=>'201907181703404867264713', '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实现微信扫码登录,微信公众号关注登录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/193875

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi