6.4 KiB
6.4 KiB
优化登录模块
使用 Redis 短暂存储验证码
- 验证码需要频繁刷新,对性能要求较高
- 验证码不需永久保存(设置在 Cookie 和 Redis 中的保留时间为 60 s)
- 分布式部署,存在 Session 共享问题
原来我们是将验证码存在 session 中,这里我们将其存入 redis 中
存入 redis 中的数据 key(kaptcha:随机字符串) value(验证码)
用户在输入验证码的时候,还没有进行登录,我们无法通过用户的 id 指明这个验证码是针对谁的。所以这里我们随机生成一个字符串,将其短暂的存入 cookie,使用这个字符串来标识这个用户
工具类:生成 Redis 的 Key
/**
* 生成 Redis 的 key
*/
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_KAPTCHA = "kaptcha"; // 验证码
/**
* 登录验证码(指定这个验证码是针对哪个用户的)
* @param owner 用户进入登录页面的时候,由于此时用户还未登录,无法通过 id 标识用户
* 随机生成一个字符串,短暂的存入 cookie,使用这个字符串来标识这个用户
* @return
*/
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
}
修改生成验证码的方法:
/**
* 生成验证码
* @param response
* @param session
*/
@GetMapping("/kaptcha")
public void getKaptcha(HttpServletResponse response, HttpSession session) {
// 生成验证码
String text = kaptchaProducer.createText(); // 生成随机字符
System.out.println("验证码:" + text);
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 {
ServletOutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error("响应验证码失败", e.getMessage());
}
}
使用 Redis 存储登录凭证
- 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高(登录凭证这个数据不需要删除,永久保存在 redis 中)
工具类:生成 Redis 的 Key
/**
* 生成 Redis 的 key
*/
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_TICKET = "ticket"; // 登录凭证
/**
* 登陆凭证
* @param ticket
* @return
*/
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
}
LoginTicket 这张表以及相关操作可以废弃了,不过我们最好不要直接就删除了。在 LoginMapper
类上面加上 @Deprecated
注解表示不推荐使用就好了。
修改 UserService 中生成/修改/查询登录凭证这三个方法
// 生成登录凭证
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey, loginTicket);
/**
* 用户退出(将凭证状态设为无效)
* @param ticket
*/
public void logout(String ticket) {
// loginTicketMapper.updateStatus(ticket, 1);
// 修改(先删除再插入)对应用户在 redis 中的凭证
String redisKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
loginTicket.setStatus(1);
redisTemplate.opsForValue().set(redisKey, loginTicket);
}
/**
* 根据 ticket 查询 LoginTicket 信息
* @param ticket
* @return
*/
public LoginTicket findLoginTicket(String ticket) {
// return loginTicketMapper.selectByTicket(ticket);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}
使用 Redis 缓存用户信息
- 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高(用户信息只在 Redis 中保存一段时间)
在 UserService 中添加以下三个方法:
- 优先从缓存中取值;
- 缓存中没有该用户信息时,则将其存入缓存;
- 用户信息变更时清除对应缓存数据;
/**
* 优先从缓存中取值
* @param userId
* @return
*/
private User getCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}
/**
* 缓存中没有该用户信息时,则将其存入缓存
* @param userId
* @return
*/
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;
}
/**
* 用户信息变更时清除对应缓存数据
* @param userId
*/
private void clearCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}
在修改 UserService 中其他方法的逻辑,将上面三个方法加入
/**
* 根据 Id 查询用户
* @param id
* @return
*/
public User findUserById (int id) {
// return userMapper.selectById(id);
User user = getCache(id); // 优先从缓存中查询数据
if (user == null) {
user = initCache(id);
}
return user;
}
/**
* 激活用户
* @param userId 用户 id
* @param code 激活码
* @return
*/
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;
}
}