疫情防控交流社区平台——4.3 Redis一站式高性能存储(优化登录模块)

🌕Redis一站式高性能存储(优化登录模块)

在这里插入图片描述

一、Redis存储验证码

1.RedisKeyUtil

/*生成redis的key的工具类,方便复用*/
public class RedisKeyUtil {

	private static final String PREFIX_KAPTCHA = "kaptcha"; //验证码

	//5.登录验证码
    public static String getKaptchaKey(String owner){
        //获取登录验证码的时候,验证码应该和某一个用户是相关的,不同的用户验证码是不一样的,我们要识别出来这个验证码是属于哪个用户.
        //可能想加一个userId,这个不对。验证码的作用是辅助用户去登录的,当他打开验证页面看到验证码的时候,这个时候他还没登陆,我们根本不知道他是哪个user,不知道它的userId,这个时候还传不了userId
        //这个时候怎么办呢?还要识别用户是谁。可以这样,在用户访问登录页面的时候,给他打一个凭证,这个凭证是一个随机字符串,发给他,存到cookie里;我们以字符串来标识这个用户,临时标一下就可以了,然后很快让这个字符串过期就行了
        return PREFIX_KAPTCHA + SPLIT + owner;
    }
}

2.LoginController

对 “生成验证码的方法 ”进行重构;
原来是把验证码存到session里的,现在不用它,把它注释掉,
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

//生成验证码的方法
    @RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response/*, HttpSession session*/){
        //生成验证码
        String text = kaptchaProducer.createText();                 //先 生成字符串text
        BufferedImage image = kaptchaProducer.createImage(text);    //利用字符串text去生成一个图片
        //※ 将验证码存入session
        //session.setAttribute("kaptcha",text);

        //※ 取代上面——存到redis里,首先要构造一个key,而key又需要验证码的归属者
        //1.1验证码的归属
        String kaptchaOwner = CommunityUtil.generateUUID();//这个凭证要发给客户端,用cookie保存
        Cookie cookie = new Cookie("kaptchaOwner",kaptchaOwner);
        //1.2设置生存时间
        cookie.setMaxAge(60);           //1分钟
        cookie.setPath(contextPath);    //路径:整个项目下都有效
        //1.3都设置完了以后,把它发送给客户端
        response.addCookie(cookie);
        //2.1将验证码存入到 Redis
        //拼key
        String redisKey= RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        //有了key以后,就往redis里存数据
//        redisTemplate.opsForValue().set(redisKey,text);
        redisTemplate.opsForValue().set(redisKey,text,60, TimeUnit.SECONDS);
        //再调redisTemplate给key设置一个有效的时间(或者也可以在这直接设置——方法有重载的方法,如上)


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

    }

当我们首次访问登录页面的时候,验证码就生成了也存到了Redis里;存进去什么时候用呢?就是我们在登录进行具体的验证的时候要用,所以 “登录操作请求” login方法也要处理。
之前我是从session里把验证码取到的,现在的话得从Redis里取。首先还是注释掉参数session
在这里插入图片描述
然后呢,kaptcha需要单独从Redis里取,这个也好办。
在这里插入图片描述

	//※登录操作请求
    @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){
        /*参数说明:
        code —— 用户前端输入的验证码,传到后端来的
        boolean rememberme —— 判断前端是否勾了记住我的选项,有就把凭证时间放长一点
        session —— 页面传进来验证码,我需要取到和之前生成的验证码去对比,所以从session里取出验证码
        response —— 登陆成功,要把ticket发给客户端让它好保存,所以用cookie保存;要想创建cookie就得用HttpServletResponse。
        */

        //检查验证码
        /*将验证码从session取出*/
        /*String kaptcha = (String) session.getAttribute("kaptcha");*/
        /*将验证码和用户传入的code验证码相比*/

        /*从Redis里取kaptcha*/
        //1.变量声明好,默认为null
        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)){
            /*kaptcha、code为空,不对两者不相等,不对。三个判断*/
            model.addAttribute("codeMsg","验证码不正确!");
            return "/site/login";
        }

        //检查账号,密码
        /*调业务层来处理。调service的时候,不止要存传账号密码,还要传一个ticket凭证过期的时间*/
        /*逻辑:如果没有勾上'记住我',依然把它存到库里,只是凭证时间短一点;勾上‘记住我’,依然存库里,时间会长
        *      所以定义两个常量时间 ———— 写在CommunityConstant中*/
        long 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生效路径,全项目,但是不能写死,所以将路径注入进来 —— @Value("${server.servlet.context-path}")  private String contextPath;
            cookie.setMaxAge((int) expiredSeconds); //设置cookie有效时间,就是凭证时间
            response.addCookie(cookie);//将cookie发送给页面;响应时就会发送给浏览器
            return "redirect:/index";
        }else {
            model.addAttribute("usernameMsg",map.get("usernameMsg"));
            model.addAttribute("passwordMsg",map.get("passwordMsg"));
            return "/site/login";
        }
    }

3.测试

如果能登录进去,说明就是没问题,因为代码我已经把session注掉了。同时看Redis第12(11)个库就知道。
在这里插入图片描述
在这里插入图片描述
成功!

二、Redis存储登录凭证

2.1 ※详细说明

使用Redis去存登录凭证代替之前的那个login_ticket表,可以把之前的LoginTicketMapper废弃掉(不用删,类上加一个@Deprecated就行:声明组件不推荐使用)
然后需要去重构一个之前用到LoginTicketMapper这个bean的地方:

  • 1.UserService里登录成功以后,我们生成了一个凭证,做了一个保存;
  • 2.我们登录以后存了凭证,还要注意退出登录的时候,还要把凭证删掉,这里也要处理;
  • 3.UserService里还写过一个查询凭证的方法,其他地方都是调这个方法去查凭证的,这个地方也要重构。

解决步骤
1.来到UserService,因为LoginTicketMapper不推荐使用了,所以@Autowired可以注释掉,引入RedisTemplate进来。
一旦注释掉,下面代码中调用LoginTicketMapper的部分代码就必定报红,这时只需要去将报红的淦掉,之前的service中对mapper及数据库的操作全部由Redis来代替。
1.添加重构
在这里插入图片描述
2.退出登录重构
在这里插入图片描述
3.查询凭证重构
在这里插入图片描述

2.1.1 RedisKeyUtil

/*生成redis的key的工具类,方便复用*/
public class RedisKeyUtil {

	private static final String PREFIX_TICKET = "ticket";       //登录凭证

	...

	//6.登录凭证
    public static String getTicketKey(String ticket){
        //因为要获得登录凭证的详细数据,所以要传进来登陆成功的凭证
        return PREFIX_TICKET + SPLIT + ticket;
    }

}

2.1.2 LoginTicketMapper

在这里插入图片描述
在这里插入图片描述

2.1.3 UserService

	//※登录的方法
    public Map<String,Object> login(String username,String password,Long expiredSeconds){
        HashMap<String, Object> map = new HashMap<>();
		...
        //5.如果走到这,就说明都是成功的!所以就要开始生成登录凭证给你
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());//查出的用户id设置进去
        loginTicket.setTicket(CommunityUtil.generateUUID());//工具类生成的随机字符串设置进去
        loginTicket.setStatus(0); /*0凭证有效  1凭证无效*/
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds*1000));
        //6.进行添加操作
            /*loginTicketMapper.insertLoginTicket(loginTicket);*/
        /*redis进行添加操作*/
        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        //这是一个LoginTicket对象,下面怎么按字符串来寸呢?我们可以把对象序列化成一个json字符串存进去就完了
        redisTemplate.opsForValue().set(redisKey,loginTicket);//然后把对象loginTicket传进来,redisTemplate会把对象序列化成一个json字符串

        map.put("ticket",loginTicket.getTicket());
        return map;
    }

    //※退出登录
    /*退出的时候,要把凭证传过来,服务端就知道是谁要退出*/
    public void logout(String ticket){
        /*int i = loginTicketMapper.updateStatus(ticket,1);*/
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        //有了key以后,我们要先把loginTicket取出来,把对象的状态改完以后再存回去
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);//强转
        loginTicket.setStatus(1);//1表示删除态,然后存回去即可
        redisTemplate.opsForValue().set(redisKey,loginTicket);
    }

    //查询凭证的方法
    public LoginTicket findLoginTicket(String ticket){
        /*return LoginTicket loginTicket = loginTicketMapper.selectByTicket(ticket);*/
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);//强转
    }

2.2 测试

在这里插入图片描述
成功!

三、Redis缓存用户信息

缓存:只是暂时存,数据过一会就要删掉。
做缓存基本就是这几步:(查user为例)

  • 1.查User时,先尝试从缓存中取值;
  • 2.取到就用,没取到说明数据没有初始化进去,那就要做初始化 ;这就能把findUserById传给我;
  • 3.有些地方我们会改变这些数据,比如修改密码,头像等;这个时候改数据以后,缓存也得更新一下。
    两种做法:一种更新缓存,另一种直接把缓存删了(一般用这个),下次再查的话就再数据库访问一次。第一种可能会有并发的问题,第二种更干脆。

这个实现也是重构UserService。在UserService里封装这三步的每一个方法,然后哪里要用就直接调,如下;

3.1 RedisKeyUtil

/*生成redis的key的工具类,方便复用*/
public class RedisKeyUtil {

	private static final String PREFIX_USER = "user";           //用户

	...

	//7.用户
    public static String getUserKey(int userId){
        return PREFIX_USER + SPLIT +userId;
    }

}

3.2 UserService

在这里插入图片描述
后面的话,主要就是看一下哪个地方我们是修改了user,改的地方都要把缓存清理

在这里插入图片描述

	...
	//1.根据用户id查询用户名
    public User findByUserId(int id){
        /*return userMapper.selectById(id);*/
        //首先从cache查
        User user = getCache(id);
        if (user==null){
            //判断,没有就对它做缓存
            user = initCache(id);
        }
        return user;
    }
    ...
    //邮件激活的方法
    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)){
            //如果用户的激活码和数据库的对比是一样的,就成功!并且将用户状态从0修改为1
            userMapper.updateStatus(userId,1);
            /*清除缓存*/
            clearCache(userId);
            return ACTIVATION_SUCCESS;
        }else {
            return ACTIVATION_FAILURE;
        }
    }
    ...
    //更新用户头像
    public int updateHeaderByUserId(int userId,String header){
        int i = userMapper.updateHeader(userId, header);
        clearCache(userId);
        return i;
    }
    ...
	/*缓存用户信息
    * 这是要做的三件事,然后将其封装成三个方法,其他地方要就直接调
    * */
    //1.优先从缓存中取值
    private User getCache(int userId){
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);//强转型
    }

    //2.取不到数据就初始化缓存数据(说白了就是添加user信息到redis)
    private User initCache(int userId){
        User user = userMapper.selectById(userId);//通过用户id查到用户信息
        String redisKey = RedisKeyUtil.getUserKey(userId);//构建key
        redisTemplate.opsForValue().set(redisKey,user,3600, TimeUnit.SECONDS);//将user添加到redis里
        return user;
    }

    //3.数据变更时,清除缓存数据
    private void clearCache(int userId){
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }

3.3 测试

打断点
在这里插入图片描述
运行程序,进入首页,然后发现就已经卡到了,然后后端接收到了参数:
在这里插入图片描述
然后取消断点继续运行F9:
在这里插入图片描述
立马进来了!
然后接着一样的地方打断点,重新启动程序,这次看有没有进来,发现已经直接进来了,说明已经有缓存了!
然后查看redis:
在这里插入图片描述
ok,成功!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

11_1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值