实现功能
在最初是做项目时一些数据采用的是mysql存储,但一些功能需要频繁进行访问的时候可以将这些数据采用redis存储来提高性能
使用Redis存储验证码
- 验证码需要频繁的访问与刷新,对性能要求较高。
- 验证码不需永久保存,通常在很短的时间后就会失效。
- 分布式部署时,存在Session共享的问题。
用来存储验证码的key
public class RedisKeyUtil {
private static final String SPLIT=":";
public static final String PREFIX_KAPTCHA="kaptcha"; //验证码
//获取验证码的key:当用户到登录页面时,需要给验证码下发一个临时凭证来进行验证码归属
public static String getKaptchaKey(String owner){
return PREFIX_KAPTCHA+SPLIT+owner;
}
}
重构项目代码1:生成验证码,存入redis
@RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response/* , HttpSession session */){
//生成验证码(需注入bean
String text = defaultKaptcha.createText();
BufferedImage image = defaultKaptcha.createImage(text);
//原功能:将验证码存入session
//session.setAttribute("kaptcha",text);
/**
* 性能优化
*/
//现功能:将验证码存入redis
//验证码的归属:为用户临时生成一个凭证
String kaptchaOwner=CommunityUtil.generateUUID();
//将登录凭证存放进cookie中
Cookie cookie=new Cookie("kaptchaOwner",kaptchaOwner);
//设置cookie最大生存时间
cookie.setMaxAge(60);
//设置cookie有效路径
cookie.setPath(contextPath);
response.addCookie(cookie);
//生成验证码key
String kaptchaKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
//将验证码存入redis,并设置过期时间
redisTemplate.opsForValue().set(kaptchaKey,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());
}
}
重构项目代码2:在登陆界面进行验证码验证
/**
*
* @param userName 用户名
* @param password 密码
* @param code 验证码
* @param model 封装返回的数据
* @param rememberMe 是否勾选记住我
//* @param session 从session中取出生成的验证码
* @param response 如果登陆成功,将用户登录状态ticket存入客户端(cookie
*/
@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.isNoneBlank(kaptchaOwner)){ //验证码未过期
//生成验证码key
String kaptchaKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
//从redis中取对应的验证码
kaptcha = (String) redisTemplate.opsForValue().get(kaptchaKey);
}
//得到的验证码和用户传入的验证码进行相比
if(StringUtils.isBlank(kaptcha)||StringUtils.isBlank(code)||(!kaptcha.equalsIgnoreCase(code))){
//验证码错误返回提示,将提示封装进model中
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")){
//验证成功:将ticket存入cookie中
Cookie cookie=new Cookie("ticket",map.get("ticket").toString());
cookie.setPath(contextPath);//设置cookie有效路径
cookie.setMaxAge(expiredSeconds);//设置cookie有效时间
response.addCookie(cookie);//将cookie发送给浏览器(将cookie存入到response头部
return "redirect:/index"; //跳转至首页
}else{
//验证失败
model.addAttribute("userNameMsg",map.get("userNameMsg"));
model.addAttribute("passwordMsg",map.get("passwordMsg"));
return "/site/login";//回到登录页面
}
}
使用Redis存储登录凭证
- 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
用来存储登录凭证的key
public class RedisKeyUtil {
private static final String SPLIT=":";
public static final String PREFIX_TICKET="ticket"; //登录凭证
//获取登录凭证key
public static String getTicketKey(String ticket){
return PREFIX_TICKET+SPLIT+ticket;
}
}
重构项目代码1:将生成的登录凭证存入redis中
//用户登录验证(用户登录凭证
public Map<String,Object> login(String userName,String password,int expiredSeconds){
Map<String,Object> map=new HashMap<>();//将验证信息封装进map中
//账号不能为空
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(!password.equals(user.getPassword())){
map.put("passwordMsg","密码错误!");
return map;
}
//验证通过,生成用户凭证
LoginTicket loginTicket=new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());//生成随机字符串
loginTicket.setStatus(0);//账号登录状态0:未过期
loginTicket.setExpired(new Date(System.currentTimeMillis()+expiredSeconds*1000));
//原来:loginTicketMapper.insertLoginTicket(loginTicket);//存入用户登录凭证
//现在
//生成key
String ticketKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
//将登录凭证存入redis
redisTemplate.opsForValue().set(ticketKey,loginTicket);
map.put("ticket",loginTicket.getTicket());//将ticket存入,方便传给浏览器
return map;
}
重构项目代码2:更改登录凭证状态
public void logout(String ticket){
//原来:loginTicketMapper.updateStatus(ticket,1);
//现在:更改redis中登录凭证状态 :先取、在更改、再存入
//生成key
String ticketKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(ticketKey);
loginTicket.setStatus(1);
redisTemplate.opsForValue().set(ticketKey,loginTicket);
}
重构项目代码3:获取登录凭证
//获取登录凭证ticket
public LoginTicket findLoginTicket(String ticket){
//原来:return loginTicketMapper.selectByTicket(ticket);
//现在
//生成key
String ticketKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(ticketKey);
}
使用Redis缓存用户信息
- 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高
重构userService
//1、优先从缓存中取值
public User getCache(int userId){
//构建用户key
String userKey=RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(userKey);
}
//2、若缓存中无值,则从mysql中查找,后在写入缓存中
public User initCache(int userId){
User user = userMapper.selectById(userId);
//构建用户key
String userKey=RedisKeyUtil.getUserKey(userId);
redisTemplate.opsForValue().set(userKey,user,3600, TimeUnit.SECONDS);
return user;
}
//3、当用户信息发生更改时清除缓存
public void clearCache(int userId){
//构建用户key
String userKey=RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(userKey);
}
重构项目代码1:从缓存中查找用户
public User findUserById(int id){
// 之前:return userMapper.selectById(id);
User user = getCache(id); //先从缓存中查
if(user==null){
user = initCache(id); //缓存中没有
}
return user;
}
重构项目代码2:修改数据后清除缓存
//编写激活业务
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; //激活码不等
}
}
//更改头像信息(headerUrL
public int updateHeaderUrl(int userId,String headerUrl){
//之前:return userMapper.updateHeader(userId,headerUrl);
int rows = userMapper.updateHeader(userId, headerUrl);
clearCache(userId);
return rows;
}
//修改密码
public int updatePassword(int userId,String password){
//之前:return userMapper.updatePassword(userId,password);
int rows = userMapper.updatePassword(userId, password);
clearCache(userId);
return rows;
}