• 4-7:用Redis优化登陆模块


    相关功能

    • 使用Redis存储验证码
      • 验证码需要频繁的访问与刷新,对性能要求较高。
      • 验证码不需永久保存,通常在很时间后就会失效
        (Redis可以设置有效时间,分布式应用也可以绕过session共享的问题)
      • 分布式部署时,存在Session共享的问题。
    • 使用Redis存储登录凭证
      • 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高
    • 使用Redis缓存用户信息
      • 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高

    redis用户凭证存了,mysql则不用存,但用户需要存储;

    RedisKeyUtil.java

    添加如下内容

    private static final String PREFIX_KAPTCHA = "kaptcha";
     // 登录验证码
        public static String getKaptchaKey(String owner) {
            return PREFIX_KAPTCHA + SPLIT + owner;
            //不同用户的验证码不一样,不能用用户id;因为用户还没登陆
            //所以就要给予一个字符串,零时凭证,一定时间内失效。
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    打开Lontroller.java重构生成验证码的代码
     @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);//生存时间,60s
            cookie.setPath(contextPath);//有效路径,整个项目都有效
            response.addCookie(cookie);//发送给客户端
            // 将验证码存入Redis
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);//有了key,在key里存数据
    
            // 将突图片输出给浏览器
            response.setContentType("image/png");
            try {
                OutputStream os = response.getOutputStream();
                ImageIO.write(image, "png", os);
            } catch (IOException e) {
                logger.error("响应验证码失败:" + e.getMessage());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
        @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) {
            // 检查验证码
            // @CookieValue("kaptchaOwner") ,从cookie中取值的一个注解,要想要验证码,就要从redis里面取处key,要想要key,就要知道onwer,要想要owner就要从cookie里面取值.
            // String kaptcha = (String) session.getAttribute("kaptcha");
            String kaptcha = null;
            if (StringUtils.isNotBlank(kaptchaOwner)) {判断key不为空,验证码没有失效才能使用;
                String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);//得到key
                kaptcha = (String) redisTemplate.opsForValue().get(redisKey);//根据key得到验证码
            }
    
            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<String, Object> 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";
            }
        }
    
        @RequestMapping(path = "/logout", method = RequestMethod.GET)
        public String logout(@CookieValue("ticket") String ticket) {
            userService.logout(ticket);
            return "redirect:/login";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    实现登陆凭证的优化操作
    RedisKeyUtil.java
        // 登录的凭证
        public static String getTicketKey(String ticket) {
            return PREFIX_TICKET + SPLIT + ticket;//要传进来登陆成功的凭证
            //使用redis存这个凭证,代替那个LoginTicket表
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    找出需要使用的地方

    UserService.java

            // 生成登录凭证
            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);//将redisTemplate注入,然后将凭证存入到value中,可以以字符串的形式
    
            map.put("ticket", loginTicket.getTicket());
            return map;
        }
    
        public void logout(String ticket) {
    //        loginTicketMapper.updateStatus(ticket, 1);
            //登出时,把状态改为1,先在redis中存了,再取出来改,再存
            String redisKey = RedisKeyUtil.getTicketKey(ticket);
            LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
            loginTicket.setStatus(1);//改变状态
            redisTemplate.opsForValue().set(redisKey, loginTicket);//改变的值再重新存进去。
        }
    
        public LoginTicket findLoginTicket(String ticket) {//查询凭证的方法也需要重构
    //        return loginTicketMapper.selectByTicket(ticket);
            String redisKey = RedisKeyUtil.getTicketKey(ticket);//取出key
            return (LoginTicket) redisTemplate.opsForValue().get(redisKey);//根据key查询
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    重启项目,测试登陆
    发起任何访问,拦截器都会调用查找凭证的方法,重构函数成功。

    使用redis缓存用户信息

    一定要注意,信息需要过一段时间删掉,与上一个方法功能不同,凭证不会删除,指挥修改状态。

    在RedisUtils.java中添加

     // 用户
        public static String getUserKey(int userId) {
            return PREFIX_USER + SPLIT + userId;
        }
    
    • 1
    • 2
    • 3
    • 4

    重构UserService.java
    通过用户id查找用户,这个方法非常常见,于是将其信息存到redis中。

    /**
         * 在查询user时,先查询缓存数据,
         * 如查不到,则需要重新初始化;更改用户后(用户数据变化后),
         * 也要更新缓存或删除缓存;一般用删除操作
         * 都是私有的,给userService自己调用
         * **/
        // 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);//从mysql中查数据
            String redisKey = RedisKeyUtil.getUserKey(userId);
            //3600s,一个小时
            redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
            return user;//得到一个user
        }
    
        // 3.数据变更时清除缓存数据
        private void clearCache(int userId) {
            String redisKey = RedisKeyUtil.getUserKey(userId);
            redisTemplate.delete(redisKey);//将用户信息删除;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    再重构调用查询用户的方法

    public User findUserById(int id) {
    //        return userMapper.selectById(id);
            User user = getCache(id);
            if (user == null) {
                user = initCache(id);
            }
            return user;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    紧接着,再在需要修改用户的地方设置,将缓存清除;

    public int activation(int userId, String code) {
            User user = userMapper.selectById(userId);
            if (user.getStatus() == 1) {
                return ACTIVATION_REPEAT;
            } else if (user.getActivationCode().equals(code)) {
                userMapper.updateStatus(userId, 1);
                clearCache(userId);//清除用户的缓存
                return ACTIVATION_SUCCESS;
            } else {
                return ACTIVATION_FAILURE;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
        public int updateHeader(int userId, String headerUrl) {
    //        return userMapper.updateHeader(userId, headerUrl);
            int rows = userMapper.updateHeader(userId, headerUrl);
            clearCache(userId);//清除用户缓存
            return rows;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重启服务器,检查即可。
    在这里插入图片描述

    首页查询的每个帖子,都用到了缓存机制,虽然页面上没有变化,但内部的存储机制过程已经改变。

  • 相关阅读:
    Ton 区块链的官方 类ERC20-Token 智能合约代码-Transfer部分解析
    嵌入式数据库开发编程(四)——DDL、DML
    【进阶篇】基于 Redis 实现分布式锁的全过程
    基于协同过滤算法的电影推荐系统
    基于SourceForge.net+Java+JMS技术实现的分布式爬虫系统
    智能制造容器平台架构设计与实战
    DYVAL: GRAPH-INFORMED DYNAMIC EVALUATION OF LARGE LANGUAGE MODELS
    在 Meta 中避免 Presto 中的数据孤岛:从 Raptor 到 RaptorX 的旅程
    Codeforces Round #811 (Div. 3)
    “蔚来杯“2022牛客暑期多校训练营2
  • 原文地址:https://blog.csdn.net/qq_41026725/article/details/128172765