目录
1. 使用 Redis 优化登陆模块
1.1 使用 Redis 存储验证码
1.2 使用 Redis 存储登录凭证
1.3 使用 Redis 缓存用户信息
在 RedisKeyUtil 类中添加:
private static final String PREFIX_KAPTCHA = "kaptcha";
// 登录验证码
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
验证码在登陆功能中使用(修改 LoginController 类中的生成验证码的方法):
//验证码在登陆功能中使用,重构获取验证码方法:之前是把验证码存入 Session 中,现在使用 Redis
//生成验证码的方法
@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);
//将验证码存入 Redis 中:首先构造 key,而 key 需要验证码的归属
// 这个凭证需要发送给客户端,客户端需要 cookie 保存,创建 Cookie、设置 Cookie 生成时间、有效路径,最后发送给客户端
String kaptchaOwner = CommunityUtil.generateUUID();
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
cookie.setMaxAge(60);
cookie.setPath(contextPath);
response.addCookie(cookie);
//拼接 key, 将验证码存入Redis,注入 RedisTemplate
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());
}
}
首次访问登陆页面,当 getKaptcha 方法被调用,生成验证码存入 Redis 中,在对登陆具体验证的时候使用,因此还需要处理登陆的方法(logic 方法)
//首次访问登陆页面,当getKaptcha方法被调用,生成验证码存入Redis中,在对登陆具体验证的时候使用,因此还需要处理登陆的方法(logic 方法)
@RequestMapping(path = "/login", method = RequestMethod.POST)
//表单中传入 用户、密码、验证码、记住我(boolean 类型)、Model(返回数据)、HttpSession(页面传入验证码和之前的验证码进行对比)
// 、 HttpServletResponse (登录成功,要把 ticket 发送给客户端使用 cookie 保存,创建 cookie 使用 HttpServletResponse 对象)
//之前是从 Session 中获取验证码,现在需要从 Redis 中获取(需要 key,而 key 需要验证码的归属,从 Cookie 中获取)
// 因此登陆方法还需要添加注解@CookieValue,从 Cookie 中取值
public String login(String username, String password, String code, boolean remember, Model model
, /*HttpSession session, */ HttpServletResponse response, @CookieValue("kaptchaOwner") String kaptchaOwner) {
//检查验证码
//String kaptcha = (String) session.getAttribute("kaptcha");
String kaptcha = null;
//判断 key 是否存在:如果存在,构造 key,然后从 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";
}
//检查账号、密码:判断账号密码是否正确:没有勾选记住我,存入库中的时间比较短;勾选记住我,存入库中的时间比较长
// 定义两个常量放入 CommunityConstant 接口中:如果勾选记住我,使用记住状态时间;如果不勾选,则使用默认的
int expiredSeconds = remember ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map map = userService.login(username, password, expiredSeconds);
//成功:取出 ticket 发送 cookie 给客户端,重定向首页
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());//map 中拿到的是对象需要转化为字符串
cookie.setPath(contextPath);//有效路径:整个项目但是不要写死,写参数即可
cookie.setMaxAge(expiredSeconds);//设置 cookie 有效时间
response.addCookie(cookie);//把 cookie 发送到页面
return "redirect:/index";
} else { //如果登录失败,返回登陆页面
//把错误的消息返回给页面
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
在 RedisKeyUtil 类中添加:
//定义登录凭证的前缀
private static final String PREFIX_TICKET = "ticket";
// 添加登录的凭证方法:获得登录凭证的详细数据,传入登录成功的凭证(ticket),返回 前缀 + 分隔符 + 凭证
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
接下来使用 Redis 存储凭证代替之前的 LoginTicketMapper 类,在此类中添加注解 @Deprecated,表示不推荐使用;重构使用到 Bean 的地方:在
UserService 中使用到(在登录成功以后生成凭证并且保存、退出删除凭证、查询凭证),修改 UserService 中登录功能模块
@Service
public class UserService implements CommunityConstant {
@Autowired
private RedisTemplate redisTemplate;
/**
* 实现登录功能
*/
//实现登录功能:成功、失败、不存在等等情况,返回数据的情况很多,可以使用 map 封装多种返回结果
//登录需要传入用户、密码、凭证有限时间
public Map login(String username, String password, int expiredSeconds) {
Map 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() + expiredSeconds * 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);
//退出的时候,将状态改为1:将 key 传入 Redis 中,返回为一个对象,将状态改为1,再把 key 传回去
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);
}
//更新修改头像路径
public int updateHeader(int userId, String headerUrl) {
return userMapper.updateHeader(userId, headerUrl);
}
//修改密码
public int updatePassword(int userId,String password){
return userMapper.updatePassword(userId,password);
}
//通过用户名查询用户得到 id
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
}
在 RedisKeyUtil 类中添加:
private static final String PREFIX_USER = "user";
// 用户
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
缓存数据主要是重构 UserService 中 findUserId 方法:每次请求获取凭证,根据凭证查询用户就需要调用 findUserId 方法,把每个 User 缓存到 Redis 中,在调用此方法效率就提升了。
缓存一般分为: