【Uniapp】小程序携带Token请求接口+无感知登录方案2.0
本次改进原文《【Uniapp】小程序携带Token请求接口+无感知登录方案》,在实际使用过程中我发现以下bug:
- 若token恰好在用户访问接口时到期,就会直接查询为空,不反映token过期问题(例如:弹窗显示订单查询记录为空),并不是因为没有数据而是因为token过期了,接口返回了但是没有在前端显示
- token过期后需要重新启动小程序,才会获取到新的token
- 获取到token后,原接口不会继续请求,页面页面空白没有任何数据,数据需要下一次刷新才有
问题演示如下:
【审核中】
目录
- 吐槽
- token 是什么?
- 设计思路(点击方法可跳转原文档)
- 操作流程
- 后端代码
- 前端小程序封装📦代码
- 演示
- 1、不存在本地缓存、不存在redis记录 演示
- 2、不存在本地缓存演示
- 3、服务器端不存在redis记录 演示
- 如何进行token鉴权
- thinkphp5 redis补充
- 2.0改进方案
- 总结
吐槽
写本篇的原因是因为之前开发用的都不是微信小程序给的session作为token鉴权的,这次开发打算使用多端同步的uniapp开发小程序,方便后面转多端,所以我想尝试新的东西,另外在热榜中我看到一篇文章用"
access_token
作为token来请求验证接口、checkSession用来检测access_token有无过期",不得不使我感叹,现在的技术er这么差了吗?简直就是误人子弟!!
我们来说说为什么不能用access_token作为token
- 【官方回答】access_token 是小程序全局唯一后台接口调用凭据,调用绝大多数后台接口时都需使用。开发者可以通过 getAccessToken 接口获取并进行妥善保存。 -【官方回答】
获取小程序全局唯一后台接口调用凭据,token有效期为7200s,开发者需要进行妥善保存。
所以说,access_token 只是用来调用一些微信提供的api服务的,并且access_token 只有两个小时,你把access_token当作小程序的token?不仅不满足暴露这个问题,时间上也有限制
我们再来说说checkSession是用来检测什么的?
- 登录态过期后开发者可以再调用 wx.login 获取新的用户登录态。调用成功说明当前 session_key 未过期,调用失败说明 session_key 已过期。
所以!checkSession是用来检测session_key而不是access_token的,access_token是根据小程序的appid和secret确定的,没有单一用户代表性
token 是什么?
token 顾名思义就是令牌,也就是一种身份标志。用于和服务器确定身份,它具有时效性,超过有效时间身份标志就会失效。
设计思路(点击方法可跳转原文档)
通过小程序客户端发起的**wx.login()** 获取临时登录凭证code ,并回传到开发者服务器,通过微信提供的 auth.code2Session 接口,换取 用户唯一标识 openid、 会话密钥 session_key。并通过以session_key为名,openid为值将数据存放到redis中,在这里我将时间设置为48h
- 若服务端token失效,客户端登陆状态也会失效,失效后重新登陆执行上述步骤;
- 若客户端checkSession失效或者本地数据缓存失效,则也会重新登录
上述两个步骤保证小程序端的token都是最新的,缺点是不能及时性作废原先在服务器存储的数据只能等redis过期
以上设计逻辑思路满足下图:
操作流程
后端代码
以Thinkphp5.0.24为案例(20230614新增控制器写法,原来写的是原生php,二选一,建议第一种)
1.TP控制器登录接口方法(建议)
//登录接口// http://code.taila.club/index.php/index/api/login// 目录\控制器\方法public function login(){ $code=input('code');if ($code) {// 存在记录// $res=Db::table("sys")->where("id","1")->find();// $appid=$res["val1"];//小程序id// $secret=$res["val2"];//密钥$appid="w***********e";$secret="6***************6";$code=$_GET['code'];$url="https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code";$header = array('Accept: application/json',);$curl = curl_init();//设置抓取的urlcurl_setopt($curl, CURLOPT_URL, $url);//设置头文件的信息作为数据流输出curl_setopt($curl, CURLOPT_HEADER, 0);// 超时设置,以秒为单位curl_setopt($curl, CURLOPT_TIMEOUT, 1);// 超时设置,以毫秒为单位// curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);// 设置请求头curl_setopt($curl, CURLOPT_HTTPHEADER, $header);//设置获取的信息以文件流的形式返回,而不是直接输出。curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);//执行命令$data = curl_exec($curl);// 显示错误信息if (curl_error($curl)) {print "Error: " . curl_error($curl);die(json_encode(array('code' => 100,'data' => '','msg' => '请求出错!'),480));} else {// 打印返回的内容$result=json_decode($data,true);if (array_key_exists("errcode",$result)){// echo "键存在!";die(json_encode(array('code' => 100,'data' => '','msg' => '获取token失败!'.$result['errmsg']),480));}else{// echo "键不存在!";// 开启redius//写入redius session_key名命的openid数据 默认存储2天curl_close($curl);$Redis=new Redis();$Redis->set($result['session_key'],$result['openid'],24*60*60*2);die(json_encode(array('code' => 200,'data' => $result,'msg' => '获取token成功'),480));}}} else {// 已被处理或者不存在 请求重新登陆die(json_encode(array('code' => 100,'data' => '','msg' => '非法操作'),480)
);}}
在public文件夹创建php文件access_token.php(不建议)
用于接收前端wx.login方法获得的code换回openid和session_key,并通过以session_key为名,openid为值将数据存放到redis中,在这里我将时间设置为48h
<?php
//小程序登录
$appid="";//小程序id
$secret="";//密钥
$code=$_GET['code'];
curl_get("https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code");
function curl_get($url){$header = array('Accept: application/json',);$curl = curl_init();//设置抓取的urlcurl_setopt($curl, CURLOPT_URL, $url);//设置头文件的信息作为数据流输出curl_setopt($curl, CURLOPT_HEADER, 0);// 超时设置,以秒为单位curl_setopt($curl, CURLOPT_TIMEOUT, 1);// 超时设置,以毫秒为单位// curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);// 设置请求头curl_setopt($curl, CURLOPT_HTTPHEADER, $header);//设置获取的信息以文件流的形式返回,而不是直接输出。curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);//执行命令$data = curl_exec($curl);// 显示错误信息if (curl_error($curl)) {print "Error: " . curl_error($curl);die(json_encode(array('code' => 100,'msg' => '请求出错!'),480));} else {// 打印返回的内容$result=json_decode($data,true);if (array_key_exists("errcode",$result)){// echo "键存在!";if ($result['errcode']==0) {// code...// 开启rediusini_set('session.save_handler', 'redis');ini_set('session.save_path', 'tcp://127.0.0.1:6379');$redis = new redis();$redis->connect('127.0.0.1', 6379);//写入redius session_key名命的openid数据 默认存储2天curl_close($curl);$redis->set($result['session_key'],$result['openid'],24*60*60*2);die(json_encode(array('code' => 200,'msg' => $result),480)
);} else {die(json_encode(array('code' => 100,'msg' => '获取token失败!'.$result['errmsg']),480));}}else{// echo "键不存在!";die(json_encode(array('code' => 100,'msg' => '获取token失败'),480));}}
}?>
- 在tp框架中(application/index/controller)新建Api.php控制器
用来检测服务器端的token是否存在,以便于让小程序做出重新登录操作
<?php
namespace app\index\controller;
use think\Db;
use think\cache\driver\Redis;
use app\index\controller\Base;
class Api extends Base
{// 验证session_key是否过期(服务器默认48h,到期后自动删除,查询不到表示过期)public function check_session(){ $session_key=input('session_key');$redis = new Redis();//读取数据$result= $redis->get($session_key);if ($result) {// 存在记录die(json_encode(array('code' => 200,'msg' => 'token验证通过'),480)
);} else {// 已被处理或者不存在 请求重新登陆die(json_encode(array('code' => 100,'msg' => 'token验证失败,请重新登录'),480)
);}}
}
前端小程序封装📦代码
- 客户端封装
- 在项目中新建token.js文件
代码:
/*** token.js,全局校验方法,可以自己补充*/
export default {login: function(session) { let that = this;uni.showLoading({title: '登录中...'});uni.login({provider: 'weixin',success: loginRes => {console.log(loginRes);that.code = loginRes.code;// 将用户登录code传递到后台置换用户SessionKey、OpenId等信息uni.request({url: 'https://serverhost/wx_token.php', //仅为示例,并非真实接口地址。data: {code:loginRes.code},method: 'GET',header: {'content-type': 'application/x-www-form-urlencoded' //自定义请求头信息},success: (res) => {console.log(res.data);console.log(res.data.msg.session_key)uni.hideLoading()//存取sessionuni.setStorageSync('session', res.data.msg.session_key);//openid只需要服务通过session请求redis即可}});},fail: () => {uni.showToast({ title: '获取 code 失败', icon: 'none' });}});},//检测token 每次发起业务请求检测即可check_token: function(session) { let that=this;//微信检测uni.checkSession({success () {//session_key 未过期,并且在本生命周期一直有效console.log("未过期")//没有过期在判断下存储是否存在 后需提交业务需要用到const session = uni.getStorageSync('session');if (session=='') {console.log("session不存在");that.login()}else{//检测服务器的console.log("session存在-校验合法性");//验证检测服务器session有效性uni.request({url: 'https://serverhost/index.php/index/Api/check_session', //仅为示例,并非真实接口地址。data: {session_key:session},method: 'POST',header: {'content-type': 'application/x-www-form-urlencoded' //自定义请求头信息},success: (res) => {if (res.data.code!=200){ //服务器token已过期 重新登录console.log("服务器token已过期 重新登录");that.login()}else{console.log("服务器token有效");}}});}},fail () {// session_key 已经失效,需要重新执行登录流程console.log("session过期")that.login()}})}
}
- 在main.js中引用,挂载到全局
import token from '@/sdk/token.js'// 挂载到全局
Vue.prototype.$token = token
- 使用方法
this.$token.check_token()
演示
uniapp打包成微信小程序运行后
1、不存在本地缓存、不存在redis记录 演示
-
前端运行产生了新的token,记录在本地缓存中
- -
并且前端登陆后有一条新的记录到redis中
-
2、不存在本地缓存演示
- 前端清除了上次的token,刷新后会无感登录获取最新的token并记录在本地缓存、redis中
- 后端redis存在新的一个token记录,第一次的token等时间倒计时结束失效
3、服务器端不存在redis记录 演示
-
删除第二次的token记录,刷新前端模拟器(不清除token),看看结果
-
刷新后,前端检测不到token记录执行重新登陆获取最新token
以上已经对所有的可能做了一个实验,除了【更新新的token后,上次的token并不能及时失效】这个问题,找不到其他毛病了
如何进行token鉴权
-
前端小程序每次发起业务请求时,先调用一次封装好的【check_token】用于检查本地有误存储token、token是否已经过期(微信决定)、服务器端redis是否存在(不存在没必要发起,因为还是会被拒绝)
-
服务器端验证token是否有效只需要对token进行查询即可,存在即为成功,直接取出openid书写业务逻辑代码,失败让小程序重新登陆,这些根据返回码即可
还是看演示吧
- 新建控制器Index.php(路径application/index/controller)
服务器端验证token是否有效只需要对token进行查询即可,存在即为成功,直接取出openid书写业务逻辑代码,失败让小程序重新登陆,这些根据返回码即可
<?php
// 访问路由 https://***/index.php/index/Api/index
namespace app\index\controller;
use think\Db;
use think\cache\driver\Redis;
use app\index\controller\Base;
class Index extends Base
{public function index(){ // 实例 获取openid$token=input('token');$redis = new Redis();$result= $redis->get($token);if ($result) {die(json_encode(array('code' => 200,'data'=>$result,'msg' => '数据请求成功'),480)
);} else {die(json_encode(array('code' => 100,'data'=>'','msg' => 'token失效或不存在!请重新获取'),480)
);}}
}
发起正确的请求
如果在后面加了一个1
thinkphp5 redis补充
$redis->set('name','value','3600');//添加记录前两个分别表示名和值,后者单位秒
$redis->get($session_key);//根据名查询值
2.0改进方案
在上述测试中发现了以下问题:
- 若token恰好在用户访问接口时到期,就会直接查询为空,不反映token过期问题(例如:弹窗显示订单查询记录为空),并不是因为没有数据而是因为token过期了,接口返回了但是没有在前端显示
- token过期后需要重新启动小程序,才会获取到新的token
- 获取到token后,原接口不会继续请求,页面页面空白没有任何数据,数据需要下一次刷新才有
测试过程:
通过删除redis中的记录使token提前到期测试
改进思路:
- 将后端查询失败的接口和token失效的接口返回码调整(我这里成功200 查询失败100 token失效400,这里与上面无关,开发者自己写接口知道这个就行)
- 将前端代码接口请求中的requests返回判断中加入
代码解释: 告知用户token失效,自动获取,然后获取后调用自身方法传递刚刚的参数重新执行
else if(res.data.code==400){
that.$token.toast("token已过期,正在获取",1500)
that.$token.check_token();
setTimeout(function() {
that.get_book_detail(id);
}, 1200);
}
完整代码示例:(参考下方代码就明白了)
get_book_detail(id){
//获取图书详情数据库
let that=this;
that.$token.request("index.php/index/Api/query_book_details","POST",{token:uni.getStorageSync('token'),
id:id
}).then(res => {
console.log(res.data)
if (res.data.code==200) {
that.$token.toast(res.data.msg,1500)
that.book_data=res.data.data;
that.get_book_detail_api(res.data.data.book_isbn)
// 由于速度更不上,查询大概在2-3s,现在做了调整
//that.htmlContent=res.data.data.details.contentIntro.c;
// 轮播图
var swiper=[
res.data.data.book_cover
// res.data.data.details.coverImage.middle];
that.swiper=swiper;
// that.showPopup2 = true;//开启浮窗
} else if(res.data.code==400){
that.$token.toast("token已过期,正在获取",1500)
that.$token.check_token();
setTimeout(function() {
that.get_book_detail(id);
}, 1200);
}
else{
that.$token.toast("未查询到结果",1500)
}
})
},
- 所有的前端请求都根据上一步整改,即可解决
成功演示如下:
总结
以上就是今天对uniapp结合微信小程序携带Token请求接口无感知的登录方案,如果您喜欢请收藏起来!
相关文章:

【Uniapp】小程序携带Token请求接口+无感知登录方案2.0
本次改进原文《【Uniapp】小程序携带Token请求接口无感知登录方案》,在实际使用过程中我发现以下bug: 若token恰好在用户访问接口时到期,就会直接查询为空,不反映token过期问题(例如:弹窗显示订单查询记录…...

Ubuntu常用命令
文章目录 1:文件管理2:文档编辑3:系统管理4:磁盘管理5:文件传输6:网络通讯7:设备管理8:备份压缩9:其他命令扩展:知识干货 1:文件管理 ls命令 –…...

ERP重构-SLA子分类账-分布式实现方案
背景 ERP中的GL总账模块,明细数据来源于各个业务模块如库存、成本、应收、应付、费控、资产等,统称为子模块,生成的账叫做子分类账。然而记账的业务逻辑各式各样,但是最终输出都是来源、类型、期间、科目、借贷金额等等关键信息。…...

IP路由协议(RIP、IGRP、OSPF、IS-IS、BGP)
文章目录 1、路由分类2、RIP协议1)RIP的工作原理2)RIP路由表的更新过程3)RIP路由表的更新原则4)RIP的特性5)RIP协议的版本 4、IGRP协议1)IGRP路由表的更新2)IGRP的度量标准 5、OSPF协议1&#x…...

互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
多线程访问共享资源的时候,避免不了资源竞争而导致数据错乱的问题,所以我们通常为了解决这一问题,都会在访问共享资源之前加锁。 最常用的就是互斥锁,当然还有很多种不同的锁,比如自旋锁、读写锁、乐观锁等࿰…...
Python WSGI 与 Web 开发框架
目录 文章目录 目录WSGIWSGI 的工作原理environ 参数start_resposne 参数 WSGI 的中间件 WSGI Web 开发框架OpenStack 中的应用案例进程入口WSGI Application 加载Paste/PasteDeployRoutesWebOb WSGI Server 启动 WSGI WSGI(Web Server Gateway Interfaceÿ…...

[洛谷]P6464 [传智杯 #2 决赛] 传送门
看到数据范围:n<100,嗯......脑子闪过:还在想什么呢!Floyd啊。哈哈哈 思路: 详细注释: 话不多说,上ACcode!: #include<bits/stdc.h> using namespace std; #define int lo…...

Http协议和RestTemplate协议有什么区别?
目录 一、功能不同 二、技术不同 三、使用场景不同 四、总结 RestTemplate 是一个 Spring 框架提供的用于发送 HTTP请求的客户端工具,它封装了 Java 原生的 HTTP 客户端库,并提供了一组简洁易用的 API 来发送 HTTP 请求和处理响应。而 HTTPÿ…...

基于SpringBoot+微信小程序的医院预约叫号小程序
✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: 该项目是基于uniappWe…...

springboot整合RabbitMQ 消费端处理数据
pom 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>写一个rabbitmq配置文件 import org.springframework.amqp.core.Binding; import org.springframewo…...

计算机中CPU、内存、缓存的关系
CPU(Central Processing Unit,中央处理器) 内存(Random Access Memory,随机存取存储器) 缓存(Cache) CPU、内存和缓存之间有着密切的关系,它们共同构成了计算机系统的核…...

【Linux实验】构造一个简单的 shell
一、实验目的 l 用 C/C++构造一个简单的 shell; l 理解 shell 程序的功能; l 学会 shell 的使用;...

【电路原理学习笔记】第2章:电压、电流和电阻:2.6 电路
第2章:电压、电流和电阻 2.6 电路 2.6.1 电流的方向 电流方向有两种说法,一种按电子流动方向,另一种是传统的认为从正极流出到负极,这本教材采用传统电流方法。(事传统派,维新派输了,1&#…...

基于深度学习的人脸检测技术
用到环境 1、pycharm community edition 2022.3.2 2、Python 3.10 整篇内容都已上传至我的csdn资源中,想用的请移步。 流程 多任务级联卷积神经网络(Multi-task Cascaded Convolutional Networks, MTCNN)算法进行人脸检测 普通人脸检测 单人人脸检测 图1 单人人…...

【linux kernel】一文总结linux内核通知链
文章目录 1、通知链简介2、通知链的类型3、原理分析和API(1)注销通知器(2)注销通知器(3)通知链的通知 4、实例代码(1)定义一个通知链(2)实现观察者模块&#…...

kafka入门,Kafka 副本(十三)
Kafka副本 副本基本信息 1)Kafka副本作用,提高数据可靠性 2)Kafka默认副本1个,生产环境一般配置2个,保证数据可靠性,太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率 3&a…...

利用PPT制作简单的矢量图
1.用PPT画一个图形(可以多个图) 2.鼠标圈住图形 3.利用 Ctrl G 组合图形,再用 Ctrl C 复制 4.打开word—粘贴—选择性粘贴—图片(增强性图元文件) 确认即可。...

18-Linux 常用命令
目录 1.ls PS:FinalShell设置背景和字体 2.pwd 3.cd PS:认识 Linux 目录结构——Linux 是一个树形目录结构 PS:绝对路径 vs 相对路径 PS:使用 tab 键补全 PS:使用 ctrl c 重新输入 4.touch PS:L…...

2024考研408-计算机组成原理第六章-总线学习笔记
文章目录 前言初识总线一、总线概述1.1、总线的概述1.1.1、认识总线1.1.2、设计总线需要的特性1.1.3、总线的分类①按照数据传输格式分(串行、并行)②按照总线功能连接的总线(片内总线、系统总线、通信总线)③按照时序控制方式&am…...

uni_app 微信小程序 苹果手机 边框显示不全

vue 访问第三方 跨域, 配置vue.config.js
目录 0 config 文件被修改 一个要重启vscode 配置文件才会生效 1 第一种 (有两种写法) 1.1 配置vue.config.js 1.2 axios 使用 1.3 终端打印 2 第二种方法 --> 错误 --> 没有运行成功 2.1 配置vue.config.js --> 就是api 不被设置成 替换为 / 2.2 axios 使用…...

使用gradio库的File模块实现文件上传和展示
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️ 👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...

网络安全进阶学习第四课——SSRF服务器请求伪造
文章目录 一、什么是SSRF?二、SSRF成因三、SSRF简析四、PHP存在SSRF的风险函数五、后台源码获取方式六、SSRF危害七、SSRF漏洞挖掘从WEB功能上寻找,从URL关键字中寻找 八、SSRF具体利用ssrf常利用的相关协议PHP伪协议读取文件端口扫描 九、SSRF存在的必要…...

js处理扁平数组和树结构相互转换
一、将扁平的数据转为树形结构 在 js中,可以使用递归算法将扁平的数据转换为树形结构。 扁平数据通常是一个带有 parentId 属性的数组,而树形结构通常是一个带有 children 属性的对象。 1、方法一 下面是一个简单的例子,演示如何将扁平数…...

Spark弹性分布式数据集
1. Spark RDD是什么 RDD(Resilient Distributed Dataset,弹性分布式数据集)是一个不可变的分布式对象集合,是Spark中最基本的数据抽象。在代码中RDD是一个抽象类,代表一个弹性的、不可变、可分区、里面的元素可并行计…...

ffmpeg学习记录
1、对图片进行裁剪 ffmpeg -i input.jpg -vf cropiw/3:ih:20:0 caijian.jpg PS: crop100:100:12:34 相同效果: cropw100:h100:x12:y34 2、视频增加文字水印 使用drawtext滤镜进行增加水印 参数 类型 说明 text 字符串 文字 textfile 字符串 文字文件 …...

ChatGPT:为教育创新提供五大机遇
随着智能技术的不断发展,ChatGPT在教育场景中的创新价值可能比我们能够意识到的还要多。比如它可以自动处理作业、在线答疑,可以辅助语言学习、实时沟通,甚至还可以用于评估诊断、科学研究。国内外关于利用ChatGPT实现教育创新的场景描绘已经…...

Educational Codeforces Round 151 (Rated for Div. 2)
Edu 151 A. Forbidden Integer 题意: 你有[1, k]内除了 x x x的整数,每个数可以拿多次,问 ∑ n \sum n ∑n是否可行并构造 思路: 有1必能构造,否则假如没有1,假如有2, 3必定能构造出大于等于2的所有数&…...

【AI机器学习入门与实战】机器学习算法都有哪些分类?
👍【AI机器学习入门与实战】目录 🍭基础篇 🔥 第一篇:【AI机器学习入门与实战】AI 人工智能介绍 🔥 第二篇:【AI机器学习入门与实战】机器学习核心概念理解 🔥 第三篇:【AI机器学习入…...

React之hooks
Hooks函数 1.useState():状态钩子。纯函数组件没有状态,用于为函数组件引入state状态, 并进行状态数据的读写操作。 const [state, setState] useState(initialValue); // state:初始的状态属性,指向状态当前值,类似…...