原由

近期一直在填自己一个基于SpringBoot图书馆系统项目的坑,突然想起来或许尝试与相关硬件联动,例如自助借还取柜机,方便读者更简便快捷的归还/借阅图书,那自助借还取柜机怎么实现鉴权呢?输入账户密码容易有泄露个人信息的风险,短信验证码又太过于浪费时间,也或多或少增加网站支出,身份(二维)码无疑是个不错的选择。
博主也是Java入门没有多久,可能有更好的实现思路,当时做这个功能的时候,网上也没有搜到相关的算法参考,自己利用现有的知识写了一个。

实现思路

后端

实现

首先通过HttpServletResponse对象获取到浏览器发送到后端的Cookie存入Cookies变量中,初始化identityCode(身份码)变量,并从HttpServletRequest对象中取出存在Session中的用户相关信息。(登录时自动将用户信息存入Session)

@GetMapping("/getIdentityCode")
    public Result<String> getIdentityCode(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();
        String identityCode = null;
        User user = (User) request.getSession().getAttribute("user");

判断Session取出的User对象是否为空,若为空,则表示用户未登录,直接return异常即可。

if (user == null) {
            return Result.error(0, "登录态失效, 请重新登录");
}

若取出的User对象不为空,则表示用户已经登录进行后续操作。遍历Cookies变量寻找键为identityCode的cookie,取出对应键的值并拼接redis的key。

for (Cookie cookie: cookies) {
            if (cookie.getName().equals("identityCode")) {
                identityCode = cookie.getValue();
            }
        }

String key = "user:identityCode:" + identityCode;

若identityCode变量不为空,则表示cookie内存在identityCode的值,则表示该用户并非第一次获取身份码,且上一次获取的身份码可能未过期,则查询redis内是否存在键为identityCode相关的键值对,若存在则表示该身份码此刻未过期,直接返回identityCode的值就可以。

 if (identityCode != null) {
            // 如果cookie中已经存在该key, 则直接返回identityCode
            if (redisTemplate.opsForValue().get(key) != null) {
                // 如果redis中已经存在该key, 则直接返回identityCode
                return Result.success(identityCode);

如果redis内并未获取到身份码(identityCode)的值则表示该用户上传获取身份码可能已过期,则重新生成UUID充当新的身份码(identityCode)的值,并将新生成的值添加到redis中,并设置有效期为60秒,并设置一个新的cookie存入identityCode的键值对,同样设置60秒的有效期,并把新的身份码返回给前端。

} else {
      identityCode = UUID.randomUUID().toString().replace("-", "");
      redisTemplate.opsForValue().set(key, user.getId(), 60, TimeUnit.SECONDS);
      Cookie cookie = new Cookie("identityCode", identityCode);
      cookie.setPath("/");
      cookie.setMaxAge(60);
      response.addCookie(cookie);
      return Result.success(identityCode);
}

如果cookie内并未获取到身份码(identityCode)的值则表示该用户可能未获取过身份码或上次获取的已经过期,则重新重复上一步骤,完成整个身份码获取的过程。

} else {
      identityCode = UUID.randomUUID().toString().replace("-", "");
      key = "user:identityCode:" + identityCode;
      redisTemplate.opsForValue().set(key, user.getId(), 60, TimeUnit.SECONDS);
      Cookie cookie = new Cookie("identityCode", identityCode);
      cookie.setMaxAge(60);
      return Result.success(identityCode);
}

全部代码

/**
     * 获取用户身份(二维)码
     * @param request 请求
     * @param response 响应
     * @return 用户身份码
     */
    @GetMapping("/getIdentityCode")
    public Result<String> getIdentityCode(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();
        String identityCode = null;
        User user = (User) request.getSession().getAttribute("user");

        // 判断session中是否有user对象
        if (user == null) {
            return Result.error(0, "登录态失效, 请重新登录");
        }

        // 获取cookie中的identityCode
        for (Cookie cookie: cookies) {
            if (cookie.getName().equals("identityCode")) {
                identityCode = cookie.getValue();
            }
        }

        // 拼凑key并取出user对象
        String key = "user:identityCode:" + identityCode;

        // 判断cookie内是否有身份码
        if (identityCode != null) {
            // 如果cookie中已经存在该key, 则查询redis内是否存在该key
            if (redisTemplate.opsForValue().get(key) != null) {
                // 如果redis中已经存在该key, 则直接返回identityCode
                return Result.success(identityCode);
            } else {
                // 如果不存在重新生成身份码
                identityCode = UUID.randomUUID().toString().replace("-", "");
                // 将identityCode存入redis
                redisTemplate.opsForValue().set(key, user.getId(), 60, TimeUnit.SECONDS);
                // 将identityCode存入cookie
                Cookie cookie = new Cookie("identityCode", identityCode);
                cookie.setPath("/");
                cookie.setMaxAge(60);
                response.addCookie(cookie);
                return Result.success(identityCode);
            }
        } else {
            // 如果不存在重新生成身份码
            identityCode = UUID.randomUUID().toString().replace("-", "");
            // 将identityCode存入redis
            key = "user:identityCode:" + identityCode;
            redisTemplate.opsForValue().set(key, user.getId(), 60, TimeUnit.SECONDS);
            // 将identityCode存入cookie
            Cookie cookie = new Cookie("identityCode", identityCode);
            cookie.setMaxAge(60);
            return Result.success(identityCode);
        }
    }

前端

为什么后端只返回身份码的值,而不直接返回二维码呢,当时的第一想法是直接从后端生成身份码的值后直接生成二维码在发送给前端,写完相关代码后,感觉如果在高并发的情况下,把生成二维码的工作也交给后端,无疑增加了服务器的压力,决定把生成二维码的工作交给前端。

前端直接引入QRCode.js库,发送请求到后端获取到身份码的值后前端生成二维码即可。

<body>
	<script src="js/qrcode.min.js"></script>
	<div id="qrcode"></div>
	<script type="text/javascript">
		new QRCode(document.getElementById("qrcode"), identityCode);
	</script>
</body>

成品展示


“最终,你受益最多的,是你学业之外的东西”