diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/LikeUnlikeNoteConsumer.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/LikeUnlikeNoteConsumer.java new file mode 100644 index 0000000..78a2f09 --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/LikeUnlikeNoteConsumer.java @@ -0,0 +1,100 @@ +package com.hanserwei.hannote.note.biz.comsumer; + +import com.google.common.util.concurrent.RateLimiter; +import com.hanserwei.framework.common.utils.JsonUtils; +import com.hanserwei.hannote.note.biz.constant.MQConstants; +import com.hanserwei.hannote.note.biz.domain.dataobject.NoteLikeDO; +import com.hanserwei.hannote.note.biz.domain.mapper.NoteLikeDOMapper; +import com.hanserwei.hannote.note.biz.model.dto.LikeUnlikeNoteMqDTO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.spring.annotation.ConsumeMode; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Objects; + +@SuppressWarnings("UnstableApiUsage") +@Component +@RocketMQMessageListener( + consumerGroup = "han_note_" + MQConstants.TOPIC_LIKE_OR_UNLIKE, + topic = MQConstants.TOPIC_LIKE_OR_UNLIKE, + consumeMode = ConsumeMode.ORDERLY// 顺序消费 +) +@Slf4j +public class LikeUnlikeNoteConsumer implements RocketMQListener { + + // 每秒创建 5000 个令牌 + private final RateLimiter rateLimiter = RateLimiter.create(5000); + @Resource + private NoteLikeDOMapper noteLikeDOMapper; + + @Override + public void onMessage(Message message) { + // 流量削峰:通过获取令牌,如果没有令牌可用,将阻塞,直到获得 + rateLimiter.acquire(); + + // 幂等性,通过联合索引保证 + + // 消息体 + String bodyJsonStr = new String(message.getBody()); + // 标签 + String tags = message.getTags(); + + log.info("==> LikeUnlikeNoteConsumer 消费了消息 {}, tags: {}", bodyJsonStr, tags); + + // 根据 MQ 标签,判断操作类型 + if (Objects.equals(tags, MQConstants.TAG_LIKE)) { // 点赞笔记 + handleLikeNoteTagMessage(bodyJsonStr); + } else if (Objects.equals(tags, MQConstants.TAG_UNLIKE)) { // 取消点赞笔记 + handleUnlikeNoteTagMessage(bodyJsonStr); + } + } + + /** + * 处理取消点赞笔记的 MQ 消息 + * + * @param bodyJsonStr 消息体 + */ + private void handleUnlikeNoteTagMessage(String bodyJsonStr) { + + } + + /** + * 处理点赞笔记的 MQ 消息 + * + * @param bodyJsonStr 消息体 + */ + private void handleLikeNoteTagMessage(String bodyJsonStr) { + // 消息体 JSON 字符串转 DTO + LikeUnlikeNoteMqDTO likeNoteMqDTO = JsonUtils.parseObject(bodyJsonStr, LikeUnlikeNoteMqDTO.class); + + if (Objects.isNull(likeNoteMqDTO)) return; + + // 用户ID + Long userId = likeNoteMqDTO.getUserId(); + // 点赞的笔记ID + Long noteId = likeNoteMqDTO.getNoteId(); + // 操作类型 + Integer type = likeNoteMqDTO.getType(); + // 点赞时间 + LocalDateTime createTime = likeNoteMqDTO.getCreateTime(); + + // 构建 DO 对象 + NoteLikeDO noteLikeDO = NoteLikeDO.builder() + .userId(userId) + .noteId(noteId) + .createTime(createTime) + .status(type) + .build(); + + // 添加或更新笔记点赞记录 + boolean count = noteLikeDOMapper.insertOrUpdate(noteLikeDO); + + // TODO: 发送计数 MQ + + } +} diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/MQConstants.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/MQConstants.java index dc40c77..a3f209e 100644 --- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/MQConstants.java +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/MQConstants.java @@ -11,4 +11,19 @@ public interface MQConstants { * Topic 主题:延迟双删 Redis 笔记缓存 */ String TOPIC_DELAY_DELETE_NOTE_REDIS_CACHE = "DelayDeleteNoteRedisCacheTopic"; + + /** + * Topic: 点赞、取消点赞共用一个 + */ + String TOPIC_LIKE_OR_UNLIKE = "LikeUnlikeTopic"; + + /** + * 点赞标签 + */ + String TAG_LIKE = "Like"; + + /** + * Tag 标签:取消点赞 + */ + String TAG_UNLIKE = "Unlike"; } \ No newline at end of file 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 c26f570..6aacd22 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 @@ -12,6 +12,11 @@ public class RedisKeyConstants { */ public static final String BLOOM_USER_NOTE_LIKE_LIST_KEY = "bloom:note:likes:"; + /** + * 用户笔记点赞列表 ZSet 前缀 + */ + public static final String USER_NOTE_LIKE_ZSET_KEY = "user:note:likes:"; + /** * 构建完整的笔记详情 KEY @@ -32,4 +37,14 @@ public class RedisKeyConstants { return BLOOM_USER_NOTE_LIKE_LIST_KEY + userId; } + /** + * 构建完整的用户笔记点赞列表 ZSet KEY + * + * @param userId 用户ID + * @return 用户笔记点赞列表 ZSet KEY + */ + public static String buildUserNoteLikeZSetKey(Long userId) { + return USER_NOTE_LIKE_ZSET_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/domain/dataobject/NoteLikeDO.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/dataobject/NoteLikeDO.java index bfd4fa0..1613305 100644 --- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/dataobject/NoteLikeDO.java +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/dataobject/NoteLikeDO.java @@ -4,12 +4,13 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.util.Date; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + /** * 笔记点赞表 */ @@ -41,11 +42,11 @@ public class NoteLikeDO { * 创建时间 */ @TableField(value = "create_time") - private Date createTime; + private LocalDateTime createTime; /** * 点赞状态(0:取消点赞 1:点赞) */ @TableField(value = "`status`") - private Byte status; + private Integer status; } \ No newline at end of file diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/mapper/NoteLikeDOMapper.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/mapper/NoteLikeDOMapper.java index dae97c1..665b4d4 100644 --- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/mapper/NoteLikeDOMapper.java +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/domain/mapper/NoteLikeDOMapper.java @@ -6,4 +6,11 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface NoteLikeDOMapper extends BaseMapper { + /** + * 新增笔记点赞记录,若已存在,则更新笔记点赞记录 + * + * @param noteLikeDO 笔记点赞记录 + * @return 影响行数 + */ + boolean insertOrUpdate(NoteLikeDO noteLikeDO); } \ No newline at end of file diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/LikeUnlikeNoteTypeEnum.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/LikeUnlikeNoteTypeEnum.java new file mode 100644 index 0000000..db6ab86 --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/LikeUnlikeNoteTypeEnum.java @@ -0,0 +1,17 @@ +package com.hanserwei.hannote.note.biz.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum LikeUnlikeNoteTypeEnum { + // 点赞 + LIKE(1), + // 取消点赞 + UNLIKE(0), + ; + + private final Integer code; + +} \ No newline at end of file diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteLikeLuaResultEnum.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteLikeLuaResultEnum.java index d38846b..67c6ac6 100644 --- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteLikeLuaResultEnum.java +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteLikeLuaResultEnum.java @@ -9,7 +9,9 @@ import java.util.Objects; @AllArgsConstructor public enum NoteLikeLuaResultEnum { // 布隆过滤器不存在 - BLOOM_NOT_EXIST(-1L), + NOT_EXIST(-1L), + // 笔记点赞成功 + NOTE_LIKE_SUCCESS(0L), // 笔记已点赞 NOTE_LIKED(1L), ; diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/model/dto/LikeUnlikeNoteMqDTO.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/model/dto/LikeUnlikeNoteMqDTO.java new file mode 100644 index 0000000..3740dc5 --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/model/dto/LikeUnlikeNoteMqDTO.java @@ -0,0 +1,26 @@ +package com.hanserwei.hannote.note.biz.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class LikeUnlikeNoteMqDTO { + + private Long userId; + + private Long noteId; + + /** + * 0: 取消点赞, 1:点赞 + */ + private Integer type; + + private LocalDateTime createTime; +} \ 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 bfa230a..bed1ad2 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 @@ -3,6 +3,7 @@ package com.hanserwei.hannote.note.biz.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.RandomUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -11,6 +12,7 @@ import com.google.common.collect.Lists; import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder; import com.hanserwei.framework.common.exception.ApiException; import com.hanserwei.framework.common.response.Response; +import com.hanserwei.framework.common.utils.DateUtils; import com.hanserwei.framework.common.utils.JsonUtils; import com.hanserwei.hannote.note.biz.constant.MQConstants; import com.hanserwei.hannote.note.biz.constant.RedisKeyConstants; @@ -19,6 +21,7 @@ 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.enums.*; +import com.hanserwei.hannote.note.biz.model.dto.LikeUnlikeNoteMqDTO; import com.hanserwei.hannote.note.biz.model.vo.*; import com.hanserwei.hannote.note.biz.rpc.DistributedIdGeneratorRpcService; import com.hanserwei.hannote.note.biz.rpc.KeyValueRpcService; @@ -577,10 +580,13 @@ public class NoteServiceImpl extends ServiceImpl implement NoteLikeLuaResultEnum noteLikeLuaResultEnum = NoteLikeLuaResultEnum.valueOf(result); + // 用户点赞列表 ZSet Key + String userNoteLikeZSetKey = RedisKeyConstants.buildUserNoteLikeZSetKey(userId); + assert noteLikeLuaResultEnum != null; switch (noteLikeLuaResultEnum) { // Redis 中布隆过滤器不存在 - case BLOOM_NOT_EXIST -> { + case NOT_EXIST -> { // TODO: 从数据库中校验笔记是否被点赞,并异步初始化布隆过滤器,设置过期时间 long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class) .eq(NoteLikeDO::getNoteId, noteId) @@ -604,15 +610,159 @@ public class NoteServiceImpl extends ServiceImpl implement redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), noteId, expireSeconds); } // 目标笔记已经被点赞 - case NOTE_LIKED -> throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED); + 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); + } + } } // 3. 更新用户 ZSET 点赞列表 + LocalDateTime now = LocalDateTime.now(); + // lua 脚本路径 + script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/note_like_check_and_update_zset.lua"))); + // 返回值类型 + script.setResultType(Long.class); + // 执行 Lua 脚本,拿到返回结果 + result = redisTemplate.execute(script, Collections.singletonList(userNoteLikeZSetKey), noteId, DateUtils.localDateTime2Timestamp(now)); + // 若 ZSet 列表不存在,需要重新初始化 + if (Objects.equals(result, NoteLikeLuaResultEnum.NOT_EXIST.getCode())) { + // 查询当前用户最新点赞的100篇笔记 + Page page = noteLikeDOService.page(new Page<>(1, 100), + new LambdaQueryWrapper<>(NoteLikeDO.class) + .select(NoteLikeDO::getNoteId, NoteLikeDO::getCreateTime) + .eq(NoteLikeDO::getUserId, userId) + .eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()) + .orderByDesc(NoteLikeDO::getCreateTime)); + List noteLikeDOS = page.getRecords(); + // 保底1天+随机秒数 + long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24); + DefaultRedisScript script2 = new DefaultRedisScript<>(); + // Lua 脚本路径 + script2.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/batch_add_note_like_zset_and_expire.lua"))); + // 返回值类型 + script2.setResultType(Long.class); + // 若数据库中存在点赞记录,需要批量同步 + if (CollUtil.isNotEmpty(noteLikeDOS)) { + // 构建 Lua 参数 + Object[] luaArgs = buildNoteLikeZSetLuaArgs(noteLikeDOS, expireSeconds); + + redisTemplate.execute(script2, Collections.singletonList(userNoteLikeZSetKey), luaArgs); + + // 再次调用 note_like_check_and_update_zset.lua 脚本,将点赞的笔记添加到 zset 中 + redisTemplate.execute(script, Collections.singletonList(userNoteLikeZSetKey), noteId, DateUtils.localDateTime2Timestamp(now)); + } else { // 若数据库中,无点赞过的笔记记录,则直接将当前点赞的笔记 ID 添加到 ZSet 中,随机过期时间 + List luaArgs = Lists.newArrayList(); + luaArgs.add(DateUtils.localDateTime2Timestamp(LocalDateTime.now())); // score :点赞时间戳 + luaArgs.add(noteId); // 当前点赞的笔记 ID + luaArgs.add(expireSeconds); // 随机过期时间 + + redisTemplate.execute(script2, Collections.singletonList(userNoteLikeZSetKey), luaArgs.toArray()); + } + + } // 4. 发送 MQ, 将点赞数据落库 + // 构建MQ消息体 + LikeUnlikeNoteMqDTO likeUnlikeNoteMqDTO = LikeUnlikeNoteMqDTO.builder() + .userId(userId) + .noteId(noteId) + .type(LikeStatusEnum.LIKE.getCode()) // 点赞 + .createTime(now) + .build(); + // 构建消息,将DTO转换为JSON字符串设置到消息体中 + Message message = MessageBuilder.withPayload(JsonUtils.toJsonString(likeUnlikeNoteMqDTO)).build(); + // 通过冒号连接, 可让 MQ 发送给主题 Topic 时,携带上标签 Tag + String destination = MQConstants.TOPIC_LIKE_OR_UNLIKE + ":" + MQConstants.TAG_LIKE; + String hashKey = String.valueOf(noteId); + // 异步发送 MQ 消息,提升接口响应速度 + rocketMQTemplate.asyncSendOrderly(destination, message, hashKey, new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + log.info("==> 【笔记点赞】MQ 发送成功,SendResult: {}", sendResult); + } + + @Override + public void onException(Throwable throwable) { + log.error("==> 【笔记点赞】MQ 发送异常: ", throwable); + } + }); return Response.success(); } + /** + * 异步初始化用户点赞笔记 ZSet + * + * @param userId 用户ID + * @param userNoteLikeZSetKey 用户点赞笔记 ZSet KEY + */ + private void asynInitUserNoteLikesZSet(Long userId, String userNoteLikeZSetKey) { + threadPoolTaskExecutor.submit(() -> { + // 判断用户笔记点赞 ZSET 是否存在 + boolean hasKey = redisTemplate.hasKey(userNoteLikeZSetKey); + + // 若不存在,则初始化 + if (!hasKey) { + // 查询当前用户最新点赞的100篇笔记 + Page page = noteLikeDOService.page(new Page<>(1, 100), + new LambdaQueryWrapper<>(NoteLikeDO.class) + .select(NoteLikeDO::getNoteId, NoteLikeDO::getCreateTime) + .eq(NoteLikeDO::getUserId, userId) + .eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()) + .orderByDesc(NoteLikeDO::getCreateTime)); + List noteLikeDOS = page.getRecords(); + if (CollUtil.isNotEmpty(noteLikeDOS)) { + // 保底1天+随机秒数 + long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24); + // 构建 Lua 参数 + Object[] luaArgs = buildNoteLikeZSetLuaArgs(noteLikeDOS, expireSeconds); + + DefaultRedisScript script2 = new DefaultRedisScript<>(); + // Lua 脚本路径 + script2.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/batch_add_note_like_zset_and_expire.lua"))); + // 返回值类型 + script2.setResultType(Long.class); + + redisTemplate.execute(script2, Collections.singletonList(userNoteLikeZSetKey), luaArgs); + } + } + }); + } + + /** + * 构建 Lua 脚本参数 + * + * @param noteLikeDOS 点赞记录 + * @param expireSeconds 过期时间(秒) + * @return 参数列表 + */ + private Object[] buildNoteLikeZSetLuaArgs(List noteLikeDOS, long expireSeconds) { + int argsLength = noteLikeDOS.size() * 2 + 1; // 每个笔记点赞关系有 2 个参数(score 和 value),最后再跟一个过期时间 + Object[] luaArgs = new Object[argsLength]; + + int i = 0; + for (NoteLikeDO noteLikeDO : noteLikeDOS) { + luaArgs[i] = DateUtils.localDateTime2Timestamp(noteLikeDO.getCreateTime()); // 点赞时间作为 score + luaArgs[i + 1] = noteLikeDO.getNoteId(); // 笔记ID 作为 ZSet value + i += 2; + } + + luaArgs[argsLength - 1] = expireSeconds; // 最后一个参数是 ZSet 的过期时间 + return luaArgs; + } + /** * 异步批量添加笔记点赞记录到布隆过滤器中,并设置过期时间 * diff --git a/han-note-note/han-note-note-biz/src/main/resources/lua/batch_add_note_like_zset_and_expire.lua b/han-note-note/han-note-note-biz/src/main/resources/lua/batch_add_note_like_zset_and_expire.lua new file mode 100644 index 0000000..df15eb6 --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/resources/lua/batch_add_note_like_zset_and_expire.lua @@ -0,0 +1,20 @@ +-- 操作的 Key +local key = KEYS[1] + +-- 准备批量添加数据的参数表 +local zaddArgs = {} + +-- 遍历 ARGV 参数,将分数和值按顺序插入到 zaddArgs 变量中 +for i = 1, #ARGV - 1, 2 do + table.insert(zaddArgs, ARGV[i]) -- 分数(点赞时间) + table.insert(zaddArgs, ARGV[i + 1]) -- 值(笔记ID) +end + +-- 调用 ZADD 批量插入数据 +redis.call('ZADD', key, unpack(zaddArgs)) + +-- 设置 ZSet 的过期时间 +local expireTime = ARGV[#ARGV] -- 最后一个参数为过期时间 +redis.call('EXPIRE', key, expireTime) + +return 0 diff --git a/han-note-note/han-note-note-biz/src/main/resources/lua/note_like_check_and_update_zset.lua b/han-note-note/han-note-note-biz/src/main/resources/lua/note_like_check_and_update_zset.lua new file mode 100644 index 0000000..9b6c6e3 --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/resources/lua/note_like_check_and_update_zset.lua @@ -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) + +-- 若已经点赞了 100 篇笔记,则移除最早点赞的那篇 +if size >= 100 then + redis.call('ZPOPMIN', key) +end + +-- 添加新的笔记点赞关系 +redis.call('ZADD', key, timestamp, noteId) +return 0 diff --git a/han-note-note/han-note-note-biz/src/main/resources/mapperxml/NoteLikeDOMapper.xml b/han-note-note/han-note-note-biz/src/main/resources/mapperxml/NoteLikeDOMapper.xml index f064bdd..8a172f7 100644 --- a/han-note-note/han-note-note-biz/src/main/resources/mapperxml/NoteLikeDOMapper.xml +++ b/han-note-note/han-note-note-biz/src/main/resources/mapperxml/NoteLikeDOMapper.xml @@ -14,4 +14,11 @@ id, user_id, note_id, create_time, `status` + + + INSERT INTO t_note_like (user_id, note_id, create_time, status) + VALUES (#{userId}, #{noteId}, #{createTime}, #{status}) + ON DUPLICATE KEY UPDATE + create_time = #{createTime}, status = #{status}; + \ No newline at end of file diff --git a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java b/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java index 26095a0..ba34355 100644 --- a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java +++ b/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java @@ -2,6 +2,7 @@ package com.hanserwei.hannote.user.relation.biz.consumer; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.google.common.util.concurrent.RateLimiter; +import com.hanserwei.framework.common.utils.DateUtils; import com.hanserwei.framework.common.utils.JsonUtils; import com.hanserwei.hannote.user.relation.biz.constant.MQConstants; import com.hanserwei.hannote.user.relation.biz.constant.RedisKeyConstants; @@ -13,7 +14,6 @@ import com.hanserwei.hannote.user.relation.biz.model.dto.FollowUserMqDTO; import com.hanserwei.hannote.user.relation.biz.model.dto.UnfollowUserMqDTO; import com.hanserwei.hannote.user.relation.biz.service.FansDOService; import com.hanserwei.hannote.user.relation.biz.service.FollowingDOService; -import com.hanserwei.hannote.user.relation.biz.util.DateUtils; import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/service/impl/RelationServiceImpl.java b/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/service/impl/RelationServiceImpl.java index 836c285..1769686 100644 --- a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/service/impl/RelationServiceImpl.java +++ b/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/service/impl/RelationServiceImpl.java @@ -8,6 +8,7 @@ import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder; import com.hanserwei.framework.common.exception.ApiException; import com.hanserwei.framework.common.response.PageResponse; import com.hanserwei.framework.common.response.Response; +import com.hanserwei.framework.common.utils.DateUtils; import com.hanserwei.framework.common.utils.JsonUtils; import com.hanserwei.hannote.user.dto.req.FindFollowingListReqVO; import com.hanserwei.hannote.user.dto.resp.FindFollowingUserRspVO; @@ -28,7 +29,6 @@ import com.hanserwei.hannote.user.relation.biz.rpc.UserRpcService; import com.hanserwei.hannote.user.relation.biz.service.FansDOService; import com.hanserwei.hannote.user.relation.biz.service.FollowingDOService; import com.hanserwei.hannote.user.relation.biz.service.RelationService; -import com.hanserwei.hannote.user.relation.biz.util.DateUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.client.producer.SendCallback; diff --git a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/util/DateUtils.java b/hanserwei-framework/hanserwei-common/src/main/java/com/hanserwei/framework/common/utils/DateUtils.java similarity index 87% rename from han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/util/DateUtils.java rename to hanserwei-framework/hanserwei-common/src/main/java/com/hanserwei/framework/common/utils/DateUtils.java index 73f8241..369656c 100644 --- a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/util/DateUtils.java +++ b/hanserwei-framework/hanserwei-common/src/main/java/com/hanserwei/framework/common/utils/DateUtils.java @@ -1,4 +1,4 @@ -package com.hanserwei.hannote.user.relation.biz.util; +package com.hanserwei.framework.common.utils; import java.time.LocalDateTime; import java.time.ZoneOffset;