网站调用微信小程序授权登录示例

在这里插入图片描述

前言

晓杰网站增加了多个登录方案包括公众号扫码登录,公众号快捷登录,QQ快捷登录,后面也研究了下小程序扫码登录方案,现在分享给大家。

技术栈

THinkphp5.0+Redis+Mysql

前端代码

index.html

<html>
<head>
    <title>微信小程序扫码授权登录</title>
    <meta name="wechat-enable-text-zoom-em" content="true">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="color-scheme" content="light dark">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0,viewport-fit=cover">
    <link rel="shortcut icon" type="image/x-icon" href="//res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico" reportloaderror>
    <link rel="mask-icon" href="//res.wx.qq.com/a/wx_fed/assets/res/MjliNWVm.svg" color="#4C4C4C" reportloaderror>
    <link rel="apple-touch-icon-precomposed" href="//res.wx.qq.com/a/wx_fed/assets/res/OTE0YTAw.png" reportloaderror>
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="format-detection" content="telephone=no">
    <meta name="referrer" content="origin-when-cross-origin">
    <meta name="referrer" content="strict-origin-when-cross-origin">
    <style>
        *{
            padding: 0;
            margin: 0;
        }
        .title {
            text-align: center;
            margin-top: 50px;
            font-size: 25px;
        }
        #createQrcode {
            border: none;
            padding: 12px;
            background: #07C160;
            color: #fff;
            font-size: 16px;
            border-radius: 10px;
            margin: 30px auto 0;
            display: block;
            cursor: pointer;
            outline: none;
            -webkit-tap-highlight-color:rgba(255,0,0,0);
        }

        #qrcode {
            width: 220px;
            height: 220px;
            margin: 30px auto 0;
            display: none;
        }

        #qrcode img {
            width: 220px;
            height: 220px;
        }

        #status {
            border: none;
            padding: 12px 15px;
            background: #eee;
            color: #666;
            font-size: 18px;
            border-radius: 100px;
            margin: 15 auto 0;
            display: block;
            cursor: pointer;
            outline: none;
            -webkit-tap-highlight-color:rgba(255,0,0,0);
            display: none;
        }
    </style>
</head>

<body>

<!--标题-->
<p class="title">微信小程序扫码授权登录示例</p>

<!--生成按钮-->
<button id="createQrcode" onclick="createQrcode()">生成微信小程序码</button>

<!--小程序码显示区域-->
<div id="qrcode"></div>

<!--状态-->
<button id="status"></button>

<!--scene隐藏域-->
<input type="hidden" id="scene" />

<script>

    // 定义一个全局变量来控制轮询状态
    var pollingInterval;

    // 用于记录轮询次数的变量
    var pollingCount = 0;

    // 创建小程序码
    function createQrcode() {

        var xhr = new XMLHttpRequest();
        xhr.open("GET", "createQrcode", true);

        xhr.onreadystatechange = function () {

            if (xhr.readyState === 4 && xhr.status === 200) {

                // 渲染小程序码
                var response = JSON.parse(xhr.responseText);
                document.getElementById("qrcode").style.display = "block";
                document.getElementById("qrcode").innerHTML = '<img src="data:image/jpg;base64,'+response.qrcode+'" id="miniproQrCode" />';
                document.getElementById("createQrcode").style.display = "none";
                document.getElementById("status").style.display = "block";
                document.getElementById("status").innerHTML = '请使用微信扫码';
                document.getElementById("scene").value = response.scene;

                // 重置轮询次数
                pollingCount = 0;

                // 开始轮询
                startPolling();
            }
        };
        xhr.send();
    }

    // 开始轮询
    // 1500毫秒轮询一次
    function startPolling() {
        var pollingInterval = setInterval(function () {
            pollDatabase(pollingInterval);
        }, 1500);
    }

    // 轮询扫码状态
    function pollDatabase(pollingInterval) {

        var sceneValue = document.getElementById("scene").value;
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "checkScanStatus?scene=" + sceneValue, true);

        xhr.onreadystatechange = function () {

            if (xhr.readyState === 4 && xhr.status === 200) {

                // 获取轮询结果
                var response = JSON.parse(xhr.responseText);
                document.getElementById("status").innerHTML = response.msg;

                // 轮询的信息
                console.log(response.msg)

                // 每次轮询递增计数
                pollingCount++;

                // 204状态码
                if (response.code == 204) {

                    // 修改为已取消的图片
                    document.getElementById("miniproQrCode").src = '__STATIC__/login/isCancel.png';

                    // 停止轮询
                    clearInterval(pollingInterval);
                }

                // 203状态码
                if (response.code == 203) {

                    // 修改为已扫码的图片
                    document.getElementById("miniproQrCode").src = '__STATIC__/login/isScan.png';
                }

                // 200状态码
                if (response.code == 200) {

                    // 修改为登录成功的图片
                    document.getElementById("miniproQrCode").src = '__STATIC__/login/loginSuccess.png';

                    // 登录成功的逻辑
                    // 例如修改DOM或者跳转到Url
                    // 以回调地址为例
                    // 检查URL中是否包含'?callback='
                    if (window.location.href.indexOf('?callback=') !== -1) {
                        var callbackUrl = window.location.href.split('?callback=')[1];

                        // 去掉参数部分
                        callbackUrl = callbackUrl.split('&')[0];

                        if (isValidCallback(callbackUrl)) {

                            // 添加斜杠结尾
                            callbackUrl = addTrailingSlash(callbackUrl);

                            // 跳转到回调地址并传递token
                            location.href = callbackUrl + '?token=' + response.token;

                        } else {

                            // 无需添加斜杠
                            // 跳转到回调地址并传递token
                            location.href = callbackUrl + '?token=' + response.token;
                        }
                    }

                    // 用于验证callback是不是符合格式的域名
                    function isValidCallback(callback) {

                        // 使用正则表达式验证是否是有效的域名或域名+目录
                        var pattern = /^(https?:\/\/)?([a-z\d]([a-z\d-]*[a-z\d])*\.)+[a-z]{2,}(\/\w*\/?)?$/i;
                        return pattern.test(callback);
                    }

                    // 添加/作为结尾
                    function addTrailingSlash(callback) {

                        // 如果字符串不以斜杠结尾,添加斜杠
                        if (!callback.endsWith('/')) {
                            callback += '/';
                        }
                        return callback;
                    }

                    // 停止轮询
                    clearInterval(pollingInterval);

                }else if (pollingCount >= maxPollingCount) {

                    // 修改为小程序码已过期的图片
                    document.getElementById("miniproQrCode").src = '__STATIC__/login/isExpire.png';
                    document.getElementById("status").innerHTML = '小程序码已过期,请刷新';

                    // 停止轮询
                    clearInterval(pollingInterval);
                }
            }
        };
        xhr.send();
    }

    // 设置最大轮询次数
    var maxPollingCount = 60;

</script>

</body>
</html>

后端代码

Login.php

<?php

namespace app\admin\controller;
use app\admin\model\Admin as AdminModel;;
use qqconnect\QC;
use think\cache\driver\Redis;
use think\Controller;
use think\Request;
use think\Session;
use tools\Mobile_Detect as Mobile_DetectModel;
use tools\Tools as ToolsModel;
class Login  extends Common
{
    public function getOpenid()
    {
        isset($_GET['scene']) ? $scene = $_GET['scene'] : exit(json_encode(array('code' => 0, 'msg' => '参数不能为空!'), JSON_UNESCAPED_UNICODE));
        isset($_GET['code']) ? $code = $_GET['code'] : exit(json_encode(array('code' => 0, 'msg' => '参数不能为空!'), JSON_UNESCAPED_UNICODE));
        $wxConfig = new WxConfig();
        $appidStr = $wxConfig::$appid;
        $secretStr = $wxConfig::$secret;
        // 换取openid的API
        $api = "https://api.weixin.qq.com/sns/jscode2session?appid=$appidStr&secret=$secretStr&js_code=$code&grant_type=authorization_code";
        $result = Index::httpGet($api);
        $arr_result = json_decode($result, true);
        if (empty($arr_result['session_key'])) {
            $ret = array(
                'code' => 202,
                'msg' => '授权失败'
            );
        }
        // 解析出openid
        $openid = $arr_result["openid"];
        // 验证scene参数
        $redis = new Redis();
        $redisKey = 'qrcode_'.$scene;
        $checkScene = $redis->get($redisKey);
        if (!empty($checkScene) && $checkScene['status'] == 1) {
            // 如果存在scene
            $ret = array(
                'code' => 200,
                'msg' => '已扫码',
                'openid' => $openid
            );
            $checkScene['status']=2;
            $checkScene['openid']=$openid;
            $redis->set($redisKey,$checkScene,300);
        }else{
            $ret = array(
                'code' => 201,
                'msg' => '小程序码已过期'
            );
        }
        echo json_encode($ret, JSON_UNESCAPED_UNICODE);
    }
    public function loginAuth(){
        header("content-type:application/json");
        isset($_GET['scene'])?$scene = $_GET['scene']:exit(json_encode(array('code'=>202,'msg'=>'授权失败,scene不存在'),JSON_UNESCAPED_UNICODE));
        $redis = new Redis();
        $redisKey = 'qrcode_'.$scene;
        $checkScene = $redis->get($redisKey);
        if(!empty($checkScene)) {
            $checkScene['status']=3;
            $checkScene['authTime']=date('Y-m-d H:i:s');
            $redis->set($redisKey,$checkScene,300);
            // 已授权
            $ret = array(
                'code' => 200,
                'msg' => '已授权'
            );
        }else {

            // scene不存在
            $ret = array(
                'code' => 202,
                'msg' => '授权失败,scene不存在'
            );
        }

        // 返回结果
        echo json_encode($ret, JSON_UNESCAPED_UNICODE);
    }
    public function cancelAuth(){
        header("content-type:application/json");
        isset($_GET['scene'])?$scene = $_GET['scene']:exit(json_encode(array('code'=>202,'msg'=>'取消失败,scene不存在!'),JSON_UNESCAPED_UNICODE));
        $redis = new Redis();
        $redisKey = 'qrcode_'.$scene;
        $cancelAuth = $redis->get($redisKey);
        if(!empty($cancelAuth)) {
            // 更新为取消授权且设置小程序码为过期
            $checkScene['status']=4;
            $redis->set($redisKey,$checkScene,300);
            $ret = array(
                'code' => 200,
                'msg' => '已取消授权'
            );
            $redis->rm($redisKey);
        }else {
            $ret = array(
                'code' => 202,
                'msg' => '取消失败,scene不存在'
            );
        }

        // 返回结果
        echo json_encode($ret, JSON_UNESCAPED_UNICODE);
    }
    public function checkScene(){
        header("Content-type:application/json");
        isset($_GET['scene'])?$scene = $_GET['scene']:exit(json_encode(array('code'=>202,'msg'=>'参数不能为空!'),JSON_UNESCAPED_UNICODE));
        $redis = new Redis();
        $redisKey = 'qrcode_'.$scene;
        $checkScene = $redis->get($redisKey);
        if(!empty($checkScene)){
            $result = array(
                'code' => 200,
                'msg' => '获取成功'
            );
        }else{
            $result = array(
                'code' => 204,
                'msg' => '参数错误'
            );
        }
            echo json_encode($result, JSON_UNESCAPED_UNICODE);
        }
		    public function createQrcode(){
        $accessToken = $this->getAccessToken();
        $url="https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=$accessToken";
        $scene =ToolsModel::getMillisecond().rand(1000000,9999999);
        // 请求参数
        $data = array(
            "page" => "pages/authorize/index", // 小程序扫码页面的路径
            "scene" => $scene,
            "check_path" => false, // 是否验证你的路径是否正确
            "env_version" => "release" // 开发的时候这个参数是develop,小程序审核通过发布上线之后改为release
        );
        $dataReturn = ToolsModel::getCurl($url,json_encode($data));
        $redis = new Redis();
        $redisKey = 'qrcode_'.$scene;

// 向数据库插入一条生成小程序码的记录
        $dataArr = array(
            'scene' => $scene,
            'status' => 1,
            'scene' => $scene,
        );
        $redis->set($redisKey,$dataArr,600);
        $result = array(
            'code' => 200,
            'msg' => '创建成功',
            'scene' => $scene,
            'qrcode' => base64_encode($dataReturn)
        );
        echo json_encode($result, JSON_UNESCAPED_UNICODE);
    }
    private  function getAccessToken()
    {
        $redis= new Redis();
        $AccessTokenKey = 'access_token_'.Config('miniApp.APPID');
        if(!$redis->has($AccessTokenKey)){
            $times = 700;
            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".Config('miniApp.APPID')."&secret=".Config('miniApp.APPSECRET');
            $result =ToolsModel:: https_request($url);
            $jsoninfo = json_decode($result, true);
            $access_token = $jsoninfo['access_token'];
            if ($access_token)
            {
                $redis->set($AccessTokenKey,$access_token,$times);
            }
        }else
        {
            $access_token =  $redis->get($AccessTokenKey);
        }
        return $access_token;

    }

    public function checkScanStatus()
    {
        header("Content-type:application/json");
        isset($_GET['scene']) ? $scene = $_GET['scene'] : exit(json_encode(array('code' => 0, 'msg' => '参数不能为空!'), JSON_UNESCAPED_UNICODE));
        // 查看Scene的状态
        $redis = new Redis();
        $redisKey = 'qrcode_'.$scene;
        $checkScanStatus = $redis->get($redisKey);
        if (!empty($checkScanStatus)) {
            // 扫码状态
            $status = $checkScanStatus['status'];
            // openid
            $openid = $checkScanStatus['openid'];

            if ($status == 1) {

                // 未扫码
                $result = array(
                    'code' => 202,
                    'msg' => '请使用微信扫码'
                );

            } else if ($status == 2) {

                // 已扫码
                $result = array(
                    'code' => 203,
                    'msg' => '已扫码,请点击授权登录'
                );

            } else if ($status == 3 && $openid) {

                // 删除临时文件
                //  unlink('qrcode/' . $Scene . '.png');

                // 登录成功的处理
                // 例如存SESSION
                // 数据库操作等
                // -----------------------------------
                // 在这里编写你的逻辑
                $token = MD5($scene.Config('miniApp.APPSECRET').time());

                // 已登录
                $result = array(
                    'code' => 200,
                    'msg' => '登录成功',
                    'token' => $token
                );
                $redis->rm($redisKey);

            } else if ($status == 4) {

                // 已取消授权
                $result = array(
                    'code' => 204,
                    'msg' => '已取消授权'
                );

                // 删除临时文件
                // unlink('qrcode/' . $Scene . '.png');

            }
        } else {

            // 获取失败
            $result = array(
                'code' => 204,
                'msg' => '该二维码无法登录'
            );
        }
        echo json_encode($result, JSON_UNESCAPED_UNICODE);
    }

    public function index()
    {
        return view();
    }

小程序源码

存储位置 pages下面

file
index.js

// 获取应用实例
const app = getApp()

// 获取服务器域名和目录名
const domain = app.domain.url;
const dirName = app.dirName.dir;

Page({
	data: {
		scanStep: 1
	},

	// 获取扫码结果
	onLoad(options) {
		
		const that = this;
		if(options !== undefined) {
			if(options.scene) {

				// 获取scene
				let scene = decodeURIComponent(options.scene);

				wx.showLoading({
					title: '加载中'
				})

				// 验证scene是否存在
				wx.request({
					url: 'https://' + domain  + '/checkScene/?scene=' + scene,
					header: {
						'content-type': 'application/json'
					},
					success (res) {

						// 输出验证结果
						console.log(res.data)

						// 存在
						if(res.data.code == 200) {

							// 微信登录
							wx.login({
								success (res) {
									if (res.code) {
										wx.request({
											url: 'https://' + domain  + '/getOpenid/?code=' + res.code + '&scene=' + scene,
											header: {
												"content-type": "application/json"
											},
											success (res) {
												
												// 成功获取到Openid
												if(res.data.code == 200) {

													// 切换至授权界面
													that.setData({
														scanStep: 2,
														sceneCode: scene
													})
												}else {

													// 获取失败
													that.setData({
														scanStep: 3,
														loginSuccess: false,
														errorMsg: res.data.msg
													})
												}
											}
										});
									}
								}
							});
						}
						wx.hideLoading();
					}
				})
			}
		}
	},

	// 点击授权登录
	loginAuth() {
		const that = this;
		wx.showNavigationBarLoading();
		wx.request({
			url: 'https://' + domain  + '/loginAuth/?scene=' + that.data.sceneCode,
			header: {
				'content-type': 'application/json'
			},
			success (res) {
				if(res.data.code == 200) {

					// 切换至授权结果
					that.setData({
						scanStep: 3,
						loginSuccess: true
					})

					wx.hideNavigationBarLoading();
				}else{

					that.setData({
						scanStep: 3,
						loginSuccess: false,
						errorMsg: res.data.msg
					})
				}
			}
		})
	},

	// 取消授权
	cancelAuth() {
		const that = this;
		wx.request({
			url: 'https://' + domain  + '/cancelAuth/?scene=' + that.data.sceneCode,
			header: {
				'content-type': 'application/json'
			},
			success (res) {
				that.setData({
					scanStep: 3,
					loginSuccess: false,
					errorMsg: res.data.msg
				})
			}
		})
	}
})

index.json

{
  "usingComponents": {}
}

index.wxml

<view class="container">

<!-- 扫描二维码 -->
<view class="scanQrcode" wx:if="{{scanStep == 1}}">

	<!-- 提醒logo -->
	<view class="tips-logo">
		<image src="../../images/tips.png"></image>
	</view>

	<!-- 扫码提示 -->
	<view class="scan-tips">请使用微信扫一扫</view>
</view>

<!-- 授权登录 -->
<view class="loginAuth" wx:if="{{scanStep == 2}}">
	
	<!-- 头像区域 -->
	<view class="avatar">
		<image src="../../images/warn.png"></image>
	</view>

	<!-- 昵称区域 -->
	<view class="nickname">使用微信授权登录</view>

	<!-- 授权按钮 -->
	<view class="button auth-login" bind:tap="loginAuth">授权登录</view>

	<!-- 取消授权 -->
	<view class="button cancel-login" bind:tap="cancelAuth">取消授权</view>

	<!-- 授权须知 -->
	<!--<view class="auth-know">
		<span>授权登录即同意</span>
		<span class="blue-font">xxx用户服务协议</span>和
		<span class="blue-font">xxx用户隐私协议</span>
		<span>,请阅读以上两项协议。</span>
	</view> -->
</view>

<!-- 登录结果 -->
<view class="loginResult" wx:if="{{scanStep == 3}}">

	<!-- 提醒logo -->
	<view class="tips-logo">
		<image src="../../images/success.png" wx:if="{{loginSuccess}}"></image>
		<image src="../../images/fail.png" wx:else></image>
	</view>

	<!-- 登录结果 -->
	<view class="login-success" wx:if="{{loginSuccess}}">登录成功</view>
	<view class="login-fail" wx:else>{{errorMsg}}</view>
</view>

</view>

index.wxss

.container {
	width: 93%;
	margin: 0 auto;
}

.loginAuth {
	width: 100%;
	margin: 30px auto 0;
	background: #fff;
	border-radius: 15px;
	overflow: hidden;
}
.loginAuth .avatar {
	width: 90px;
	height: 90px;
	margin: 50px auto 0;
}
.loginAuth .avatar image {
	width: 90px;
	height: 90px;
}
.loginAuth .nickname {
	text-align: center;
	color: #999;
	font-size: 33rpx;
	margin-top: 15px;
	margin-bottom: 50px;
}
.loginAuth .button {
	padding: 12px;
	width: 150px;
	margin: 0 auto;
	border-radius: 10px;
	text-align: center;
	font-size: 33rpx;
	font-weight: 400;
	margin-bottom: 10px;
}
.loginAuth .auth-login {
	background: #07c160;
	color: #fff;
}
.loginAuth .cancel-login {
	background: #eee;
	color: #666;
}

.auth-know {
	text-align: center;
	font-size: 28rpx;
	color: #999;
	width: 80%;
	margin: 35px auto 25px;
}
.auth-know .blue-font {
	color: #576b95;
	padding: 0 2px;
}

/* 扫码 */
.scanQrcode {
	width: 100%;
	margin: 30px auto 0;
	background: #fff;
	border-radius: 15px;
	overflow: hidden;
}
.scanQrcode .tips-logo {
	width: 90px;
	height: 90px;
	margin: 50px auto 0;
}
.scanQrcode .tips-logo image {
	width: 90px;
	height: 90px;
}
.scanQrcode .scan-tips {
	text-align: center;
	color: #999;
	font-size: 50rpx;
	margin-top: 15px;
	margin-bottom: 50px;
}
.scanQrcode .button {
	padding: 12px;
	width: 150px;
	margin: 0 auto;
	border-radius: 10px;
	text-align: center;
	font-size: 33rpx;
	font-weight: 400;
}
.scanQrcode .scan-qrcode {
	background: #07c160;
	color: #fff;
	margin-bottom: 25px;
}

.loginResult {
	width: 100%;
	margin: 30px auto 0;
	background: #fff;
	border-radius: 15px;
	overflow: hidden;
}
.loginResult .tips-logo {
	width: 90px;
	height: 90px;
	margin: 50px auto 0;
}
.loginResult .tips-logo image {
	width: 90px;
	height: 90px;
}
.loginResult .login-success {
	text-align: center;
	color: #333;
	font-size: 50rpx;
	margin-top: 15px;
	margin-bottom: 25px;
}
.loginResult .login-fail {
	text-align: center;
	color: #fa5151;
	font-size: 50rpx;
	margin-top: 15px;
	margin-bottom: 25px;
}

示例地址

https://soft.svip8.vip/login/index1

本文作者

Soujer

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Soujer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值