Echo/docs/220-优化登录模块.md

226 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 优化登录模块
---
## 使用 Redis 短暂存储验证码
- 验证码需要频繁刷新,对性能要求较高
- 验证码不需永久保存(设置在 Cookie 和 Redis 中的保留时间为 60 s
- 分布式部署,存在 Session 共享问题
> 原来我们是将验证码存在 session 中,这里我们将其存入 redis 中
>
> 存入 redis 中的数据 key(kaptcha:随机字符串) value(验证码)
>
> 用户在输入验证码的时候,还没有进行登录,我们无法通过用户的 id 指明这个验证码是针对谁的。所以这里我们随机生成一个字符串,将其短暂的存入 cookie使用这个字符串来标识这个用户
工具类:生成 Redis 的 Key
```java
/**
* 生成 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;
}
}
```
修改生成验证码的方法:
```java
/**
* 生成验证码
* @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
```java
/**
* 生成 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 中生成/修改/查询登录凭证这三个方法
```java
// 生成登录凭证
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 中添加以下三个方法:
- 优先从缓存中取值;
- 缓存中没有该用户信息时,则将其存入缓存;
- 用户信息变更时清除对应缓存数据;
```java
/**
* 优先从缓存中取值
* @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 中其他方法的逻辑,将上面三个方法加入
```java
/**
* 根据 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;
}
}
```