使用Redis存储验证码
(1)验证码需要频繁的访问与刷新,对性能要求较高。
(2)验证码不需永久保存,通常在很短的时间后就会失效。
(3)分布式部署时,存在Session共享的问题。
使用Redis存储登录凭证
(1)处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
使用Redis缓存用户信息
(1)处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。
一、在RedisKeyUtil类里,添加
private static final String PREFIX_KAPTCHA = "kaptcha";
//登录的凭证
public static String getTicketKey(String ticket){
return PREFIX_TICKET + SPLIT + ticket;
}
在LoginController类里,重构getKaptcha和login方法
@RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) { //原来是把验证码放到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); //设置过期时间为60秒
//将图片输出给浏览器
response.setContentType("image/png");
try {
OutputStream os = response.getOutputStream();
ImageIO.write(image,"png",os);
//不用手动关闭,spring MVC会自动处理
} catch (IOException e) {
//e.printStackTrace();
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;
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";
}
二、在RedisKeyUtil类里添加
private static final String PREFIX_TICKET = "ticket";
//登录的凭证
public static String getTicketKey(String ticket){
return PREFIX_TICKET + SPLIT + ticket;
}
把LoginTicketMapper类加上@Deprecated注解。
在UserService里进行修改login方法、logout方法、findLoginTicket方法
// loginTicketMapper.insertLoginTicket(loginTicket);
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey,loginTicket);
// loginTicketMapper.updateStatus(ticket,1);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
loginTicket.setStatus(1);
redisTemplate.opsForValue().set(redisKey,loginTicket); //把新改变的值覆盖了原有的值
// return loginTicketMapper.selectByTicket(ticket);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket)redisTemplate.opsForValue().get(redisKey);
三、在RedisKeyUtil类里添加
private static final String PREFIX_USER = "user";
//用户
public static String getUserKey(int userId){
return PREFIX_USER + SPLIT + userId;
}
在UserService类里,添加
//1.优先从缓存中取值(先尝试从缓存取用户数据)
private User getCache(int userId){
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}
//2、取不到时初始化缓存数据(这个数据来源于mysql)
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);
}
然后改写findUserById方法
public User findUserById(int id) {
// return userMapper.selectById(id);
User user = getCache(id); //先尝试从缓存读数据
if(user==null){
user = initCache(id);
}
return user;
}
改写activation方法
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;
}
}
改写updateHeader方法
public int updateHeader(int userId,String headerUrl){
// return userMapper.updateHeader(userId,headerUrl);
int rows = userMapper.updateHeader(userId,headerUrl);
clearCache(userId); //加入清除缓存的动作
return rows;
}
可以用压测工具测一下,优化前后的QPS。