feat(note): 实现笔记收藏ZSET更新逻辑

- 移除TODO注释,完善收藏判断逻辑
- 新增Lua脚本实现ZSET收藏列表更新
- 添加ZSET列表不存在时的初始化逻辑
- 实现收藏列表超限移除最早收藏项
- 支持批量同步历史收藏数据到Redis
- 设置随机过期时间避免缓存雪崩
This commit is contained in:
2025-10-18 21:19:12 +08:00
parent 65b089de70
commit 1ac61d1b06
2 changed files with 70 additions and 2 deletions

View File

@@ -807,7 +807,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
// 1. 校验被收藏的笔记是否存在 // 1. 校验被收藏的笔记是否存在
checkNoteIsExist(noteId); checkNoteIsExist(noteId);
// TODO: 2. 判断目标笔记,是否已经收藏过 // 2. 判断目标笔记,是否已经收藏过
// 当前登录用户ID // 当前登录用户ID
Long userId = LoginUserContextHolder.getUserId(); Long userId = LoginUserContextHolder.getUserId();
@@ -875,7 +875,54 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
} }
} }
// TODO: 3. 更新用户 ZSET 收藏列表 // 3. 更新用户 ZSET 收藏列表
// 3. 更新用户 ZSET 收藏列表
LocalDateTime now = LocalDateTime.now();
// Lua 脚本路径
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/note_collect_check_and_update_zset.lua")));
// 返回值类型
script.setResultType(Long.class);
// 执行 Lua 脚本,拿到返回结果
result = redisTemplate.execute(script, Collections.singletonList(userNoteCollectZSetKey), noteId, DateUtils.localDateTime2Timestamp(now));
// 若 ZSet 列表不存在,需要重新初始化
if (Objects.equals(result, NoteCollectLuaResultEnum.NOT_EXIST.getCode())) {
// 查询当前用户最新收藏的 300 篇笔记
Page<NoteCollectionDO> page = noteCollectionDOService.page(new Page<>(1, 300), new LambdaQueryWrapper<>(NoteCollectionDO.class)
.select(NoteCollectionDO::getNoteId, NoteCollectionDO::getCreateTime)
.eq(NoteCollectionDO::getUserId, userId)
.eq(NoteCollectionDO::getStatus, CollectStatusEnum.COLLECT.getCode())
.orderByDesc(NoteCollectionDO::getCreateTime));
List<NoteCollectionDO> noteCollectionDOS = page.getRecords();
// 保底1天+随机秒数
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
DefaultRedisScript<Long> script2 = new DefaultRedisScript<>();
// Lua 脚本路径
script2.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/batch_add_note_collect_zset_and_expire.lua")));
// 返回值类型
script2.setResultType(Long.class);
// 若数据库中存在历史收藏笔记,需要批量同步
if (CollUtil.isNotEmpty(noteCollectionDOS)) {
// 构建 Lua 参数
Object[] luaArgs = buildNoteCollectZSetLuaArgs(noteCollectionDOS, expireSeconds);
redisTemplate.execute(script2, Collections.singletonList(userNoteCollectZSetKey), luaArgs);
// 再次调用 note_collect_check_and_update_zset.lua 脚本,将当前收藏的笔记添加到 zset 中
redisTemplate.execute(script, Collections.singletonList(userNoteCollectZSetKey), noteId, DateUtils.localDateTime2Timestamp(now));
} else { // 若无历史收藏的笔记,则直接将当前收藏的笔记 ID 添加到 ZSet 中,随机过期时间
List<Object> luaArgs = Lists.newArrayList();
luaArgs.add(DateUtils.localDateTime2Timestamp(LocalDateTime.now())); // score收藏时间戳
luaArgs.add(noteId); // 当前收藏的笔记 ID
luaArgs.add(expireSeconds); // 随机过期时间
redisTemplate.execute(script2, Collections.singletonList(userNoteCollectZSetKey), luaArgs.toArray());
}
}
// TODO: 4. 发送 MQ, 将收藏数据落库 // TODO: 4. 发送 MQ, 将收藏数据落库

View File

@@ -0,0 +1,21 @@
local key = KEYS[1] -- Redis Key
local noteId = ARGV[1] -- 笔记ID
local timestamp = ARGV[2] -- 时间戳
-- 使用 EXISTS 命令检查 ZSET 笔记收藏列表是否存在
local exists = redis.call('EXISTS', key)
if exists == 0 then
return -1
end
-- 获取笔记收藏列表大小
local size = redis.call('ZCARD', key)
-- 若已经收藏了 300 篇笔记,则移除最早收藏的那篇
if size >= 300 then
redis.call('ZPOPMIN', key)
end
-- 添加新的笔记收藏关系
redis.call('ZADD', key, timestamp, noteId)
return 0