同学,请实现一个扫码登录

马上要到春节了,小伙伴们的公司是不是已经可以申请请假调休呢?虽然今年刚入职没有年假(好像国家不是这么规定的,但也不好跟公司硬杠),大小周的我已经攒了 7 天调休,也可以提前回家过年啦!

即使是年底,打工人的工作量也没有减少,最近 leader 扔给我一个扫码登录的需求,我一看有点来劲了。一来做了多年前端,类似的需求还没有接触过,平时做的多的页面需求和改改 bug 对自身能力显然是无法提升的。二来扫码登录的功能很多应用都有做过,常见的微信扫码登录,也挺好奇具体如何实现。我大概看了一遍需求文档,写的挺详细的,流程图也标明了各端的交互流程。由于内网开发,产品流程图也忘记截图了,此处在网上找到的一个大概的流程图: image.png

主要涉及到的是 pc 端、手机端和后台服务端。由于听产品同事说手机端由原生端(安卓和 IOS)来实现,因此我这边只需要开发 pc 端就行,工作量直接减半有没有。做过该功能的小伙伴肯定了解,pc 端的实现还是比较简单的,主要就是开启轮询查询后台扫码状态,然后做对应的提示或登录成功后跳转首页。

扫码登录的需求在前端主要难点在轮询上

0. 什么叫轮询?

所谓的轮询就是,由后端维护某个状态,或是一种连续多篇的数据(如分页、分段),由前端决定按序访问的方式将所有片段依次查询,直到后端给出终止状态的响应(结束状态、分页的最后一页等)。

1. 轮询的方案?

一般有两种解决方案:一种是使用websocket,可以让后端主动推送数据到前端;还有一种是前端主动轮询(上网查了下细分为长轮询和短轮询),通过大家熟悉的定时器(setIntervalsetTimeout)实现。

由于项目暂未用到websocket,且长轮询需要后台配合,所以直接采用短轮询(定时器)开撸了。

遇到的问题:

1、由于看需求文档上交互流程比较清晰,最开始没去网上查找实现方案,自己直接整了一版setInterval的轮询实现。在跟后台联调的过程中发现定时器每 1s 请求一次接口,发现很多接口没等响应就开启下一次的请求,很多请求都还在 pending 中,这样是不对的,对性能是很大消耗。于是想了下,可以通过setTimeout来优化,具体就是用setTimeout递归调用方式模拟setInterval的效果,达到只有上一次请求成功后才开启下一次的请求。

// 开启轮询async beginPolling() {if (this.isStop) return;try {const status = await this.getQrCodeStatus();if (!status) return;this.codeStatus = status;switch(this.codeStatus) {case '2':this.stopPolling();// 确认登录后,需前端修改状态this.codeStatus = '5';this.loading = true;// 走登录逻辑this.$emit('login', {qrcId: this.qrcId,encryptCSIIStr: this.macAddr})break;case '3':// 取消登录this.stopPolling();await this.getQrCode();break;case '4':// 二维码失效this.stopPolling();break;default:break;}this.timer = setTimeout(this.beginPolling);} catch(err) {console.log(err);this.stopPolling();}},

2、在自测了过程中又发现了另外一个问题,stopPolling方法中clearTimeout似乎无法阻止setTimeout的执行,二维码失效后请求仍在不停发出,这就很奇怪了。上网搜索了一番,发现一篇文章(很遗憾,已经找不到是哪篇文章了!)记录了这个问题:大概意思是虽然 clearTimeout 已经清除了定时器,但此时有请求已经在进行中,导致再次进入了循环体,重新开启了定时器。解决办法就是,需要手动声明一个标识位isStop来阻止循环体的执行。

    stopPolling() {if (this.timer) {clearTimeout(this.timer);this.timer = null;// 标记终止轮询(仅clearTimeout无法阻止)this.isStop = true;}},

试了下确实达到效果了,但其实这个问题产生的具体原因我还是有些模糊的,希望遇到过相关问题的大佬能指点一下,感激不尽!

3、解决了上面提到的问题,就在以为万事大吉,只待提测的时候。后台同事发现了一个问题(点赞后台同事的尽责之心):他在反复切换登录方式(扫码登录<->账号密码登录)的过程中,发现后台日志有一段时间打印的qrcId不是最新的。然后我这边试了下,确实在切换频率过高时,此时有未完成的请求仍在进行中,导致qrcId被重新赋值了。虽然已经在beforeDestroy里调用了stopPolling清除定时器,但此时请求是未停止的。聪明的小伙伴们肯定想到axioscancelToken可以取消未完成的请求,但我实际也并没有用过,而且项目里也没有可以表演Ctrl+CCtrl+V的地方。于是百度了一番,找到一篇掘友的文章,为了表示尊敬我原封不动的搬到我的代码里了,哈哈!

import axios from "axios";
const CancelToken = axios.CancelToken;const cancelTokenMixin = {data() {return {cancelToken: null, // cancelToken实例cancel: null, // cancel方法};},created() {this.newCancelToken();},beforeDestroy() {//离开页面前清空所有请求this.cancel("取消请求");},methods: {//创建新CancelTokennewCancelToken() {this.cancelToken = new CancelToken((c) => {this.cancel = c;});},},
};
export default cancelTokenMixin;

掘友文章[:](在 vue 项目中取消 axios 请求(单个和全局) - 掘金 (juejin.cn))

在组件里引入 mixin,另外在请求时传入cancelToken实例,确实达到效果了。此时再次切换登录方式,之前的未完成的请求已被取消,也就无法再篡改qrcId。写到此处,我发现问题 2 也是未完成的请求导致的,那么是否可以不用isStop标识,直接在stopPolling中调用this.cancel("取消请求");不是更好吗?

完整代码如下:

import sunev from 'sunev'; // 全局公共方法库
import cancelTokenMixin from "@/utils/cancelTokenMixin"; // axios取消请求export default {props: {loginType: {type: String,default: 'code'}},mixins: [cancelTokenMixin],data() {return {qrcId: '', // 二维码标识qrcBase64: '', // 二维码base64图片macAddr: '', // mac地址loading: false,isStop: false,codeStatus: '0',qrStatusList: [{status: '-1',icon: 'error',color: '#ed7b2f',svgClass: 'icon-error-small',text: '二维码生成失败\n请刷新重试',refresh: true},{ status: '0', icon: '', text: '', refresh: false },{status: '1',icon: 'scan',color: '#2986ff',svgClass: 'icon-scan-small',text: '扫描成功\n请在移动端确认',refresh: false},{status: '2',icon: 'confirm',color: '#2986ff',svgClass: 'icon-confirm-small',text: '移动端确认登录',refresh: false},{status: '3',icon: 'cancel',text: '移动端已取消',refresh: false},{status: '4',icon: 'error',color: '#ed7b2f',svgClass: 'icon-error-small',text: '二维码已失效\n请刷新重试',refresh: true},{status: '5',icon: 'success',color: '#2986ff',svgClass: 'icon-success-small',text: '登录成功',refresh: false},{status: '6',icon: 'error',color: '#ed7b2f',svgClass: 'icon-error-small',text: '登录失败\n请刷新重试',refresh: true}],errMsg: ''}},async created() {try {await this.getQrCode();this.beginPolling();} catch(err) {console.log(err);}},computed: {// 当前状态curQrStatus() {const statusObj = this.qrStatusList.find(item => item.status === this.codeStatus);if (this.errMsg) {statusObj.text = this.errMsg;}return statusObj;}},methods: {// 开启轮询async beginPolling() {if (this.isStop) return;try {const status = await this.getQrCodeStatus();if (!status) return;this.codeStatus = status;switch(this.codeStatus) {case '2':this.stopPolling();// 确认登录后,需前端修改状态this.codeStatus = '5';this.loading = true;// 走登录逻辑this.$emit('login', {qrcId: this.qrcId,encryptCSIIStr: this.macAddr})break;case '3':// 取消登录this.stopPolling();await this.getQrCode();break;case '4':// 二维码失效this.stopPolling();break;default:break;}this.timer = setTimeout(this.beginPolling);} catch(err) {console.log(err);this.stopPolling();}},// 暂停轮询stopPolling() {if (this.timer) {clearTimeout(this.timer);this.timer = null;// 标记终止轮询(仅clearTimeout无法阻止)this.isStop = true;}},// 获取二维码base64async getQrCode() {this.reset();this.loading = true;try {const params = {encryptCSIIStr: this.macAddr}const res = await sunev.$https.post('sunev/LoginQRCGen',{ isLoading: false, cancelToken: this.cancelToken },params)if (res.qrcId) {this.qrcId = res.qrcId;this.qrcBase64 = res.qrcBase64;} else {this.stopPolling();}} catch(err) {this.errMsg = err.message;this.stopPolling();}},// 获取二维码状态async getQrCodeStatus() {try {const params = {encryptCSIIStr: this.macAddr}const res = await sunev.$https.post('sunev/LoginQRCQry',{ isLoading: false, cancelToken: this.cancelToken },params)return res.status;} catch(err) {this.stopPolling();}},// 刷新二维码async refresh() {await this.getQrCode();this.beginPolling();},// 切换登录类型toggle() {this.$emit('toggleLoginType');},// 重置reset() {this.isStop = false;this.codeStatus = '0';this.errMsg = '';},beforeDestroy() {this.stopPolling();}}
}

ps:

1、由于是老项目了,登录界面逻辑较多,避免臃肿,二维码登录拆分成单独组件实现

2、由于项目组在内网开发,以下代码都是一行行重新手打的,不是很重要的 html 和 css 部分就省略了

后记:

由于此需求并不着急上线,暂未提测,所以还不知测试同事会提出怎样的 bug。另外掘友们如果发现问题,也欢迎批评指正,感激不尽!

最后

欢迎关注"所谓前端"微信公众号,大家一起交流
点击扫码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/472126.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

悦纳自己:拥抱个人局限,开启成长之旅

悦纳自己&#xff1a;拥抱个人局限&#xff0c;开启成长之旅 在人生的旅途中&#xff0c;我们每个人都会面临无数的挑战和选择。有时我们会因为这些挑战而感到焦虑和不安&#xff0c;因为我们害怕失败&#xff0c;害怕无法达到预期的目标。然而&#xff0c;真正重要的是我们如何…

Java学习笔记------static

static 创建Javabean类 public class student {private int age;private String name;private String gender;public student() {}public student(int age, String name, String gender) {this.age age;this.name name;this.gender gender;}/*** 获取* return age*/public…

C语言—for循环(1)

for 语句在语法格式上&#xff0c;降低了提供循环结构时&#xff0c;遗忘循环三要素的几率。 for语句的应用场景&#xff1a;对循环次数预先可以获知的情况&#xff0c;如果预先无法获知次数时&#xff0c;推荐使用while语句 1.当型循环:(while) 特点&#xff1a; 先判断后执行…

【C语言期末项目-通讯录】-终级版本-可动态申请内存、可存储数据到文件(手把手详细过程,期末评分A+的项目,答辩辅助神博文,建议三连点赞收藏)

目录 ​编辑 前言&#xff1a; 1.项目功能需求分析 2.文件框架说明 3.程序主框架实现 4.创建联系人结构体类型和通讯录结构体类型 4.1创建通讯录 5.程序功能实现--封装功能函数实现不同功能 5.1通讯录初始化 5.2增加联系人 5.3显示所有联系人的信息 5.4删除指定…

基于Java springmvc+mybatis酒店信息管理系统设计和实现

基于Java springmvcmybatis酒店信息管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取…

【复现】cellinx摄像设备 未授权漏洞_50

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 cellinx是一家韩国的摄像设备 二 .漏洞影响 通过未授权访问可以创建用户进入后台&#xff0c;可能造成系统功能破坏。 三.漏洞复…

枚举,#define,C中程序内存区域划分

目录 一、枚举 1.1枚举类型的声明 1.2枚举类型的优点 1.3枚举类型的使用 二、#define定义常量 三、C中程序内存区域划分 一、枚举 1.1枚举类型的声明 枚举顾名思义就是⼀⼀列举。 把可能的取值⼀⼀列举。 比如我们现实生活中&#xff1a; ⼀周的星期⼀到星期日是有限…

数值类型的运算方式总结

提纲1&#xff1a;常见的位运算使用场景 提纲2&#xff1a;整数类型运算时的类型溢出问题&#xff0c;产生原因以及解决办法 提纲3&#xff1a;浮点类型运算时的精度丢失问题&#xff0c;产生原因以及解决办法 数值类型&#xff08;6种&#xff09;分为&#xff1a; 整型&…

高校疫情防控系统的全栈开发实战

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

英文单词-计算: calculate、count、compute、reckon

英文单词-计算: calculate、count、compute、reckon count 数数; 计算总数; 重要; 包括在内; 正式认可; 认为; 被视作; compute 计算&#xff0c;估算; calculate 计算; 估算; 估计; 预料; reckon 测算&#xff0c;估计; 认为; 计算; 评定&#xff0c;断定; 这四个单词 “c…

Mock.js

在开发后端的应用中&#xff0c;我们使用postman来测试接口&#xff0c;观察和验证前后端之间的数据传递是否正常。 在开发前端的应用中&#xff0c;我们使用Mock.js来模拟后端服务&#xff0c;以便进行前端业务逻辑的开发和测试。 一般情况下&#xff0c;个人开发或者小团队开…

laravel_进程门面_简单介绍

文章目录 Facade是什么&#xff1f;Facade能干什么Facade有哪些方法&#xff1f;怎么使用Facade呢&#xff1f;详细的代码解释Symfony Process是什么&#xff1f;介绍Symfony总结 Facade是什么&#xff1f; 在 Laravel 框架中&#xff0c;Facade 是一种设计模式。 它提供了一…