目录树
- 🌕Redis一站式高性能存储(优化登录模块)
- 一、Redis存储验证码
- 1.RedisKeyUtil
- 2.LoginController
- 3.测试
- 二、Redis存储登录凭证
- 2.1 ※详细说明
- 2.1.1 RedisKeyUtil
- 2.1.2 LoginTicketMapper
- 2.1.3 UserService
- 2.2 测试
- 三、Redis缓存用户信息
- 3.1 RedisKeyUtil
- 3.2 UserService
- 3.3 测试
🌕Redis一站式高性能存储(优化登录模块)
一、Redis存储验证码
1.RedisKeyUtil
/*生成redis的key的工具类,方便复用*/
public class RedisKeyUtil {
private static final String PREFIX_KAPTCHA = "kaptcha"; //验证码
//5.登录验证码
public static String getKaptchaKey(String owner){
//获取登录验证码的时候,验证码应该和某一个用户是相关的,不同的用户验证码是不一样的,我们要识别出来这个验证码是属于哪个用户.
//可能想加一个userId,这个不对。验证码的作用是辅助用户去登录的,当他打开验证页面看到验证码的时候,这个时候他还没登陆,我们根本不知道他是哪个user,不知道它的userId,这个时候还传不了userId
//这个时候怎么办呢?还要识别用户是谁。可以这样,在用户访问登录页面的时候,给他打一个凭证,这个凭证是一个随机字符串,发给他,存到cookie里;我们以字符串来标识这个用户,临时标一下就可以了,然后很快让这个字符串过期就行了
return PREFIX_KAPTCHA + SPLIT + owner;
}
}
2.LoginController
对 “生成验证码的方法 ”进行重构;原来是把验证码存到session里的,现在不用它,把它注释掉,→
//生成验证码的方法
@RequestMapping(path = "/kaptcha",method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response/*, HttpSession session*/){
//生成验证码
String text = kaptchaProducer.createText(); //先 生成字符串text
BufferedImage image = kaptchaProducer.createImage(text); //利用字符串text去生成一个图片
//※ 将验证码存入session
//session.setAttribute("kaptcha",text);
//※ 取代上面——存到redis里,首先要构造一个key,而key又需要验证码的归属者
//1.1验证码的归属
String kaptchaOwner = CommunityUtil.generateUUID();//这个凭证要发给客户端,用cookie保存
Cookie cookie = new Cookie("kaptchaOwner",kaptchaOwner);
//1.2设置生存时间
cookie.setMaxAge(60); //1分钟
cookie.setPath(contextPath); //路径:整个项目下都有效
//1.3都设置完了以后,把它发送给客户端
response.addCookie(cookie);
//2.1将验证码存入到 Redis
//拼key
String redisKey= RedisKeyUtil.getKaptchaKey(kaptchaOwner);
//有了key以后,就往redis里存数据
// redisTemplate.opsForValue().set(redisKey,text);
redisTemplate.opsForValue().set(redisKey,text,60, TimeUnit.SECONDS);
//再调redisTemplate给key设置一个有效的时间(或者也可以在这直接设置——方法有重载的方法,如上)
//将图片输出给浏览器
response.setContentType("image/png");
try {
OutputStream os = response.getOutputStream();
ImageIO.write(image,"png",os);
} catch (IOException e) {
logger.error("响应验证码失败:"+e.getMessage());
}
}
当我们首次访问登录页面的时候,验证码就生成了也存到了Redis里;存进去什么时候用呢?就是我们在登录进行具体的验证的时候要用,所以 “登录操作请求” login方法也要处理。之前我是从session里把验证码取到的,现在的话得从Redis里取。首先还是注释掉参数session然后呢,kaptcha需要单独从Redis里取,这个也好办。
//※登录操作请求
@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){
/*参数说明:
code —— 用户前端输入的验证码,传到后端来的
boolean rememberme —— 判断前端是否勾了记住我的选项,有就把凭证时间放长一点
session —— 页面传进来验证码,我需要取到和之前生成的验证码去对比,所以从session里取出验证码
response —— 登陆成功,要把ticket发给客户端让它好保存,所以用cookie保存;要想创建cookie就得用HttpServletResponse。
*/
//检查验证码
/*将验证码从session取出*/
/*String kaptcha = (String) session.getAttribute("kaptcha");*/
/*将验证码和用户传入的code验证码相比*/
/*从Redis里取kaptcha*/
//1.变量声明好,默认为null
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)){
/*kaptcha、code为空,不对两者不相等,不对。三个判断*/
model.addAttribute("codeMsg","验证码不正确!");
return "/site/login";
}
//检查账号,密码
/*调业务层来处理。调service的时候,不止要存传账号密码,还要传一个ticket凭证过期的时间*/
/*逻辑:如果没有勾上'记住我',依然把它存到库里,只是凭证时间短一点;勾上‘记住我’,依然存库里,时间会长
* 所以定义两个常量时间 ———— 写在CommunityConstant中*/
long expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")){
Cookie cookie = new Cookie("ticket",map.get("ticket").toString());
cookie.setPath(contextPath);//设置cookie生效路径,全项目,但是不能写死,所以将路径注入进来 —— @Value("${server.servlet.context-path}") private String contextPath;
cookie.setMaxAge((int) 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";
}
}
3.测试
如果能登录进去,说明就是没问题,因为代码我已经把session注掉了。同时看Redis第12(11)个库就知道。
成功!
二、Redis存储登录凭证
2.1 ※详细说明
使用Redis去存登录凭证代替之前的那个login_ticket表,可以把之前的LoginTicketMapper废弃掉(不用删,类上加一个@Deprecated就行:声明组件不推荐使用)
然后需要去重构一个之前用到LoginTicketMapper这个bean的地方:
- 1.UserService里登录成功以后,我们生成了一个凭证,做了一个保存;
- 2.我们登录以后存了凭证,还要注意退出登录的时候,还要把凭证删掉,这里也要处理;
- 3.UserService里还写过一个查询凭证的方法,其他地方都是调这个方法去查凭证的,这个地方也要重构。
解决步骤
:
1.来到UserService,因为LoginTicketMapper不推荐使用了,所以@Autowired可以注释掉,引入RedisTemplate进来。
一旦注释掉,下面代码中调用LoginTicketMapper的部分代码就必定报红,这时只需要去将报红的淦掉,之前的service中对mapper及数据库的操作全部由Redis来代替。
1.添加重构2.退出登录重构
3.查询凭证重构
2.1.1 RedisKeyUtil
/*生成redis的key的工具类,方便复用*/
public class RedisKeyUtil {
private static final String PREFIX_TICKET = "ticket"; //登录凭证
...
//6.登录凭证
public static String getTicketKey(String ticket){
//因为要获得登录凭证的详细数据,所以要传进来登陆成功的凭证
return PREFIX_TICKET + SPLIT + ticket;
}
}
2.1.2 LoginTicketMapper
2.1.3 UserService
//※登录的方法
public Map<String,Object> login(String username,String password,Long expiredSeconds){
HashMap<String, Object> map = new HashMap<>();
...
//5.如果走到这,就说明都是成功的!所以就要开始生成登录凭证给你
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());//查出的用户id设置进去
loginTicket.setTicket(CommunityUtil.generateUUID());//工具类生成的随机字符串设置进去
loginTicket.setStatus(0); /*0凭证有效 1凭证无效*/
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds*1000));
//6.进行添加操作
/*loginTicketMapper.insertLoginTicket(loginTicket);*/
/*redis进行添加操作*/
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
//这是一个LoginTicket对象,下面怎么按字符串来寸呢?我们可以把对象序列化成一个json字符串存进去就完了
redisTemplate.opsForValue().set(redisKey,loginTicket);//然后把对象loginTicket传进来,redisTemplate会把对象序列化成一个json字符串
map.put("ticket",loginTicket.getTicket());
return map;
}
//※退出登录
/*退出的时候,要把凭证传过来,服务端就知道是谁要退出*/
public void logout(String ticket){
/*int i = loginTicketMapper.updateStatus(ticket,1);*/
String redisKey = RedisKeyUtil.getTicketKey(ticket);
//有了key以后,我们要先把loginTicket取出来,把对象的状态改完以后再存回去
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);//强转
loginTicket.setStatus(1);//1表示删除态,然后存回去即可
redisTemplate.opsForValue().set(redisKey,loginTicket);
}
//查询凭证的方法
public LoginTicket findLoginTicket(String ticket){
/*return LoginTicket loginTicket = loginTicketMapper.selectByTicket(ticket);*/
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(redisKey);//强转
}
2.2 测试
成功!
三、Redis缓存用户信息
缓存:只是暂时存,数据过一会就要删掉。
做缓存基本就是这几步:(查user为例)
- 1.查User时,先尝试从缓存中取值;
- 2.取到就用,没取到说明数据没有初始化进去,那就要做初始化 ;这就能把findUserById传给我;
- 3.有些地方我们会改变这些数据,比如修改密码,头像等;这个时候改数据以后,缓存也得更新一下。
两种做法:一种更新缓存,另一种直接把缓存删了(一般用这个),下次再查的话就再数据库访问一次。第一种可能会有并发的问题,第二种更干脆。
这个实现也是重构UserService。在UserService里封装这三步的每一个方法,然后哪里要用就直接调,如下;
3.1 RedisKeyUtil
/*生成redis的key的工具类,方便复用*/
public class RedisKeyUtil {
private static final String PREFIX_USER = "user"; //用户
...
//7.用户
public static String getUserKey(int userId){
return PREFIX_USER + SPLIT +userId;
}
}
3.2 UserService
后面的话,主要就是看一下哪个地方我们是修改了user,改的地方都要把缓存清理
...
//1.根据用户id查询用户名
public User findByUserId(int id){
/*return userMapper.selectById(id);*/
//首先从cache查
User user = getCache(id);
if (user==null){
//判断,没有就对它做缓存
user = initCache(id);
}
return user;
}
...
//邮件激活的方法
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)){
//如果用户的激活码和数据库的对比是一样的,就成功!并且将用户状态从0修改为1
userMapper.updateStatus(userId,1);
/*清除缓存*/
clearCache(userId);
return ACTIVATION_SUCCESS;
}else {
return ACTIVATION_FAILURE;
}
}
...
//更新用户头像
public int updateHeaderByUserId(int userId,String header){
int i = userMapper.updateHeader(userId, header);
clearCache(userId);
return i;
}
...
/*缓存用户信息
* 这是要做的三件事,然后将其封装成三个方法,其他地方要就直接调
* */
//1.优先从缓存中取值
private User getCache(int userId){
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);//强转型
}
//2.取不到数据就初始化缓存数据(说白了就是添加user信息到redis)
private User initCache(int userId){
User user = userMapper.selectById(userId);//通过用户id查到用户信息
String redisKey = RedisKeyUtil.getUserKey(userId);//构建key
redisTemplate.opsForValue().set(redisKey,user,3600, TimeUnit.SECONDS);//将user添加到redis里
return user;
}
//3.数据变更时,清除缓存数据
private void clearCache(int userId){
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}
3.3 测试
打断点
运行程序,进入首页,然后发现就已经卡到了,然后后端接收到了参数:
然后取消断点继续运行F9:
立马进来了!
然后接着一样的地方打断点,重新启动程序,这次看有没有进来,发现已经直接进来了,说明已经有缓存了!
然后查看redis:
ok,成功!!