问答平台(4),优化登录模块

Redis 存储验证码

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

工具类

  • RedisKeyUtil:增加内容
    1
    2
    3
    4
    5
    6
    private static final String PREFIX_KAPTCHA = "kaptcha";

    // 登录验证码
    public static String getKaptchaKey(String owner) {
    return PREFIX_KAPTCHA + SPLIT + owner;
    }

表现层

  • LoginController:重构
    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
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKpatcha(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.setPath(contextPath);
    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());
    }
    }

    @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;
    // Redis中取验证码
    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<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";
    }
    }

Redis 存储登录凭证

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

工具类

  • RedisKeyUtil:增加内容
    1
    2
    3
    4
    5
    6
    private static final String PREFIX_TICKET = "ticket";

    // 登录的凭证
    public static String getTicketKey(String ticket) {
    return PREFIX_TICKET + SPLIT + ticket;
    }

数据访问层

  • LoginTicketMapper:修改,不推荐使用
    1
    @Deprecated

业务层

  • UserService:重构
    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
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    // @Autowired
    // private LoginTicketMapper loginTicketMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    public Map<String, Object> login(String username, String password, int expiredSecond) {
    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() + expiredSecond * 1000));
    // loginTicketMapper.insertLoginTicket(loginTicket);

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

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

    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);
    }

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

Redis 缓存用户信息

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

工具类

  • RedisKeyUtil:增加内容
    1
    2
    3
    4
    5
    6
    private static final String PREFIX_USER = "user";

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

业务层

  • UserService:重构
    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
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public User findUserById(int id) {
    // return userMapper.selectById(id);
    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)) {
    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;
    }

    // 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);
    }

问答平台(4),优化登录模块
https://lcf163.github.io/2020/05/28/问答平台(4),优化登录模块/
作者
乘风的小站
发布于
2020年5月28日
许可协议