目录
Redis实现点赞与关注
使用Redis优化登录模块
Redis:支持数据类型完善;使用简单;性能好;Redis是一款基于键值对的NoSQL数据库,对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值可以是字符串对象、列表对象、哈希对象、集合对象或有序集合对象:String, hashes, Lists, Sets, Sorted Sets.
Redis将所有的数据都存放在内存中,所以他的读写性能十分惊人;(性能)
同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。(安全性)
其中:快照的形式:RDB的形式,直接把内存的数据存到硬盘上,优点是数据体积小,如果想从硬盘恢复到内存速度快;缺点是比较耗时,做存储的时候可能会产生阻塞,此时在处理其他的业务会影响其他的业务。不适合实时去做,可以几小时做一次的那种;日志的方式:AOF:每执行一个Redis命令就以日志的形式存下来,可以做到实时的存;但是以不断的追加的形式存的,体积较大,占用磁盘空间,且恢复的时候是把存的命令从头到尾再跑一遍,恢复的速度较慢;
访问Redis:
redisTemplate.opsForValue()
redisTemplate.opsForHash()
redisTemplate.opsForList()
redisTemplate.opsForSet()
redisTemplate.opsForZSet()
@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
可以参考Bean重写序列化:https://blog.csdn.net/lichuangcsdn/article/details/80866253
@Configuration
public class RedisConfig {
/*
* @Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一
* 个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext
* 或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
* */
//实际上,Spring很好的实现了泛型依赖注入
// https://blog.csdn.net/f641385712/article/details/84679147
/*
* StringRedisTemplate与RedisTemplate的区别
两者的关系是StringRedisTemplate继承RedisTemplate。
两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,
RedisTemplate只能管理RedisTemplate中的数据。
默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
* */
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
//定义一个bean,方法名为redisTemplate;当声明了这个bean方法,
// 参数为RedisConnectionFactory,参数也是一个Bean,它会被自动注入进来,被容器装配
//都配好后,就可以通过redisTemplate访问redis了
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(factory);//template具有了访问数据库的能力
//写Java程序得到Java类型数据是要存到数据库中,所以要指定一种数据转化的方式
// 设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
// 设置value的序列化方式
template.setValueSerializer(RedisSerializer.json());
// value本身为hash时:设置hash的key的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
// 设置hash的value的序列化方式
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();//触发生效
return template;
}
}
点赞点踩:访问非常频繁;就是一个数量,这样的数据存放到关系型数据的表里,也不方便存储;
RedisKeyUtil类:定义String类型的Key
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_ENTITY_LIKE = "like:entity";
private static final String PREFIX_USER_LIKE = "like:user";
private static final String PREFIX_FOLLOWEE = "followee";
private static final String PREFIX_FOLLOWER = "follower";
private static final String PREFIX_KAPTCHA = "kaptcha";
private static final String PREFIX_TICKET = "ticket";
private static final String PREFIX_USER = "user";
private static final String PREFIX_UV = "uv";
private static final String PREFIX_DAU = "dau";
private static final String PREFIX_POST = "post";
//定义一个静态方法
// 某个实体的赞
// like:entity:entityType:entityId -> set(userId)
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
//某个用户所获得的赞的总数,这里是key的格式,value为总数
//like:user:userId->int
public static String getUserLikeKey(int userId){
return PREFIX_USER_LIKE + SPLIT + userId;
}
// 某个用户关注的实体:有序集合,以时间为序
// followee:userId:entityType -> zset(entityId,now)
public static String getFolloweeKey(int userId, int entityType) {
return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
}
// 某个实体拥有的粉丝
// follower:entityType:entityId -> zset(userId,now)
public static String getFollowerKey(int entityType, int entityId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
// 登录验证码: kaptcha:owner 其中ower为用户的一个临时凭证
//每一个key都不同,对应的value为验证码
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
// 登录的凭证: kaptcha:ticket
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
// 用户 kaptcha:userId
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
// 单日UV
public static String getUVKey(String date) {
return PREFIX_UV + SPLIT + date;
}
// 区间UV
public static String getUVKey(String startDate, String endDate) {
return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
}
// 单日活跃用户
public static String getDAUKey(String date) {
return PREFIX_DAU + SPLIT + date;
}
// 区间活跃用户
public static String getDAUKey(String startDate, String endDate) {
return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
}
//@@@@@@@@@@@@@@@@@@@添加 帖子分数
public static String getPostScoreKey() {//key就是固定的字符串,声明为帖子分数;在value中会加入帖子id
return PREFIX_POST + SPLIT + "score";
}
}
LikeService与FollowService:
@Service
public class LikeService {
@Autowired
private RedisTemplate redisTemplate;
// 点赞
// public void like(int userId, int entityType, int entityId) {
// String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
// boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
// if (isMember) {
// redisTemplate.opsForSet().remove(entityLikeKey, userId);
// } else {
// redisTemplate.opsForSet().add(entityLikeKey, userId);
// }
// }
// =========点赞代码重构:在用户个人主页,添加用户的收到的赞的总数=====================
public void like(int userId, int entityType, int entityId, int entityUserId) {
//需要实现事务
//查询一定要放在事务的过程之外;Redis的事务比较特殊,如果将查询放在事务内,
// 它不会立即得到结果,在事务过程中的所有命令没有立即执行,而是把这些命令放到了队列中,
// 当提交事务时统一提交
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);//这里需要得到被赞的用户的id,从而得到用户key
boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);//查询
operations.multi();//开启事务
if(isMember){
operations.opsForSet().remove(entityLikeKey, userId);
operations.opsForValue().decrement(userLikeKey);//对user为key的处理
}else {
operations.opsForSet().add(entityLikeKey, userId);
operations.opsForValue().increment(userLikeKey);//对user为key的处理
}
operations.exec();//提交事务
return null;
}
});
}
//添加:查询某个用户获得的总赞
public int findUserLikeCount(int userId){
String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);//这里需要得到被赞的用户的id,得到uerKey
Integer count = (Integer)redisTemplate.opsForValue().get(userLikeKey);
return count == null ? 0 : count.intValue();
}
// 查询某实体点赞的数量
public long findEntityLikeCount(int entityType, int entityId) {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
return redisTemplate.opsForSet().size(entityLikeKey);
}
// 查询某人对某实体的点赞状态
public int findEntityLikeStatus(int userId, int entityType, int entityId) {
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
}
}
@Service
public class FollowService implements CommunityConstant {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserService userService;
public void follow(int userId, int entityType, int entityId) {
//关注的时候有两个需要存储:一个关注的目标;一个是粉丝;;要保证事务
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);//关注的目标的key
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);//粉丝的key
operations.multi();//开始事务
//对应的key存value,score,其中score为时间
operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());
return operations.exec();
}
});
}
public void unfollow(int userId, int entityType, int entityId) {
//取消关注
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
operations.multi();
operations.opsForZSet().remove(followeeKey, entityId);
operations.opsForZSet().remove(followerKey, userId);
return operations.exec();
}
});
}
// 查询关注的实体的数量
public long findFolloweeCount(int userId, int entityType) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().zCard(followeeKey);
}
// 查询实体的粉丝的数量
public long findFollowerCount(int entityType, int entityId) {
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
return redisTemplate.opsForZSet().zCard(followerKey);
}
// 查询当前用户是否已关注该实体
public boolean hasFollowed(int userId, int entityType, int entityId) {
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
}
//查询某用户关注的人findFollowees
public List
LikeController与FollowController:
@RequestMapping(path = "/like", method = RequestMethod.POST)
@ResponseBody
public String like(int entityType, int entityId, int entityUserId, int postId) {
//点赞用异步请求来实现;在前端页面,点击超链接访问服务器;需要用js方法来实现点赞提交请求的逻辑
//前端页面discuss_detail.html:οnclick="|like(this,1,${post.id},${post.userId});|"
//discuss.js: function like(btn, entityType, entityId)
//===========添加kafka重构:在传入参数的时候添加了一个参数:postId================
// {"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId,"postId":postId}
User user = hostHolder.getUser();
// 点赞
likeService.like(user.getId(), entityType, entityId, entityUserId);
// 数量
long likeCount = likeService.findEntityLikeCount(entityType, entityId);
// 状态
int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
// 返回的结果
Map map = new HashMap<>();
map.put("likeCount", likeCount);
map.put("likeStatus", likeStatus);
//添加kafka系统通知部分:触发点赞事件
//赞的时候发布通知;取消赞的时候就不用发布通知了
if(likeStatus == 1){//点赞
Event event = new Event().setTopic(TOPIC_LIKE)
.setEntityId(entityId)
.setEntityType(entityType)
.setUserId(hostHolder.getUser().getId())
.setEntityUserId(entityUserId)
.setData("postId", postId);
eventProducer.fireEvent(event);
}
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@计算帖子分数
if(entityType == ENTITY_TYPE_POST) {
// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, postId);
}
return CommunityUtil.getJSONString(0, null, map);
}
@Controller
public class FollowController implements CommunityConstant {
@Autowired
private FollowService followService;
@Autowired
private HostHolder hostHolder;
@Autowired
private UserService userService;
@Autowired
private EventProducer eventProducer;
//关注,传入参数:实体类型,实体id
//异步的操作,局部更新
@RequestMapping(path = "/follow", method = RequestMethod.POST)
@ResponseBody
public String follow(int entityType, int entityId) {
User user = hostHolder.getUser();
followService.follow(user.getId(), entityType, entityId);
//添加kafka系统通知部分:触发点赞事件
Event event = new Event().setTopic(TOPIC_FOLLOW)
.setUserId(hostHolder.getUser().getId())
.setEntityId(entityId)
.setEntityType(entityType)
.setEntityUserId(entityId);//只能关注人,所以实体UserId就是entityId
eventProducer.fireEvent(event);
return CommunityUtil.getJSONString(0, "已关注!");
}
//取消关注,传入参数:实体类型,实体id
@RequestMapping(path = "/unfollow", method = RequestMethod.POST)
@ResponseBody
public String unfollow(int entityType, int entityId) {
User user = hostHolder.getUser();
followService.unfollow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, "已取消关注!");
}
//显示某个用户的关注列表
@RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET)
public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setLimit(5);
page.setPath("/followees/" + userId);
page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));
List> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
if (userList != null) {//关注列表
for (Map map : userList) {
User u = (User) map.get("user");
map.put("hasFollowed", hasFollowed(u.getId()));//判断登陆用户是否已关注
}
}
model.addAttribute("users", userList);
return "/site/followee";
}
//显示某用户的粉丝列表
@RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET)
public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setLimit(5);
page.setPath("/followers/" + userId);
page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));
List> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
if (userList != null) {//粉丝列表
for (Map map : userList) {
User u = (User) map.get("user");
map.put("hasFollowed", hasFollowed(u.getId()));//判断登陆用户是否已关注
}
}
model.addAttribute("users", userList);
return "/site/follower";
}
private boolean hasFollowed(int userId) {//判断登陆用户是否已关注查询到的当前用户
if (hostHolder.getUser() == null) {
return false;
}
return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
}
在帖子详情页面添加:
//添加点赞有关信息
likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("likeCount", likeCount);
likeStatus = hostHolder.getUser() == null ? 0 :
likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("likeStatus", likeStatus);
用户个人主页:(个人信息)
//个人主页:
//个人主页
@RequestMapping(path = "/profile/{userId}", method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在");
}
//用户(MySQL中)
model.addAttribute("user", user);
//用户获赞的数量(Redis中)
int likeCount = likeService.findUserLikeCount(userId);
model.addAttribute("likeCount", likeCount);
//关注的数量
long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
model.addAttribute("followeeCount", followeeCount);
//粉丝的数量
long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
model.addAttribute("followerCount", followerCount);
//是否已关注
boolean hasFollowed = false;
if (hostHolder.getUser() != null) {
hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}
model.addAttribute("hasFollowed", hasFollowed);
return "/site/profile";
}
使用Redis存储验证码:验证码需要频繁的访问与刷新,对性能要求较高。验证码不需要永久保存,通常在很短的时间后就会失效。分布式部署,存在session共享问题
使用Redis存储登陆凭证:之前的MySQL存储的Login_ticket登陆信息不需要用了(仅首次登录时,生成登录凭证并加载到Redis缓存中),直接用Redis重构;处理每次请求时,都要查询用户的登陆凭证,访问的频率非常高;
使用Redis缓存用户信息:使用Redis缓存用户,并设置一段时间过期,符合实际业务;实际的User信息也同时还存储在MySQL中。处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。
缓存用户信息:1、查询用户信息时,先尝试在缓存中取值,取到了就使用,取不到就在Redis中初始化这个user
2、当更改用户数据,如修改图片,修改密码等;此时需要更新缓存:可以直接把这个用户数据从缓存中删掉,下次访问时,再重新插入一次;如果直接更新数据,一方面时麻烦,还可能有并发的问题。
验证码:
//=======用Redis重构验证码代码============================================
@RequestMapping(path = "/kaptcha", method = RequestMethod.GET)///路径kaptcha传入到login.html登录页面中了
public void getKaptcha(HttpServletResponse response) {
//生成验证码
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);
//将验证码存入session
// session.setAttribute("kaptcha", text);
//验证码的归属问题:
String kaptchaOwner = CommunityUtil.generateUUID();
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
cookie.setMaxAge(60);//60s
cookie.setPath(contextPath);//整个项目都有效
response.addCookie(cookie);
//将验证码存入Redis
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey,text,60, TimeUnit.SECONDS);//直接同时设置了过期时间
//将图片输出给浏览器
response.setContentType("image/png");
//response做响应;获取输出流
try {
OutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error("响应验证码失败:" + e.getMessage());
}
}
登录重构:
//===========================Redis重构登录============================
@RequestMapping(path = "/login", method = RequestMethod.POST)//路径相同但为post方法
public String login(String username, String password, String code, boolean rememberme, Model model,
@CookieValue("kaptchaOwner") String kaptchaOwner, HttpServletResponse response) {
//从Redis来取验证码
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)) {
model.addAttribute("codeMsg", "验证码不正确!");
return "/site/login";//返回登录页面
}
// 检查账号,密码
int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath);
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";//登录成功返回首页
} else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";//返回登录页面
}
}
userService中登录的业务逻辑处理,Redis缓存ticket登录凭证:
//==========登录的业务逻辑处理=============================
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());//user表中的id
loginTicket.setTicket(CommunityUtil.generateUUID());//凭证是一个随机生成的字符串
loginTicket.setStatus(0);//status:0表示有效;1表示无效
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
// loginTicketMapper.insertLoginTicket(loginTicket);
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());//由ticket生成Redis中的key
redisTemplate.opsForValue().set(redisKey, loginTicket);//设置value
//loginTicket为一个对象,这里Redis会将此对象序列化为字符串格式保存
map.put("ticket", loginTicket.getTicket());
return map;
}
//重构的登出
public void logout(String ticket) {
String redisKey = RedisKeyUtil.getTicketKey(ticket);//由ticket生成Redis中的key
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
//由Object强制转化为LoginTicket
loginTicket.setStatus(1);//将登录状态设置为1:无效
redisTemplate.opsForValue().set(redisKey, loginTicket);//再次存入;
//整个过程相当于更新了Redis中的ticket的status
// 每一个用户登录都对应一个ticket
}
每次请求时由ticket查询用户:(拦截器,用户登录与页面访问见系列文章https://blog.csdn.net/liuzewei2015/article/details/93304555)
//Redis重构:通过ticket得到LoginTicket表对象
public LoginTicket findLoginTicket(String ticket) {
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}
Redis缓存用户信息:
//Redis重构findUserById
public User findUserById(int id) {
User user = getCache(id);
if(user == null){
user = initCache(id);
}
return user;
}
//1、优先从缓存中取值
//2、取不到时初始化缓存数据
//3、数据变更是清除缓存数据
// 1.优先从缓存中取值
private User getCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}
// 2.取不到时初始化缓存数据
private User initCache(int userId) {
User user = userMapper.selectById(userId);//先从MySQL中查到
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);//一个小时
return user;
}
// 3.数据变更时清除缓存数据
private void clearCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}