2、使用Redis存储登录凭证
3、使用Redis缓存用户信息
LoginController写生成验证码并将验证码存入session里,但是会存在性能问题:
所以将验证码存在Redis里,通过Key来获取验证码。但由于使用验证码时用户并没有登录,无法获得用户信息,所以随机生成短时间内的字符串,存入cookie里,作为凭证。
public class RedisKeyUtil {
private static final String PREFIX_KAPTCHA = "kaptcha";
// 登录验证码
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
@Controller
public class LoginController implements CommunityConstant {
...
@Value("${server.servlet.context-path}")
private String contextPath;
@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);
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、生成验证码,但不再存放在Session
2、随机生成字符串 kaptchaOwner 存入cookie里
3、根据 kaptchaOwner 生成对应 RedisKey,并将验证码存入Redis里。
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS); 这里使用了String类型:60表示存活时间,TimeUnit.SECONDS 是时间单位
@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;
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 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";
}
}
之前的登录信息是构造了 LoginTicket 实体类,并通过LoginTicketMapper储存在数据库里。
1、LoginTicketMapper 设置为过期
@Mapper
@Deprecated
public interface LoginTicketMapper {
}
2、重构UserService
1)登录时,将生成的登录凭证放入Redis
// 生成登录凭证
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);
map.put("ticket", loginTicket.getTicket());
2)退出功能
从Redis取值,强转成 loginTicket 类,将登录凭证的状态修改为1,然后重新传入Redis里。
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);
}
3)查询凭证
由于登录时生成的登录凭证放入了Redis里,所以查询登录凭证时直接从Redis里取
public LoginTicket findLoginTicket(Strinqvg ticket) {
// return loginTicketMapper.selectByTicket(ticket);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}
1、首先封装缓存操作
优先从缓存中取值,取不到时初始化缓存数据,数据变更时清除缓存数据
// 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.数据/User 变更时清除缓存数据
// 即更改User中任何一属性时,调用该方法,清除缓存
private void clearCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}
2、重构 findUserById
public User findUserById(int id) {
// return userMapper.selectById(id);
User user = getCache(id);
if (user == null) {
user = initCache(id);
}
return user;
}