1. 为什么我们需要扫码登录?一个更优雅的登录方式
想象一下这个场景:你正坐在电脑前,准备登录某个系统的后台管理页面。通常,你需要输入一长串用户名和密码,可能还得应付烦人的图片验证码。这时,你掏出手机,打开对应的小程序,对着屏幕上的二维码“滴”一下,然后在手机上点一下“确认登录”,电脑页面就自动跳转进去了。整个过程行云流水,连键盘都不用碰。
这就是基于微信小程序的PC端扫码登录。它不仅仅是为了“酷”,而是实实在在地解决了几个痛点。首先,安全性更高。传统的账号密码登录,密码存在泄露、被撞库的风险。而扫码登录的本质是用你已经登录的、可信的设备(手机)去授权一个新设备(电脑),密码不参与传输,大大降低了风险。其次,用户体验极佳。用户无需记忆和输入复杂的密码,尤其是在手机已经常态化使用的今天,扫码动作非常自然。最后,对于开发者而言,它统一了用户体系。很多系统既有小程序端用户,又有后台管理用户,扫码登录能优雅地将两者关联起来,实现“一端登录,多端互通”。
我在多个企业级项目中落地过这种方案,实测下来,用户接受度非常高,登录成功率也显著提升。接下来,我就以国内流行的若依(RuoYi) 后台管理系统为基础,为你拆解这套流程从生成二维码到最终登录成功的每一个技术细节。你会发现,它的核心逻辑并不复杂,关键在于几个状态的有效流转和前后端的紧密配合。
2. 核心流程全景图:一张图看懂扫码登录
在深入代码之前,我们必须把整个交互的“剧本”搞清楚。扫码登录不是单一的动作,而是PC网页、微信小程序、后端服务器三方协同演出的一场戏。这场戏大致可以分为四个关键幕次:
- 第一幕:生成凭证(PC端发起)。用户访问PC后台登录页,页面自动向后端请求一个登录二维码。后端生成一个全局唯一的
UUID作为本次登录会话的“票根”,并将这个UUID编码进二维码图片中,以Base64格式返回给前端展示。同时,后端在Redis等缓存中,以这个UUID为键,初始化一个状态记录,状态设为“未扫描”(NOT_SCAN),并设置一个有效期(比如5分钟)。 - 第二幕:扫描与状态更新(小程序端操作)。用户用微信小程序扫描屏幕上的二维码。小程序解析出二维码中的
UUID,然后调用后端接口,上报“我扫到UUID为XXX的码了”。后端收到请求后,将Redis中该UUID对应的状态从“未扫描”(NOT_SCAN)更新为“已扫描”(SCANNED)。此时,PC端通过轮询会感知到状态变化,页面提示“扫描成功,请在手机端确认”。 - 第三幕:授权与令牌下发(小程序端确认)。小程序端展示一个确认登录的界面,用户点击“确认登录”。小程序再次调用后端接口,传递
UUID进行最终授权。这是最关键的一步:后端根据小程序的登录凭证(通常是小程序wx.login获取的code换得的openid或unionid),查询出该微信用户绑定的后台系统用户。然后,为这个系统用户生成一个标准的登录令牌(Token),并将这个Token存入该UUID对应的Redis记录中。最后,将状态更新为“已校验”(VERIFIED)。 - 第四幕:登录完成(PC端轮询到结果)。PC端一直在轮询查询
UUID的状态。当轮询发现状态变为VERIFIED时,会同时从接口响应中拿到后端存入的Token。前端拿到这个Token,就像普通登录成功一样,将其存储到本地(如Cookie或LocalStorage),并跳转到系统主页,完成登录。
整个流程的核心在于**UUID和与之绑定的状态机**,以及Redis作为中间状态存储的桥梁作用。PC端和小程序端不直接通信,全部通过后端对同一个UUID的状态进行操作来实现间接“对话”。
3. 后端基石:状态定义与二维码生成服务
后端是整个流程的指挥中心,我们先从最基础的部分搭建起。
3.1 定义清晰的状态枚举
首先,我们需要用常量来明确定义二维码在整个生命周期中的几个状态,这能让代码更清晰,避免魔法字符串。我习惯单独建一个常量类。
package com.ruoyi.login.constant;
public class QrCodeStatus {
// Redis存储UUID键的前缀,方便管理和查找
public static final String UUID_KEY_PREFIX = "login:qrcode:";
// 状态:未扫描
public static final String NOT_SCAN = "NOT_SCAN";
// 状态:已扫描(等待确认)
public static final String SCANNED = "SCANNED";
// 状态:已确认(校验通过)
public static final String VERIFIED = "VERIFIED";
}
这里我加了一个UUID_KEY_PREFIX,这是实际项目中很好的实践。把所有扫码登录相关的Redis键都归拢到login:qrcode:这个命名空间下,比如键名会是login:qrcode:550e8400-e29b-41d4-a716-446655440000,这样在Redis可视化工具里一目了然,也避免了键名冲突。
3.2 生成携带UUID的二维码
生成二维码,我推荐使用谷歌的ZXing库,非常稳定。在若依的pom.xml中添加依赖:
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
然后创建一个生成二维码Base64字符串的服务。这里有个细节需要注意:直接生成的Base64字符串,前端<img>标签的src属性是无法直接识别的,需要加上前缀data:image/png;base64,。
@Service
public class ScanServiceImpl implements IScanService {
@Override
public String createQrCode(String content, int width, int height) throws IOException {
if (StringUtils.isEmpty(content)) {
return null;
}
// 使用ByteArrayOutputStream在内存中操作图片
ByteArrayOutputStream os = new ByteArrayOutputStream();
HashMap<EncodeHintType, Comparable> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); // 编码
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); // 容错率
hints.put(EncodeHintType.MA
4865



被折叠的 条评论
为什么被折叠?



