diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
index 5bc28bd..ab5fea2 100644
--- a/.idea/dictionaries/project.xml
+++ b/.idea/dictionaries/project.xml
@@ -9,6 +9,7 @@
mget
nacos
operationlog
+ rbitmap
rustfs
zadd
zrevrangebyscore
diff --git a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNoteLikeConsumer.java b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNoteLikeConsumer.java
index 12c7a29..e66fe13 100644
--- a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNoteLikeConsumer.java
+++ b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNoteLikeConsumer.java
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
@Component
@Slf4j
@RocketMQMessageListener(
- consumerGroup = "han_note_group_" + MQConstants.TOPIC_LIKE_OR_UNLIKE,
+ consumerGroup = "han_note_count_group_" + MQConstants.TOPIC_LIKE_OR_UNLIKE,
topic = MQConstants.TOPIC_LIKE_OR_UNLIKE
)
public class CountNoteLikeConsumer implements RocketMQListener {
diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/RedisKeyConstants.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/RedisKeyConstants.java
index c4eadb5..0927ede 100644
--- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/RedisKeyConstants.java
+++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/RedisKeyConstants.java
@@ -7,6 +7,11 @@ public class RedisKeyConstants {
*/
public static final String NOTE_DETAIL_KEY = "note:detail:";
+ /**
+ * Roaring Bitmap:用户笔记点赞 前缀
+ */
+ public static final String R_BITMAP_USER_NOTE_LIKE_LIST_KEY = "rbitmap:note:likes:";
+
/**
* 布隆过滤器:用户笔记点赞
*/
@@ -76,4 +81,14 @@ public class RedisKeyConstants {
public static String buildUserNoteCollectZSetKey(Long userId) {
return USER_NOTE_COLLECT_ZSET_KEY + userId;
}
+
+ /**
+ * 构建完整的 Roaring Bitmap:用户笔记点赞 KEY
+ *
+ * @param userId 用户ID
+ * @return Roaring Bitmap:用户笔记点赞 KEY
+ */
+ public static String buildRBitmapUserNoteLikeListKey(Long userId) {
+ return R_BITMAP_USER_NOTE_LIKE_LIST_KEY + userId;
+ }
}
\ No newline at end of file
diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/service/impl/NoteServiceImpl.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/service/impl/NoteServiceImpl.java
index d2d0f1f..f7ad016 100644
--- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/service/impl/NoteServiceImpl.java
+++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/service/impl/NoteServiceImpl.java
@@ -21,6 +21,7 @@ import com.hanserwei.hannote.note.biz.domain.dataobject.NoteDO;
import com.hanserwei.hannote.note.biz.domain.dataobject.NoteLikeDO;
import com.hanserwei.hannote.note.biz.domain.dataobject.TopicDO;
import com.hanserwei.hannote.note.biz.domain.mapper.NoteDOMapper;
+import com.hanserwei.hannote.note.biz.domain.mapper.NoteLikeDOMapper;
import com.hanserwei.hannote.note.biz.enums.*;
import com.hanserwei.hannote.note.biz.model.dto.CollectUnCollectNoteMqDTO;
import com.hanserwei.hannote.note.biz.model.dto.LikeUnlikeNoteMqDTO;
@@ -76,6 +77,8 @@ public class NoteServiceImpl extends ServiceImpl implement
private RedisTemplate redisTemplate;
@Resource
private RocketMQTemplate rocketMQTemplate;
+ @Resource
+ private NoteLikeDOMapper noteLikeDOMapper;
/**
* 笔记详情本地缓存
@@ -630,14 +633,17 @@ public class NoteServiceImpl extends ServiceImpl implement
// 2. 判断目标笔记,是否已经点赞过
Long userId = LoginUserContextHolder.getUserId();
- // 布隆过滤器Key
- String bloomUserNoteLikeListKey = RedisKeyConstants.buildBloomUserNoteLikeListKey(userId);
+ // Roaring Bitmap Key
+ String rbitmapUserNoteLikeListKey = RedisKeyConstants.buildRBitmapUserNoteLikeListKey(userId);
+
DefaultRedisScript script = new DefaultRedisScript<>();
// Lua 脚本路径
- script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_note_like_check.lua")));
+ script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/rbitmap_note_like_check.lua")));
+ // 返回值类型
script.setResultType(Long.class);
+
// 执行 Lua 脚本,拿到返回结果
- Long result = redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), noteId);
+ Long result = redisTemplate.execute(script, Collections.singletonList(rbitmapUserNoteLikeListKey), noteId);
NoteLikeLuaResultEnum noteLikeLuaResultEnum = NoteLikeLuaResultEnum.valueOf(result);
@@ -659,38 +665,25 @@ public class NoteServiceImpl extends ServiceImpl implement
// 目标笔记已经被点赞
if (count > 0) {
- // 异步初始化布隆过滤器
- threadPoolTaskExecutor.submit(() -> batchAddNoteLike2BloomAndExpire(userId, expireSeconds, bloomUserNoteLikeListKey));
+ // 异步初始化 Roaring Bitmap
+ threadPoolTaskExecutor.submit(() ->
+ batchAddNoteLike2RBitmapAndExpire(userId, expireSeconds, rbitmapUserNoteLikeListKey));
throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED);
}
- // 若笔记未被点赞,查询当前用户是否点赞其他用户,有则同步初始化布隆过滤器
- batchAddNoteLike2BloomAndExpire(userId, expireSeconds, bloomUserNoteLikeListKey);
+ // 若目标笔记未被点赞,查询当前用户是否有点赞其他笔记,有则同步初始化 Roaring Bitmap
+ batchAddNoteLike2RBitmapAndExpire(userId, expireSeconds, rbitmapUserNoteLikeListKey);
- // 若数据库中也没有点赞记录,说明该用户还未点赞过任何笔记
+ // 添加当前点赞笔记 ID 到 Roaring Bitmap 中
// Lua 脚本路径
- script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_add_note_like_and_expire.lua")));
+ script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/rbitmap_add_note_like_and_expire.lua")));
// 返回值类型
script.setResultType(Long.class);
- redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), noteId, expireSeconds);
+ redisTemplate.execute(script, Collections.singletonList(rbitmapUserNoteLikeListKey), noteId, expireSeconds);
}
// 目标笔记已经被点赞
case NOTE_LIKED -> {
- // 校验 ZSet 列表中是否包含被点赞的笔记ID
- Double score = redisTemplate.opsForZSet().score(userNoteLikeZSetKey, noteId);
-
- if (Objects.nonNull(score)) {
- throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED);
- }
- // 若 Score 为空,则表示 ZSet 点赞列表中不存在,查询数据库校验
- long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
- .eq(NoteLikeDO::getNoteId, noteId)
- .eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
- if (count > 0) {
- // 数据库里面有点赞记录,而 Redis 中 ZSet 不存在,需要重新异步初始化 ZSet
- asynInitUserNoteLikesZSet(userId, userNoteLikeZSetKey);
- throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED);
- }
+ throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED);
}
}
// 3. 更新用户 ZSET 点赞列表
@@ -768,6 +761,37 @@ public class NoteServiceImpl extends ServiceImpl implement
return Response.success();
}
+ /**
+ * 初始化笔记点赞 Roaring Bitmap
+ *
+ * @param userId 用户 ID
+ * @param expireSeconds 过期时间
+ * @param rbitmapUserNoteLikeListKey RBitmap 列表 Key
+ */
+ private void batchAddNoteLike2RBitmapAndExpire(Long userId, long expireSeconds, String rbitmapUserNoteLikeListKey) {
+ try {
+ // 异步全量同步一下,并设置过期时间
+ List noteLikeDOS = noteLikeDOMapper.selectList(new LambdaQueryWrapper<>(NoteLikeDO.class)
+ .eq(NoteLikeDO::getUserId, userId));
+
+ if (CollUtil.isNotEmpty(noteLikeDOS)) {
+ DefaultRedisScript script = new DefaultRedisScript<>();
+ // Lua 脚本路径
+ script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/rbitmap_batch_add_note_like_and_expire.lua")));
+ // 返回值类型
+ script.setResultType(Long.class);
+
+ // 构建 Lua 参数
+ List