目录

需求来源

实现思路

1、进入登录页面,生成微信公众号的临时二维码;

2、用户通过微信扫一扫二维码;

3、登录页面定时查询扫码结果;

代码实现(基于Laravel框架前后端混合)

HTML

PHP-路由文件

PHP-控制器

PHP-模型

数据表(请根据实际业务定义表结构)

感谢阅读,欢迎交流


需求来源

业务系统的PC端增加微信二维码扫码登录功能

实现思路

1、进入登录页面,生成微信公众号的临时二维码;

传入二维码的场景值(开发者接收微信服务器推送的数据包用以区分业务场景)、过期时间(用户无操作时多久刷新一次二维码)

2、用户通过微信扫一扫二维码;

用户扫描二维码,微信服务器推送扫描事件的xml数据包给开发者服务器,开发者通过xml数据包处理用户的扫码登录逻辑,更新用户成功登录的标识;

3、登录页面定时查询扫码结果;

定义轮询方法,通过生成二维码的场景值定时查询用户扫码的结果,成功则处理登录成功的逻辑,反之继续轮询,本文自动更新二维码(可不做自动刷新二维码,二维码到期时停止轮询);

代码实现(基于Laravel框架前后端混合)

HTML

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><script src="{{asset('vendor/jquery-v2.2.4/jquery.min.js')}}"></script><title>{{$title}}</title><style>body,html,#container {width: 100%;height: 100%;margin: 0;}#container {margin: 5% auto;display: flex;align-items: center;flex-direction: column;}</style><link rel="stylesheet" href="{{asset('vendor/lib/layui/css/layui.css')}}">
</head>
<body>
<div id="container">@include('plugins.izi-modal')@include('plugins.loading')@if(!\Session::get('user'))<p>打开微信扫一扫</p><div style="width: 200px;height:200px;"><img id="qrcode" src="" alt="" style="width:100%;height:100%;"></div>@else<p>用户已登录</p><p><button onclick="wxLogout()">退出登录</button></p>@endif
</div>
</body>
</html>
<script src="{{asset('vendor/lib/layui/layui.js')}}"></script>
<script src="{{asset('vendor/system-js/tool.js')}}"></script>
<script type="text/javascript">layui.use(['layer'], function () {let layer = layui.layerlet scene = "",expireTime = 0;/* todo 创建二维码 */createQrcode()function createQrcode(){tool.getData("{{url('wechat/func/login/create_login_qrcode')}}", {}, (res) => {if (res.code == 200) {$('#qrcode').attr('src', res.data.url)scene = res.data.sceneexpireTime = res.data.expireTime}})}/* todo 轮询调用接口查询用户是否已扫码完成 */checkLoginStatus()function checkLoginStatus(){let timeout = setTimeout(checkLoginStatus, 5*1000)if (!scene || expireTime === 0) {return false}// 二维码过期重新获取if (expireTime < tool.get_timestamp()) {createQrcode()return false}let data = {eventKey: scene,expireTime: expireTime}tool.getData("{{url('wechat/func/login/check_login_status')}}", data, (res) => {// 设置的二维码已过期,重新请求创建if (res.code == 201) {createQrcode()}// 用户已扫码登录成功,关闭延时器if (res.code == 202) {clearTimeout(timeout)}// 查询扫码登录状态,成功则自刷新页面,根据实际业务处理后续if (res.code == 200 && res.data.result == 1) {alert("扫码登录成功")window.location.reload()}})}})/* todo 退出微信扫码登录状态 */function wxLogout() {if (confirm("请确定是否继续退出登录?")) {tool.getData("{{url('wechat/func/login/wx_logout')}}", {}, (res) => {if (res.code == 200) {window.location.reload()}})}}
</script>

PHP-路由文件

<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
Route::group(['prefix' => 'wechat/'], function (Router $router) {// 接收微信推送信息,完成验证消息真实性$router->get('receive_push', "WechatController@receivePush");// 接收微信推送信息,完成接收普通消息并回复$router->post('receive_push', "WechatController@receivePush");// 对话服务-基础支持-获取access_token$router->get('get_access_token', 'WechatController@getAccessToken');// 微信公众号集成功能$router->group(['prefix' => '/func/'], function(Router $router) {// 微信公众号集成功能--用户扫码登录$router->group(['prefix' => 'login/'], function(Router $router) {// 微信公众号集成功能--用户扫码登录--微信登录页面$router->get('wx_login_view', 'WechatController@wxLoginView');// 微信公众号集成功能--用户扫码登录--创建登录二维码$router->get('create_login_qrcode', 'WechatController@createLoginQrcode');// 微信公众号集成功能--用户扫码登录--查询扫描二维码状态$router->get('check_login_status', 'WechatController@checkLoginStatus');// 微信公众号集成功能--用户扫码登录--微信退出登录$router->get('wx_logout', 'WechatController@wxLogout');});});
});

PHP-控制器

<?php
namespace App\Http\Controllers\Wechat;use App\Enums\WechatEnum;
use App\Models\WechatUser;
use App\Tools\Helper;
use Illuminate\Http\Request;class WechatController extends BaseController
{// todo 接收微信推送--明文模式public function receivePush(Request $request){file_log("进入访问" . $request->getUri(), "wechat");try {switch ($_SERVER["REQUEST_METHOD"]) {case "GET":file_log("验证消息真实性" . $request->getUri(), "wechat");$params = $request->all();$params['token'] = config("wechat_develop.Token");// 校验签名是否正确$isMatch = $this->verifySignature($params);if (!$isMatch) {exit(json_encode(["errcode" => -40001, "errmsg" => "signature sha1 error"], JSON_UNESCAPED_UNICODE));}echo $params["echostr"];exit;break;case "POST":file_log("接收公众号发来的信息" . $request->getUri(), "wechat");// 接收微信公众号传送的xml信息包$this->postXml = isset($GLOBALS["HTTP_RAW_POST_DATA"]) ? $GLOBALS["HTTP_RAW_POST_DATA"] : file_get_contents("php://input");file_log("公众号发来的xml数据包" . $this->postXml, "wechat");// xml转换成array$postArray = json_decode(json_encode(simplexml_load_string($this->postXml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);// 按照实际业务处理不同类型的信息,并返回xml包$this->receive = $postArray;@ob_clean();echo $this->reply();exit;break;default:file_log("【非验证微信服务器信息】或【非微信服务器发送的信息】" . $request->getUri(), "wechat");exit;}} catch (\Exception $e) {exception_file_log($e, 'wechat');exit($e->getMessage() . $e->getTraceAsString());}}private function verifySignature($getParam){$tmpArr = array($getParam["token"], $getParam["timestamp"], $getParam["nonce"]);sort($tmpArr, SORT_STRING);$tmpStr = implode($tmpArr);$tmpStr = sha1($tmpStr);return $tmpStr == $getParam['signature'];}private function reply(){$this->message = ["MsgType" => $this->receive["MsgType"],"CreateTime" => time(),"ToUserName" => $this->receive["FromUserName"],"FromUserName" => $this->receive["ToUserName"],];switch ($this->receive["MsgType"]) {case "event":$this->event();break;default:return $this->postXml;break;}$replyXml = self::arr2xml($this->message);file_log("开发者发送的信息:" . $replyXml, "wechat");return $replyXml;}public static function arr2xml($data){return "<xml>" . self::_arr2xml($data) . "</xml>";}private static function _arr2xml($data, $xmlContent = ''){foreach ($data as $key => $val) {is_numeric($key) && $key = 'item';$xmlContent .= "<{$key}>";if (is_array($val) || is_object($val)) {$xmlContent .= self::_arr2xml($val);} elseif (is_string($val)) {$xmlContent .= '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $val) . ']]>';} else {$xmlContent .= $val;}$xmlContent .= "</{$key}>";}return $xmlContent;} # event类型private function event(){$this->message["MsgType"] = "text";$this->message["Content"] = "遇到未知推送事件";file_log("推送事件名称:" . strtolower($this->receive["Event"]), "wechat");switch (strtolower($this->receive["Event"])) {case "subscribe":$this->message["MsgType"] = "text";$this->message["Content"] = "关注公众号触发,暂无做其他处理";$this->subscribe();break;case "scan":$this->message["MsgType"] = "text";$this->message["Content"] = "扫码二维码触发,二维码附带的场景值:{$this->receive["EventKey"]},Ticket:{$this->receive["Ticket"]}";$this->scan();break;default:break;}WechatPushRecord::create($this->message);}# 关注公众号逻辑private function subscribe(){// 判断数据库是否已存在用户--实际业务if (!$user = WechatUser::get_one(["where" => [["openid", '=', $this->message['ToUserName']]]])) {// 拉取用户信息并存库$userInfo = $this->user_info(['openid' => $this->message['ToUserName'], 'lang' => 'zh_CN']);file_log(json_encode($userInfo,JSON_UNESCAPED_UNICODE),'wechat');check_result_issuccess($user = WechatUser::saveSubscribeUser($userInfo), "{$this->message['ToUserName']}保存微信用户信息失败", false, WechatEnum::Exception);} else {// 更新用户信息--暂时只在保存时提交数据,不做更新用户信息操作}// 判断是否存在事件键值,格式:qrscene_login-xxxxxxxxif (isset($this->receive['EventKey']) && $this->receive['EventKey']) {$eventKey = explode('_', $this->receive['EventKey']);$scene = $eventKey[0];switch ($scene) {case 'qrscene':// 扫码登录if (isset($this->receive['Ticket']) && $this->receive['Ticket']) {// 更新当前用户的扫码登录事件键值,表示用户已扫码登录成功WechatUser::updateLoginStatus($this->message['ToUserName'], $eventKey[1]);$this->message['Content'] = "用户扫码登录成功";}break;}}}# 扫描二维码逻辑private function scan(){// 定制二维码的场景值时,通过符号“-”进行分割,如login-123456,login为二维码的分类场景值if (isset($this->receive['EventKey']) && $this->receive['EventKey']) {$scene = explode('-', $this->receive['EventKey'])[0];switch ($scene) {case 'login': // 扫码登录// 更新当前用户的扫码登录事件键值,表示用户已扫码登录成功WechatUser::updateLoginStatus($this->message['ToUserName'],$this->receive['EventKey']);$this->message['Content'] = "用户扫码登录成功";break;default:$this->message['Content'] = "当前二维码场景未定义,请尽快接入";break;}}}/*** todo 微信公众号集成功能--用户扫码登录--微信登录页面*/public function wxLoginView(){$user = \Session::get('user');$title = "用户扫码登录";return view("wechat.wx-login.wxLoginView", compact("title", "user"));}/*** todo 微信公众号集成功能--用户扫码登录--创建登录二维码*/public function createLoginQrcode(Request $request){try {$eventKey = "login-". uniqid('');$expireSeconds = 60 * 20;$ticketAndUrl = $this->qrcode_create(["expire_seconds" => $expireSeconds,"scene" => $eventKey // 可选择:字符串、整型]);if (isset($ticketAndUrl['ticket'])) {$qrcodeUrl = $this->show_qrcode($ticketAndUrl);}} catch (\Exception $e) {exception_file_log($e, "wechat");exit("【createLoginQrcode】创建登录二维码失败,请到wechat日志文件查看详情");}return return_info(200, '获取二维码链接成功', ['url' => $qrcodeUrl, 'scene' => $eventKey, 'expireTime' => time() + $expireSeconds]);}/*** todo 微信公众号集成功能--用户扫码登录--查询扫描二维码状态*/public function checkLoginStatus(Request $request){if (\Session::get('user')) return return_info(202, '用户已登录');try {if (is_null($eventKey = $request->eventKey))  return return_info(500, '请先传入必要参数-eventKey');if (is_null($expireTime = $request->expireTime))  return return_info(500, '请先传入必要参数-expireTime');if ($expireTime < time()) return return_info(201, '二维码已过期');// 查询扫码登录的事件键值是否已更新到用户的信息中$checkLoginStatus = WechatUser::checkLoginStatus($eventKey);// 处理扫码成功的逻辑if ($checkLoginStatus) {# 代码...\Session::put('user', $eventKey);\Session::save();}} catch (\Exception $e) {exception_file_log($e, 'wechat');exit("【checkLoginStatus】查询扫描二维码状态失败,请到wechat日志文件查看详情");}return return_info(200, '查询扫描状态成功', ['result' => $checkLoginStatus ? 1 : 0]);}/*** todo 微信公众号集成功能--用户扫码登录--微信退出登录*/public function wxLogout(Request $request){try {if (\Session::get('user')) {\Session::put('user', null);\Session::save();}} catch (\Exception $e) {exception_file_log($e, 'wechat');exit("【wxLogout】微信退出登录失败,请到wechat日志文件查看详情");}return return_info(200, '微信退出登录成功');}/*** todo 创建二维码ticket*/public function qrcode_create($data){$url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$this->getAccessToken()}";// 处理action_infoif (is_integer($data["scene"])) {$data["action_info"]["scene"] = ["scene_id" => $data["scene"]];} else {$data["action_info"]["scene"] = ["scene_str" => $data["scene"]];}// 处理action_nameif (isset($data["expire_seconds"]) && $data["expire_seconds"] > 0) {$data["action_name"] = is_integer($data["scene"]) ? "QR_SCENE" : "QR_STR_SCENE";} else {$data["action_name"] = is_integer($data["scene"]) ? "QR_LIMIT_SCENE" : "QR_LIMIT_STR_SCENE";}return $this->post_http_request($url, $data);}/*** todo 通过ticket换取二维码*/public function show_qrcode($data){$ticket = urlencode($data["ticket"]);return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={$ticket}";}public function post_http_request($url, $postData){$resultArr = json_decode($this->http_url($url, json_encode($postData, JSON_UNESCAPED_UNICODE)), true);if (isset($resultArr["errcode"]) && $resultArr["errcode"] != 0) {file_log($resultArr, $this->logFile ?: "HttpRequest");throw new \Exception($resultArr['errmsg']);}return $resultArr;}public function http_url($url, $data = null){$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);if (!empty($data)) {curl_setopt($ch, CURLOPT_POST, 1);curl_setopt($ch, CURLOPT_POSTFIELDS, $data);}$res = curl_exec($ch);if (curl_errno($ch)) {throw new \Exception("error:" . curl_error($ch));}curl_close($ch);return $res;}
}

PHP-模型

<?php
namespace App\Models;
class WechatUser extends Model
{protected $table = 'wechat_users';const Unknown = 0; // todo 未知const Male = 1; // todo 男性const Female = 2; // todo 女性/*** todo 保存微信用户*/public static function saveUser($userInfo){if (isset($userInfo["privilege"])){unset($userInfo["privilege"]);}$user = self::insert(new self(),$userInfo);return $user ? $user->toArray() : false;}/*** todo 保存关注的用户数据*/public static function saveSubscribeUser($userInfo){$insertInfo = [];$allowFiled = ["subscribe", "openid", "nickname", "sex", "language", "city", "province", "country", "headimgurl", "subscribe_time", "remark", "groupid", "tagid_list", "subscribe_scene", "qr_scene", "qr_scene_str"];foreach ($allowFiled as $field) {if (isset($userInfo[$field])) {if ($field == "tagid_list") {$userInfo[$field] = json_encode($userInfo[$field],JSON_UNESCAPED_UNICODE);}$insertInfo[$field] = $userInfo[$field];}} unset($field);$user = self::insert(new self(), $insertInfo);return $user ? $user->toArray() : false;}/*** todo 查询用户的扫码登录状态信息*/public static function checkLoginStatus($eventKey,$field = null){$param = [];$param['where'] = [['wechat_scan_event_key', '=', $eventKey],['wechat_scan_event_key_expire_time', '>', time()]];if ($field) $param['field'] = $field;return self::get_one($param);}/*** todo 更新用户的扫码登录事件键值*/public static function updateLoginStatus($openId, $eventKey, $expireTime = 3600){$where = [['openid', '=', $openId]];$data = ['wechat_scan_event_key_expire_time' => time() + $expireTime, 'wechat_scan_event_key' => $eventKey];return self::update_by_where(['where' => $where, 'data' => $data]);}
}

数据表(请根据实际业务定义表结构)

CREATE TABLE `wechat_users` (`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,`openid` varchar(32) DEFAULT '' COMMENT '公众号唯一标识',`subscribe` varchar(1) DEFAULT '' COMMENT '是否关注 1-是',`nickname` varchar(50) DEFAULT '' COMMENT '关注用户昵称',`sex` tinyint(1) unsigned DEFAULT '0' COMMENT '关注用户性别 0:未知 1:男 2:女',`province` varchar(50) DEFAULT '' COMMENT '关注用户省份',`city` varchar(50) DEFAULT '' COMMENT '关注用户城市',`country` varchar(50) DEFAULT '' COMMENT '关注用户国家',`headimgurl` varchar(255) DEFAULT '' COMMENT '关注用户头像',`unionid` varchar(32) DEFAULT '' COMMENT '关注用户开放平台唯一标识',`language` varchar(20) DEFAULT '' COMMENT '关注用户使用语言',`subscribe_time` int(10) unsigned DEFAULT '0' COMMENT '关注时间',`remark` varchar(100) DEFAULT '' COMMENT '公众号运营者对粉丝的备注',`groupid` varchar(10) DEFAULT '' COMMENT '用户所在的分组ID',`tagid_list` varchar(255) DEFAULT '' COMMENT '用户被打上的标签ID列表',`subscribe_scene` varchar(20) DEFAULT '' COMMENT '用户关注的渠道来源',`qr_scene` varchar(20) DEFAULT '' COMMENT '二维码扫码场景(开发者自定义)',`qr_scene_str` varchar(50) DEFAULT '' COMMENT '二维码扫码场景描述(开发者自定义)',`wechat_scan_event_key` varchar(50) DEFAULT '' COMMENT '用户扫码登录的事件键值',`wechat_scan_event_key_expire_time` int(10) unsigned DEFAULT '0' COMMENT '用户扫码登录的事件键值过期时间',`created_at` datetime DEFAULT NULL COMMENT '创建时间',`updated_at` datetime DEFAULT NULL COMMENT '更新时间',PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='微信公众号用户表';

感谢阅读,欢迎交流

【微信公众号】微信集成功能--扫描二维码完成用户登录操作相关推荐

  1. 微信公众号带场景参数的二维码生成与使用?

    2019独角兽企业重金招聘Python工程师标准>>> 微信公众号推广时,用户通过扫码关注公众号,统计用户是通过哪个带场景二维码进行关注的,并对用户自动分组打标签备注.直接使用微号帮 ...

  2. JAVA 实现微信公众号的生成带参数二维码和扫码后被动回复消息

    公司新需求实现生成微信公众号带参数二维码和对应扫码后回复相应信息的功能. 1.实现带参数二维码功能 public class QrTest {private static Logger logger ...

  3. wechat-0050,微信公众号,带参数的二维码获取与扫码事件推送

    demo:https://github.com/wenrongyao/wechat-demo 摘要:这篇帖子讲述如何获取带参数的二维码,以及扫码带参数的二维码后的事件接收. 微信开发者文档:https ...

  4. 微信公众号-添加参数获取场景二维码路径

    1.微信公众平台测试账号 我用内网穿透把本地的项目映射到外网,主要为了方便测试 在本地项目映射成功后可以扫一下"测试号二维码 "看是否正常进入到后台 2,添加参数获取场景二维码路径 ...

  5. 微信公众号生成带参数的二维码

    官网功能介绍:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542 1.获取access_token:根据公众号的 ...

  6. 微信扫一扫功能扫描二维码调用外部浏览器打开指定页面实现微信中下载APP的功能

    需求分析 分享链接已经成为手机应用一个非常重要的推广传播形式.为了提高转化率,就需要用户不管是在微信内直接打开链接还是扫描二维码都能直接打下载app. 但由于微信对第三方应用管的非常严格,故目前的大环 ...

  7. 公众号文章链接怎么做二维码?在线生成链接二维码的方法

    公众号是现在品牌运营时必备的一个渠道,通过不断地发表公众号推文来提高自己的品牌力,那么如果想要将一篇公众号文章生成二维码(二维码在线制作-二维码生成器-音视频二维码制作工具-机制熊二维码)后分享给他人 ...

  8. 用微信扫一扫功能扫描二维码下载安装APK不能用,不能打开APK下载链接的解决方案

    使用微信推广的用户经常都会遇到推广链接被拦截导致无法下载app的情况,此时用户在微信中打开会提示" 已停止访问该网页 ".这对于使用微信营销的商家来说就很不友好且损失非常大,因为用 ...

  9. 微信订阅号渠道推广带参数二维码如何生成和统计呢?

    2019独角兽企业重金招聘Python工程师标准>>> 微信公众号订阅号渠道推广带参数二维码,公众号功能接口不支持订阅号生成 带参数二维码,只有服务号才可以,订阅号只能通过服务号转接 ...

最新文章

  1. 基于OpenCV的实时面部识别
  2. 自然语言处理(NLP)历史中的6个主要时期你知道吗?
  3. 计算机视觉领域不同的方向:目标识别、目标检测、语义分割等
  4. 七、功能性组件与事件逻辑(IVX 快速开发教程)
  5. JavaScript 判断浏览器类型
  6. Ubuntu 编译安装ffmpeg,mplayer,x264全教程支持VDPAU(高清硬解)
  7. 文本分类之一:语言模型
  8. 三菱plc与西门子plc编程有什么不同?
  9. 房屋租赁管理系统(Java源码+论文)
  10. 2-2 nginx整体结构
  11. 利用Python中的requests+wget批量下载微信页面上的音频
  12. 实习僧-产品体验报告
  13. 教你如何写出高质量的网络推广软文
  14. 深入浅出多目标优化--10分钟多目标优化入门
  15. 在OpenCV里使用光流算法
  16. MySQL 异常错误码使用 及 对照表 DataException
  17. 基于java+SSM+jsp的酒店管理系统(附源码)
  18. JQuery OrgChart
  19. 人大金仓数据库添加FIND_IN_SET函数
  20. 一台服务器能支持多少docker,一台物理机器部署多个docker

热门文章

  1. 帆软堆积柱形图标签显示汇总值,标签写js,树形图系列展示汇总值占比
  2. 【嵌入式--伺服电机】无刷电机FOC驱动基本原理与设计
  3. 数学建模理论自制笔记2:差分方程及其模型
  4. 商城网站建设的具体开发流程有哪些
  5. linux mint 下安装matplotlib
  6. bat 命令返回结果_Windows 用户需要知道的 CMD 常用命令总结
  7. JavaScript-0基础入门
  8. Docker学习笔记7——Docker-Compose(幕布笔记)
  9. 中国历史上最美的十五位女人
  10. 利用 DTMaster 立体编辑 DEM 方法