后端通过下载页面URL与随机生成的UUID拼接成一个字符串,利用hutool工具包的生成二维码方法生成一个二维码。
利用下载页面URL的好处就是:自己的App扫码可以获取URL后面拼接的参数进行下一步逻辑操作。其他的App,例如QQ扫码就可以根据URL直接跳转到公司App的下载页面。
同时创建二维码信息实例对象qrCode,赋值二维码状态为0(待扫描),token为" "。
使用UUID做Key,qrCode做Value,过期时间为1小时(这个地方待确定),存储到redis中。
/**
* 生成二维码
*
* @return {@link RApp}<{@link ?}>
*/
@GetMapping("/generateQrCode")
public ModelAndView generateQrCode(HttpServletResponse response) {
//app下载页面链接
String url = "https://xxx/App/download/";
//随机值UUID
String code = UUID.fastUUID().toString();
try {
//生成二维码
String qrStr = String.format("%s?k=%s", url, code);
ServletOutputStream outputStream = response.getOutputStream();
BufferedImage bufferedImage = QrCodeUtil.generate(qrStr, QrConfig.create());
ImageIO.write(bufferedImage, "png", outputStream);
} catch (IOException e) {
e.printStackTrace();
}
//存储二维码信息到redis中,缓存1小时
QrCode qrCode = new QrCode();
//待扫描
qrCode.setState(0);
qrCode.setToken("");
redisService.setCacheObject(code, qrCode, (long) 60 * 60, TimeUnit.SECONDS);
return null;
}
根据App传过来的UUID,去redis中获取qrCode。
如果qrCode 不为空:
判断qrCode.getState()的值: 如果等于2,表示已经登录过了,返回”已在别处登录“提示。 如果不等于2,生成token,将token存储在内存map中,更新二维码状态为1(已扫描),将新的qrCode更新覆盖到redis中,返回”扫码成功“的提示给App端。
如果qrCode 为空:
表示这个二维码已过期,返回”二维码已失效/过期“提示。
/**
* token存储map
*/
private static final Map<String, String> TOKEN_MAP = new ConcurrentHashMap<>();
/**
* 已扫描,并生成token
*
* @param uuid uuid
* @return {@link RApp}<{@link ?}>
*/
@GetMapping("/generateToken")
public RApp<?> generateToken(String uuid) {
QrCode qrCode = redisService.getCacheObject(uuid);
if (qrCode != null) {
if (qrCode.getState() != 2) {
//通过base64将UUID转码生成token
String token = Base64.encode(IdUtils.fastUUID().getBytes(StandardCharsets.UTF_8));
//存储token至内存中
TOKEN_MAP.put(uuid, token);
//更新二维码状态为已扫描
qrCode.setState(1);
redisService.setCacheObject(uuid, qrCode);
return RApp.createBySuccessMsg("扫码成功");
} else {
return RApp.createByErrorMsg("已在别处登录");
}
}
return RApp.createByErrorMsg("二维码已失效");
}
获取App的当前登录用户信息loginAppUser,根据UUID获取redis中的qrCode
如果qrCode不为空:
判断marker的值: marker = 1:更新二维码状态为2(确定登录),从内存map中获取对应的token 判断token是否为空: 不为空,qrCode对象赋值token,然后移除内存map中的token,刷新loginAppUser用户信息(重置token,App用户会被挤掉) 为空,返回”登录异常,请重新扫码“提示 marker = 1:更新二维码状态为3(取消登陆) 将新的qrCode更新覆盖到redis中。
如果qrCode为空:
表示这个二维码已过期,返回”二维码已失效/过期“提示。
/**
* 确定登录
*
* @param marker 标记 (1 确定登录,2 取消登陆)
* @return {@link RApp}<{@link ?}>
*/
@GetMapping("/sureLogin")
public RApp<?> sureLogin(int marker, String uuid) {
//获取app用户信息
LoginAppUser loginAppUser = SecurityAppUtils.getLoginUserErr();
//获取二维码信息cache
QrCode qrCode = redisService.getCacheObject(uuid);
if (qrCode != null) {
print();//打印map的代码
if (1 == marker) {
//更新二维码状态为成功登录
qrCode.setState(2);
//从内存中获取token
String token = TOKEN_MAP.get(uuid);
if (StrUtil.isNotBlank(token)) {
qrCode.setToken(token);
//移除token
TOKEN_MAP.remove(uuid);
//刷新令牌有效期
loginAppUser.setToken(qrCode.getToken());
loginAppUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
tokenAppService.refreshToken(loginAppUser);
} else {
return RApp.createByError("登录异常,请重新扫码", -1);
}
} else {
//更新二维码状态为取消登陆
qrCode.setState(3);
}
redisService.setCacheObject(uuid, qrCode);
return RApp.createBySuccess("成功登录");
}
return RApp.createByError("二维码已失效", -1);
}
web端从获取到二维码之后,就一直调用该接口,监听的qrCode状态做处理。
/**
* 轮询二维码状态
*
* @param uuid uuid
* @return {@link RApp}<{@link ?}>
*/
@GetMapping("/getQrCodeState")
public RApp<?> pollingQrCodeState(String uuid) {
QrCode qrCode = redisService.getCacheObject(uuid);
if (qrCode == null) {
return RApp.createByErrorMsg("二维码已过期,请重新扫码");
}
return RApp.createBySuccess(qrCode);
}