4.7 牛客网项目——优化登录模块

在这里插入图片描述

1. 使用Redis存储验证码

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

1.1 生成Redis的Key

util包下的RedisKeyUtil类下为其生成Key。

public class RedisKeyUtil {
    private static final String PREFIX_KAPTCHA = "kaptcha"; //验证码
    //登录验证码
    //oener验证码的拥有者,临时凭证
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }
}

1.2 优化生成验证码的方法

controller包下的LoginController类下的getKaptcha()方法对其进行优化:不再将验证码存入session中,而将其存入Redis中。需要服务端临时给客户端颁发一个凭证来判断验证码的归属,因此生成一个随机字符串发送给客户端并用cookie保存起来。

@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);
    //验证码存的是一个字符串,用redis的string类型
    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.3 优化登录方法

controller包下的LoginController类下的login()方法对其进行优化:不再从session中获取验证码,转而从cookie中获取临时颁发的凭证,然后拼接获得RedisKey,从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) {
    //从session中获取验证码,转为String类型
    //String kaptcha = (String) session.getAttribute("kaptcha");

	//1.检查验证码
    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";
    }

    //2.检查账号,密码
    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";
    }
}

2. 使用Redis存储登录凭证

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

2.1 生成Redis的Key

util包下的RedisKeyUtil类下为其生成Key。

public class RedisKeyUtil {
    private static final String PREFIX_TICKET = "ticket"; //登录凭证
    //登录凭证
    public static String getTicketKey(String ticket) {
        return PREFIX_TICKET + SPLIT + ticket;
    }
}

2.2 删除存储登录凭证的相关Mapper

dao包下的LoginTicketMapper类中添加注解@Deprecated用来标识此类不建议使用。

2.3 优化登录方法中的生成凭证

//登录实现
public Map<String, Object> login(String username, String password, int expiredSeconds) {
    Map<String, Object> map = new HashMap<>();
    //空值处理
    if (StringUtils.isBlank(username)) {
        map.put("usernameMsg", "账号不能为空!");
        return map;
    }
    if (StringUtils.isBlank(password)) {
        map.put("passwordMsg", "密码不能为空!");
        return map;
    }
    //验证账号
    User user = userMapper.selectByName(username);
    if (user == null) {
        map.put("usernameMsg", "该账号不存在!");
        return map;
    }
    //验证状态
    if (user.getStatus() == 0) {
        map.put("usernameMsg", "该账号未激活!");
        return map;
    }
    //验证密码
    //先把传入的明文密码,按照一定的规则加密,再与数据库中的加密密码作比较
    password = CommunityUtil.md5(password + user.getSalt());
    if (!user.getPassword().equals(password)) {
        map.put("passwordMsg", "密码不正确!");
        return map;
    }
    //生成登录凭证
    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());
    //redis会将loginTicket对象序列成为JSON格式的字符串进行保存
    redisTemplate.opsForValue().set(redisKey, loginTicket);

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

2.4 优化退出登录方法中的修改凭证状态

//退出登录实现
public void logout(String ticket) {
    //1表示无效
    //loginTicketMapper.updateStatus(ticket, 1);

    String redisKey = RedisKeyUtil.getTicketKey(ticket);
    LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    loginTicket.setStatus(1);
    redisTemplate.opsForValue().set(redisKey, loginTicket);
}

2.5 优化查找凭证方法

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

3. 使用Redis缓存用户信息

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

3.1 生成Redis的Key

util包下的RedisKeyUtil类下为其生成Key。

public class RedisKeyUtil {
    private static final String PREFIX_USER = "user";
    //用户
    public static String getUserKey(int userId) {
        return PREFIX_USER + SPLIT + userId;
    }
}

3.2 实现从缓存中获取用户信息

service包下的UserService类下实现getCache()方法优先从缓存中取值、initCache()方法如果取不到时就初始化缓存数据、clearCache()方法当数据变更时清除缓存数据。

// 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.当数据变更时清除缓存数据
private void clearCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.delete(redisKey);
}

3.3 优化根据用户id查询用户信息方法

//根据userId查询用户的具体信息
public User findUserById(int id) {
    //return userMapper.selectById(id);
    
    User user = getCache(id);
    if (user == null) {
    	//如果user为空,说明此时从缓存中取不到值,因此进行初始化
        user = initCache(id);
    }
    return user;
}

3.4 对产生数据变更的方法清除缓存中的用户信息

//激活用户
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; //激活失败
    }
}
//更新用户的头像
public int updateHeader(int userId, String headerUrl) {
    //return userMapper.updateHeader(userId, headerUrl);
    
    int rows = userMapper.updateHeader(userId, headerUrl);
    clearCache(userId);
    return rows;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值