Compare commits

..
3 Commits
5 changed files with 179 additions and 1 deletions
@@ -3,9 +3,11 @@ package cn.hezhaohui.thumb;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@MapperScan("cn.hezhaohui.thumb.mapper")
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
@@ -0,0 +1,47 @@
package cn.hezhaohui.thumb.job;
import cn.hezhaohui.thumb.constant.ThumbConstant;
import cn.hezhaohui.thumb.util.RedisKeyUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* 定时将 Redis 中的临时点赞数据同步到数据库的补偿措施
*
*/
@Component
@Slf4j
public class SyncThumb2DBCompensatoryJob {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private SyncThumb2DBJob syncThumb2DBJob;
@Scheduled(cron = "0 0 2 * * *")
public void run() {
log.info("开始补偿数据");
Set<String> thumbKeys = redisTemplate.keys(RedisKeyUtil.getTempThumbKey("") + "*");
Set<String> needHandleDataSet = new HashSet<>();
thumbKeys.stream().filter(ObjUtil::isNotNull).forEach(thumbKey -> needHandleDataSet.add(thumbKey.replace(ThumbConstant.TEMP_THUMB_KEY_PREFIX.formatted(""), "")));
if (CollUtil.isEmpty(needHandleDataSet)) {
log.info("没有需要补偿的临时数据");
return;
}
// 补偿数据
for (String date : needHandleDataSet) {
syncThumb2DBJob.syncThumb2DBByDate(date);
}
log.info("临时数据补偿完成");
}
}
@@ -0,0 +1,113 @@
package cn.hezhaohui.thumb.job;
import cn.hezhaohui.thumb.mapper.BlogMapper;
import cn.hezhaohui.thumb.model.entity.Thumb;
import cn.hezhaohui.thumb.model.enums.ThumbTypeEnum;
import cn.hezhaohui.thumb.service.ThumbService;
import cn.hezhaohui.thumb.util.RedisKeyUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrPool;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* 定时将 Redis 中的临时点赞数据同步到数据库
*
*/
@Component
@Slf4j
public class SyncThumb2DBJob {
@Resource
private ThumbService thumbService;
@Resource
private BlogMapper blogMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Scheduled(fixedRate = 10000)
@Transactional(rollbackFor = Exception.class)
public void run() {
log.info("开始执行");
DateTime nowDate = DateUtil.date();
// 如果秒数为0~9 则回到上一分钟的50秒
int second = (DateUtil.second(nowDate) / 10 - 1) * 10;
if (second == -10) {
second = 50;
// 回到上一分钟
nowDate = DateUtil.offsetMinute(nowDate, -1);
}
String date = DateUtil.format(nowDate, "HH:mm:") + second;
syncThumb2DBByDate(date);
log.info("临时数据同步完成");
}
public void syncThumb2DBByDate(String date) {
// 获取到临时点赞和取消点赞数据
String tempThumbKey = RedisKeyUtil.getTempThumbKey(date);
Map<Object, Object> allTempThumbMap = redisTemplate.opsForHash().entries(tempThumbKey);
boolean thumbMapEmpty = CollUtil.isEmpty(allTempThumbMap);
// 同步 点赞 到数据库
// 构建插入列表并收集blogId
Map<Long, Long> blogThumbCountMap = new HashMap<>();
if (thumbMapEmpty) {
return;
}
ArrayList<Thumb> thumbList = new ArrayList<>();
LambdaQueryWrapper<Thumb> wrapper = new LambdaQueryWrapper<>();
boolean needRemove = false;
for (Object userIdBlogIdObj : allTempThumbMap.keySet()) {
String userIdBlogId = (String) userIdBlogIdObj;
String[] userIdAndBlogId = userIdBlogId.split(StrPool.COLON);
Long userId = Long.valueOf(userIdAndBlogId[0]);
Long blogId = Long.valueOf(userIdAndBlogId[1]);
// -1 取消点赞,1 点赞
Integer thumbType = Integer.valueOf(allTempThumbMap.get(userIdBlogId).toString());
if (thumbType == ThumbTypeEnum.INCR.getValue()) {
Thumb thumb = new Thumb();
thumb.setUserid(userId);
thumb.setBlogId(blogId);
thumbList.add(thumb);
} else if (thumbType == ThumbTypeEnum.DECR.getValue()) {
// 拼接查询条件,批量删除
needRemove = true;
wrapper.or().eq(Thumb::getUserid, userId).eq(Thumb::getBlogId, blogId);
} else {
if (thumbType != ThumbTypeEnum.NON.getValue()) {
log.warn("数据异常:{}", userId + "," + blogId + "," + thumbType);
}
continue;
}
// 计算点赞增量
blogThumbCountMap.put(blogId, blogThumbCountMap.getOrDefault(blogId, 0L) + thumbType);
}
// 批量插入
thumbService.saveBatch(thumbList);
// 批量删除
if (needRemove) {
thumbService.remove(wrapper);
}
// 批量更新博客点赞量
if (!blogThumbCountMap.isEmpty()) {
blogMapper.batchUpdateThumbCount(blogThumbCountMap);
}
// 异步删除
Thread.startVirtualThread(() -> {
redisTemplate.delete(tempThumbKey);
});
}
}
@@ -2,6 +2,9 @@ package cn.hezhaohui.thumb.mapper;
import cn.hezhaohui.thumb.model.entity.Blog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Map;
/**
* @author 23117
@@ -10,7 +13,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @Entity cn.hezhaohui.thumb.model.entity.Blog
*/
public interface BlogMapper extends BaseMapper<Blog> {
void batchUpdateThumbCount(@Param("countMap") Map<Long, Long> countMap);
}
+13
View File
@@ -19,4 +19,17 @@
id,userId,title,coverImg,content,thumbCount,
createTime,updateTime
</sql>
<update id="batchUpdateThumbCount">
UPDATE blog
SET thumbCount = thumbCount + CASE id
<foreach collection="countMap.entrySet()" item="value" index="key">
WHEN #{key} THEN #{value}
</foreach>
END
WHERE id IN
<foreach collection="countMap.keySet()" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
</mapper>