本文使用hash类型存储点赞记录及统计总数,点赞记录使用博客id+用户id作为key,存储value值为点赞或者取消点赞状态。
点赞或取消点赞
点赞或取消点赞的方法,不会删除redis的记录,只会去更新对应的点赞状态。点赞或取消点赞需要对点赞总数进行数量+1或者-1的操作,这个适合需要去判断总量key是否在缓存中存在,如果没有需要去数据库获取再刷进缓存里面。
@Override
public String likeOrNot(Long blogId, Long userId, Integer likeDelFlag) {
redisTemplate.opsForHash().put(RedisPrefixConstant.BLOG_LIKE_USER_PREFIX, RedisPrefixConstant.BLOG_LIKE_USER_PREFIX + RedisPrefixConstant.COLON + blogId + RedisPrefixConstant.COLON + userId, likeDelFlag);
String key = RedisPrefixConstant.BLOG_LIKE_COUNT_PREFIX + RedisPrefixConstant.COLON + blogId;
String value = UUID.randomUUID().toString().replaceAll("-", "");
//对总量操作
return this.addOrDelCount(RedisPrefixConstant.BLOG_LIKE_COUNT_PREFIX, key, likeDelFlag, value, blogId, 0);
}
/**
* 增加或删除统计数量
*
* @param likeDelFlag
* @param prefixKey
* @param key
*/
private void addOrDelCount(String prefixKey, String key, Integer likeDelFlag) {
if (DelFlagConstant.DEL_FLAG__EXIST.equals(likeDelFlag)) {
//统计数加1
redisTemplate.opsForHash().increment(prefixKey, key, 1);
} else {
//统计数减1
redisTemplate.opsForHash().increment(prefixKey, key, -1);
}
}
/**
* 统计数量操作
*
* @param likeDelFlag 删除标记
* @param prefixKey 开始的key
* @param key 统计key
* @param lockValue 锁的值
* @param blogId 博客id
* @param retryCount 重试次数
*/
private String addOrDelCount(String prefixKey, String key, Integer likeDelFlag, String lockValue, Long blogId, int retryCount) {
//判断是否存在统计数缓存
Boolean hasKey = redisTemplate.opsForHash().hasKey(prefixKey, key);
if (hasKey) {
//存在直接操作
this.addOrDelCount(prefixKey, key, likeDelFlag);
return "操作成功";
} else {
String lockPrefix = RedisPrefixConstant.BLOG_LIKE_COUNT_LOCK_PREFIX;
try {
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockPrefix, lockValue, 1L, TimeUnit.SECONDS);
// 加锁失败
if (!flag) {
Thread.sleep(50);
if (retryCount < 3) {
//三次重试
retryCount++;
this.addOrDelCount(prefixKey, key, likeDelFlag, lockValue, blogId, retryCount);
} else {
return "操作失败";
}
} else {
//去数据库查询数量
Blog blog = baseMapper.selectById(blogId);
Integer likeCount = 0;
if (null != blog) {
if (null != blog.getLikeCount()) {
likeCount = blog.getLikeCount();
}
}
//将统计量写入缓存
redisTemplate.opsForHash().put(prefixKey, key, likeCount);
//对统计量+1/-1
this.addOrDelCount(prefixKey, key, likeDelFlag);
return "操作成功";
}
} catch (Exception e) {
log.error(e.getMessage());
return "操作失败";
} finally {
if (lockValue.equals(redisTemplate.opsForValue().get(lockPrefix))) {
redisTemplate.delete(RedisPrefixConstant.BLOG_LIKE_COUNT_LOCK_PREFIX);
}
}
}
return "操作失败";
}
对点赞记录写入到数据库
通过hash里面的scan方法获取到所有的map对象,然后遍历获取里面的属性,转换为对应的实体类,调用插入或者更新方法读取入库。对于点赞记录,如果数据库没有记录会插入点赞记录,后续如果取消或者取消再点赞这些操作,只会在数据库存储一条记录,然后更新对应的字段状态。
@Override
@Transactional(rollbackFor = Exception.class)
public void saveRedisToMysqlOfLikeCount() {
Cursor<Map.Entry<Object, Object>> scan = redisTemplate.opsForHash().scan(RedisPrefixConstant.BLOG_LIKE_COUNT_PREFIX, ScanOptions.NONE);
while (scan.hasNext()) {
Map.Entry<Object, Object> next = scan.next();
String key = (String) next.getKey();
//去掉前缀
key = key.replace(RedisPrefixConstant.BLOG_LIKE_COUNT_PREFIX + RedisPrefixConstant.COLON, "");
Long blogId = Long.parseLong(key);
Integer likeCount = (Integer) next.getValue();
//替换属性更新
LambdaUpdateWrapper<Blog> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(Blog::getId, blogId);
updateWrapper.set(Blog::getLikeCount, likeCount);
this.update(updateWrapper);
}
//删除缓存
redisTemplate.delete(RedisPrefixConstant.BLOG_LIKE_COUNT_PREFIX);
}
对点赞总数量写入到数据库
操作如上,点赞数量也可以使用string类型来存储,代码差不多,就是获取所有的数据时,需要先去获取所有的key再用key去获取所有的value。
@Override
@Transactional(rollbackFor = Exception.class)
public void saveRedisToMysqlOfLike() {
List<BlogLike> likes = new ArrayList<>();
Cursor<Map.Entry<Object, Object>> scan = redisTemplate.opsForHash().scan(RedisPrefixConstant.BLOG_LIKE_USER_PREFIX, ScanOptions.NONE);
while (scan.hasNext()) {
Map.Entry<Object, Object> next = scan.next();
String key = (String) next.getKey();
//去掉前缀
key = key.replace(RedisPrefixConstant.BLOG_LIKE_USER_PREFIX + RedisPrefixConstant.COLON, "");
//分离出 likedUserId,giveLikeId
String[] split = key.split(RedisPrefixConstant.COLON);
Long blogId = Long.parseLong(split[0]);
Long userId = Long.parseLong(split[1]);
Integer delFlag = (Integer) next.getValue();
//去查询有没有该数据 如果有则替换属性更新
LambdaQueryWrapper<BlogLike> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(BlogLike::getBlogId, blogId);
queryWrapper.eq(BlogLike::getUserId, userId);
BlogLike blogLike = baseMapper.selectOne(queryWrapper);
if (null == blogLike) {
//组装成对象
blogLike = new BlogLike(blogId, userId, delFlag);
} else {
blogLike.setDelFlag(delFlag);
}
likes.add(blogLike);
}
this.saveOrUpdateBatch(likes);
//删除缓存
redisTemplate.delete(RedisPrefixConstant.BLOG_LIKE_USER_PREFIX);
}
定时任务
最后使用定时调度方法去调用上面两个方法,将数据同步到数据库即可,可以使用xxl-job或者直接使用@Scheduled注解即可,对于点赞记录,可以设置更新频繁一点,比如两个小时一次,对于点赞总量,可以设置成一天更新一次。