牛客网后端项目实战(三十): 优化登录模块

发布时间:2023-03-30 17:30

文章目录

    • 缓存
    • 使用Redis存储验证码
      • RedisKey生成
      • 重写生成验证码
      • 验证码储存在Redis后的 login 方法
    • 使用Redis存储登录凭证
    • 使用Redis缓存用户信息

1、使用Redis存储验证码

  • 验证码需要频繁的访问与刷新,对性能要求较高。
  • 验证码不需永久保存,通常在很短的时间后就会失效。
  • 分布式部署时,存在Session共享的问题。

2、使用Redis存储登录凭证

  • 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。

3、使用Redis缓存用户信息

  • 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。

缓存

使用Redis存储验证码

LoginController写生成验证码并将验证码存入session里,但是会存在性能问题:

  • 验证码需要频繁的访问与刷新,对性能要求较高。
  • 验证码不需永久保存,通常在很短的时间后就会失效。
  • 分布式部署时,存在Session共享的问题。
  • 数据存放在服务端更加安全,但是也会增加服务端的内存压力。

所以将验证码存在Redis里,通过Key来获取验证码。但由于使用验证码时用户并没有登录,无法获得用户信息,所以随机生成短时间内的字符串,存入cookie里,作为凭证。

RedisKey生成

public class RedisKeyUtil {

    private static final String PREFIX_KAPTCHA = \"kaptcha\";
    // 登录验证码
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

重写生成验证码

@Controller
public class LoginController implements CommunityConstant {	

	...
    @Value(\"${server.servlet.context-path}\")
    private String contextPath;

    @RequestMapping(path = \"/kaptcha\", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {
        // 生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);

        // 将验证码存入session
        // session.setAttribute(\"kaptcha\", text);

        // 验证码的归属
        String kaptchaOwner = CommunityUtil.generateUUID();
        Cookie cookie = new Cookie(\"kaptchaOwner\", kaptchaOwner);
        cookie.setMaxAge(60);            // 设置cookie生存时间
        cookie.setPath(contextPath);  // 设置cookie生效范围
        response.addCookie(cookie);
        // 将验证码存入Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

        // 将突图片输出给浏览器
        response.setContentType(\"image/png\");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, \"png\", os);
        } catch (IOException e) {
            logger.error(\"响应验证码失败:\" + e.getMessage());
        }
    }

1、生成验证码,但不再存放在Session
2、随机生成字符串 kaptchaOwner 存入cookie里
3、根据 kaptchaOwner 生成对应 RedisKey,并将验证码存入Redis里。
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS); 这里使用了String类型:60表示存活时间,TimeUnit.SECONDS 是时间单位

验证码储存在Redis后的 login 方法

    @RequestMapping(path = \"/login\", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, /*HttpSession session, */HttpServletResponse response,
                        @CookieValue(\"kaptchaOwner\") String kaptchaOwner) {
        // 检查验证码
        // String kaptcha = (String) session.getAttribute(\"kaptcha\");
        String kaptcha = null;
        if (StringUtils.isNotBlank(kaptchaOwner)) {
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
        }

        if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute(\"codeMsg\", \"验证码不正确!\");
            return \"/site/login\";
        }

        // 检查账号,密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map map = userService.login(username, password, expiredSeconds);
        if (map.containsKey(\"ticket\")) {
            Cookie cookie = new Cookie(\"ticket\", map.get(\"ticket\").toString());
            cookie.setPath(contextPath);
            cookie.setMaxAge(expiredSeconds);
            response.addCookie(cookie);
            return \"redirect:/index\";
        } else {
            model.addAttribute(\"usernameMsg\", map.get(\"usernameMsg\"));
            model.addAttribute(\"passwordMsg\", map.get(\"passwordMsg\"));
            return \"/site/login\";
        }
    }

使用Redis存储登录凭证

之前的登录信息是构造了 LoginTicket 实体类,并通过LoginTicketMapper储存在数据库里。

1、LoginTicketMapper 设置为过期

@Mapper
@Deprecated
public interface LoginTicketMapper {

}

2、重构UserService
1)登录时,将生成的登录凭证放入Redis

         // 生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insertLoginTicket(loginTicket);

        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);

        map.put(\"ticket\", loginTicket.getTicket());

2)退出功能
从Redis取值,强转成 loginTicket 类,将登录凭证的状态修改为1,然后重新传入Redis里。

    public void logout(String ticket) {
//        loginTicketMapper.updateStatus(ticket, 1);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);
        redisTemplate.opsForValue().set(redisKey, loginTicket);
    }

3)查询凭证
由于登录时生成的登录凭证放入了Redis里,所以查询登录凭证时直接从Redis里取

    public LoginTicket findLoginTicket(Strinqvg ticket) {
//        return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

使用Redis缓存用户信息

1、首先封装缓存操作
优先从缓存中取值,取不到时初始化缓存数据,数据变更时清除缓存数据

    // 1.优先从缓存中取值
    private User getCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2.取不到时初始化缓存数据
    private User initCache(int userId) {
        User user = userMapper.selectById(userId);
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
        return user;
    }

    // 3.数据/User 变更时清除缓存数据
    // 即更改User中任何一属性时,调用该方法,清除缓存
    private void clearCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }

2、重构 findUserById

    public User findUserById(int id) {
//        return userMapper.selectById(id);
        User user = getCache(id);
        if (user == null) {
            user = initCache(id);
        }
        return user;
    }

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号