2023-03-29 如何用 SSE 实现扫码登录
温馨提示:本文使用 ChatGPT 润色。
参考链接:
服务器端事件发送(
SSE
):https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_eventsNest.js:https://docs.nestjs.cn/9/introduction
uni-app:https://uniapp.dcloud.net.cn/api/system/barcode.html
扫码登录
最近想给自己的网站添加一个扫码登录功能,这样就不用再输入密码登录了,于是就去研究了下如何实现扫码登录。
扫码登录的基本介绍
扫码登录是一种快速、便捷的登录方式,用户只需用手机扫描二维码即可完成登录。
相较于相对于传统的账号密码登录,扫码登录具有以下优势:
方便快捷:用户只需使用手机扫描二维码即可完成登录,避免了输入复杂的账号和密码,提升了用户体验;
安全可靠:扫码登录不需要用户输入账号和密码,避免了密码泄露的风险;
适用范围广:扫码登录可以应用于多种场景,比如企业 OA 系统、电商网站、社交软件等。
而对于网站运营方而言,扫码登录也有以下好处:
提升用户体验:传统的账号密码登录方式需要用户输入复杂的账号和密码,容易让用户感到繁琐和不便。而扫码登录只需要用户使用手机扫描二维码即可完成登录,简单、方便,可以提升用户体验。
提高用户留存率:账号密码登录需要用户输入账号和密码,对于新用户来说,可能会因为忘记密码、输入错误等原因而放弃注册,而扫码登录可以避免这种情况的发生,提高用户注册和留存率。
另外,对我个人而言,实现扫码登录也是一次技术性的试验,对了解如何实现扫码登录也大有好处。
扫码登录的基本流程
- 用户在电脑端打开需要登录的网站或应用,选择扫码登录选项;
- 系统生成二维码,并在电脑端显示;
- 用户使用手机扫描电脑端的二维码;
- 手机端确认登录,将登录信息发送到服务端;
- 服务端验证登录信息,验证通过后完成登录。】
技术选型
很显然的是,在扫码登录的过程中,需要从服务端向浏览器端推送消息,所以需要一项可以实现该功能的技术。
经过一番搜索,有以下几个常见技术方案:
WebSocket:WebSocket 是一种新型的双向通信协议,可以在客户端和服务端之间建立持久性的连接,支持实时双向通信。WebSocket 可以直接在浏览器和服务端之间建立连接,实现服务端向浏览器端推送消息。
Server-Sent Events(SSE):SSE 是一种基于 HTTP 的单向推送技术,它允许服务端向客户端发送事件流(Event Stream),客户端通过 EventSource API 进行监听并处理。SSE 可以实现服务端向浏览器端的单向消息推送。
Long Polling:Long Polling 是一种基于 HTTP 的轮询技术,客户端向服务端发送请求,服务端在没有消息的情况下将请求挂起,当有消息时再响应请求。客户端接收到响应后立即再次发送请求,从而实现不间断的消息推送。
WebRTC:WebRTC 是一种实时通信技术,可以在浏览器之间进行直接的点对点通信,无需通过服务器中转。WebRTC 可以实现浏览器端之间的实时双向通信,也可以实现服务端向浏览器端的消息推送。
经过一番比对后,我认为 WebSocket 虽然可以实现这个需求,但扫码登录只需要服务端向浏览器端的单向推送,无需双向推送,所以选 WebSocket 的话技术上有些重了。
而 Long Polling (长轮询)则较为消耗服务端性能,对服务端而言无法主动控制推送和断开,也有些不足。
WebRTC 虽然也可以实现双向推送,但实现起来略微复杂,也有些过重了。
故综合考虑,我认为使用 SSE 来实现扫描登录是比较合理的。
Server-Sent Events(SSE)
SSE 的基本概念
SSE 是一种基于 HTTP 的单向推送技术,它允许服务端向客户端发送事件流(Event Stream),客户端通过 EventSource API 进行监听并处理。SSE 可以实现服务端向浏览器端的单向消息推送。
在后端,Nest.js 中实现一个 SSE 只需要如下代码:
1 | 'sse') ( |
然后在浏览器端执行以下代码:
1 | const eventSource = new EventSource('/sse'); |
就可以在控制台看到每秒打印一次的日志了。
SSE 的优势和局限性
SSE 的优势有:
- 建立简单:SSE 基于 HTTP 协议,无需像 WebSocket 那样进行握手协议,建立连接比较简单。
- 兼容性好:SSE 对浏览器的支持性很好,大部分浏览器都能支持 SSE。
- 对服务器资源要求低:SSE 建立的是单向连接,推送服务端只需向客户端发送数据流,相比 WebSocket 而言,对服务器资源的要求较低。
- 实时性好:SSE 可以在服务端有新数据时立即将数据推送到客户端,实时性较好。
而 SSE 的局限性包括:
- 单向通信:SSE 是一种单向通信协议,只能由服务端向客户端推送数据,无法实现客户端到服务端的双向通信。
- 无法处理大量数据:SSE 的数据传输方式是基于文本的,对于大量数据传输效率较低,容易造成延迟和卡顿。
- 无法处理复杂的业务场景:SSE 的使用场景比较简单,只适用于一些简单的数据推送和通知场景,无法处理复杂的业务场景。
- 对浏览器的支持有限:虽然大部分浏览器都支持 SSE,但是有些浏览器对 SSE 的支持还不是很好,需要开发者进行额外的兼容性处理。
总的来说,SSE 适合于一些简单的实时通知和消息推送场景,对于一些复杂的业务场景和大量数据传输场景,SSE 的性能和功能都比较有限。
由于扫描登录只需要从服务端向客户端推送数据,所以采用 SSE 来实现扫码登录是可以的。
用 SSE 实现扫码登录 - 后端部分
既然选定了 SSE 作为技术方案,那么就要开始具体的实现了。
经过一番思考,我认为扫码登录需要实现以下几个接口:
- 获取/生成二维码的接口
- 提交扫码二维码结果的接口
- 从服务端向浏览器端推送扫码结果的接口
获取/生成二维码的接口
该部分的技术栈为:Nest.js
生成二维码其实是比较简单的,本质上还是要由服务端随机生成一段 code,然后发送给前端,前端渲染为二维码即可。
在生成 token 这一段,我采用了uuid
来实现,在足够随机的情况下,撞uuid
的概率还是很小的。
参考代码如下:
1 | 'getQrCode') ( |
在生成 code 之后,显然是需要存到数据库的,这里自然选择了使用 redis 作为数据库,无他,只是 redis 特别快而已。
我选择了在 redis 中存一个 hash,其中 status 字段为当前二维码的状态,而 uid 则用于存扫码的用户 id。
有关 redis 的使用请参考:https://github.com/luin/ioredis
在生成 code 之后,就要在前端渲染为二维码了,这一部分稍后在网页端部分详细说明。
提交扫码二维码结果的接口
在手机端扫描二维码之后,会得到之前生成的 code,再提交到服务器就可以告诉服务器扫码成功了。
参考代码如下:
1 | export class QrCodeData { |
在这里我定义了手机端的三个操作:扫码、批准和取消。
一般来讲,在用户扫码之后就应该在浏览器端有所表现,可以提示扫码成功
等,但用户实际登录要等到点击确定
授权登录之后,所以这里还需要有一个批准登录的操作。
从服务端向浏览器端推送扫码结果的接口
最后就是向浏览器端推送结果了。
在 Nest.js 中使用 SSE, 需要返回一个Observable
流,例如:
1 | export class HandleQrCodeData { |
这样一来就实现了一个每秒查询一次扫码结果并向浏览器推送的 SSE。
注意,这里的实现有以下几个问题:
- 轮询 redis 对 redis 的性能损耗较大,更合理的方案是使用 redis 的事件订阅。这里为了简单起见就采用了轮询。
- 由于查询 redis 是一个异步操作,会返回一个 Promise,需要用 RxJS 的 mergeAll 操作符才能获取到 Promise 的 resolve 值。
用 SSE 实现扫码登录 - 浏览器端部分
该部分的技术栈为:TypeScript(纯 JavaScript 也可实现)
在浏览器端要做的事情比较简单,一共以下几步:
- 获取登录二维码的 code
- 根据 code 渲染二维码
- 监听扫码结果
获取登录二维码的 code
这一步实际上比较简单,发起一个 ajax 请求即可
1 | export interface IQrCode { |
根据 code 渲染二维码
这一步需要用到一些第三方包来实现了,这里我采用了qrcode
这个包来生成二维码。
1 | import QRCode from 'qrcode' |
这里会生成一个 base64 格式的图片,在 html 中用img
标签渲染即可。
1 | <img |
监听扫码结果
然后则是要监听扫码结果了,因为使用了 SSE,这里自然也是用 SSE 相关接口了。
1 | const e = new EventSource(`/handleQrCode?qrCode=${qrCode.value}`) |
用 SSE 实现扫码登录 - 手机端部分
该部分的技术栈为:uni-app
最后就到了手机端部分的实现了。
实际上这最后一步也是最好实现的,原因在于扫码模块直接调用第三方包即可,有成熟的第三方包可以使用。
手机端部分主要有以下几个步骤:
- 扫描二维码,获取二维码结果
- 用户授权登录
- 通知服务端用户授权登录
注意:
扫码成功之后应该还要通知服务端二维码扫描成功。但这里为了简单起见,就不实现了通知扫码成功的部分了。
在等待用户授权登录的时候,用户也可以取消授权。但这里为了简单起见,就不实现取消登录的部分了。
扫描二维码,获取二维码结果
在 uni-app 中,使用uni.scanCode
可以非常轻松的在除 H5 外的环境下实现扫码登录。
参考代码如下:
1 | uni.scanCode({ |
其中的res.result
就是二维码中的 code。
用户授权登录
然后需要等待用户授权,这里弹一个模态框出来
1 | uni.showModal({ |
通知服务端用户授权登录
用户授权登录之后发一次 ajax 请求即可。
1 | interface QrCodeData { |
前后端联调
在实现了以上几个部分之后,还需要最终调试才能确定逻辑是否正确。
由于同时涉及到电脑端和手机端,因此需要两者在同一局域网下才能联调。
手机端要通过局域网访问电脑的话还需要电脑开放对应的端口。
如果想省点事的话可以直接部署到公网联调。
总结
本文旨在介绍如何使用 Server-Sent Events(SSE)技术实现扫码登录,并提供了完整的技术选型和流程。文章分为后端、浏览器端和手机端三个部分,介绍了各自的技术栈和实现方法。其中,后端部分使用 Nest.js 技术实现获取/生成二维码的接口、提交扫码二维码结果的接口、从服务端向浏览器端推送扫码结果的接口;浏览器端使用 TypeScript 技术获取登录二维码的 code、根据 code 渲染二维码、监听扫码结果;手机端使用 uni-app 技术扫描二维码、获取二维码结果、用户授权登录,并通知服务端用户授权登录。最后,文章介绍了前后端联调过程。
【总结由 ChatGPT 生成】
本文作者:草梅友仁
本文地址: https://blog.cmyr.ltd/archives/634d3ff9.html
版权声明:转载请注明出处!