226 lines
6.4 KiB
Markdown
226 lines
6.4 KiB
Markdown
# 优化登录模块
|
||
|
||
---
|
||
|
||
## 使用 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;
|
||
}
|
||
}
|
||
``` |