feat(data-align): bug修复,目前取消点赞接口可以重复访问,已经修改,但后续考虑换掉布隆过滤器。

- 新增 MQ 常量定义文件 MQConstants.java
- 新增 RocketMQ 配置类 RocketMQConfig.java
- 新增笔记点赞增量数据消费类 TodayNoteLikeIncrementData2DBConsumer- 引入 Jackson 和 RocketMQ Starter 依赖
-优化笔记取消点赞逻辑,增强布隆过滤器处理
- 增强日志记录,包括布隆过滤器初始化和执行结果- 添加空值检查以提高代码健壮性
- 修复笔记服务中重复注释问题
This commit is contained in:
2025-10-21 19:14:20 +08:00
parent f217b8133a
commit 8a1681e590
8 changed files with 80 additions and 17 deletions

View File

@@ -17,7 +17,7 @@ import java.util.Objects;
@Component
@SuppressWarnings("ALL")
@RocketMQMessageListener(consumerGroup = "han_note_" + MQConstants.TOPIC_COUNT_FOLLOWING_2_DB, // Group 组
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FOLLOWING_2_DB, // Group 组
topic = MQConstants.TOPIC_COUNT_FOLLOWING_2_DB // 主题 Topic
)
@Slf4j

View File

@@ -83,6 +83,19 @@
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<!-- Jackson 组件 -->
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-spring-boot-starter-jackson</artifactId>
</dependency>
<!-- Rocket MQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,10 @@
package com.hanserwei.hannote.data.align.config;
import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(RocketMQAutoConfiguration.class)
public class RocketMQConfig {
}

View File

@@ -0,0 +1,10 @@
package com.hanserwei.hannote.data.align.constant;
public interface MQConstants {
/**
* Topic: 计数 - 笔记点赞数
*/
String TOPIC_COUNT_NOTE_LIKE = "CountNoteLikeTopic";
}

View File

@@ -0,0 +1,20 @@
package com.hanserwei.hannote.data.align.consumer;
import com.hanserwei.hannote.data.align.constant.MQConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RocketMQMessageListener(
consumerGroup = "han_note_group_data_align_" + MQConstants.TOPIC_COUNT_NOTE_LIKE,
topic = MQConstants.TOPIC_COUNT_NOTE_LIKE
)
public class TodayNoteLikeIncrementData2DBConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String body) {
log.info("## TodayNoteLikeIncrementData2DBConsumer 消费到了 MQ: {}", body);
}
}

View File

@@ -99,6 +99,7 @@ public class LikeUnlikeNoteConsumer implements RocketMQListener<Message> {
// 执行更新
boolean update = noteLikeDOService.update(updateEntity, wrapper);
log.info("==> 【取消点赞笔记】更新数据库成功update: {}", update);
if (!update) {
return;

View File

@@ -792,26 +792,24 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
NoteUnlikeLuaResultEnum noteUnlikeLuaResultEnum = NoteUnlikeLuaResultEnum.valueOf(result);
log.info("==> 【笔记取消点赞】Lua 脚本返回结果: {}", noteUnlikeLuaResultEnum);
assert noteUnlikeLuaResultEnum != null;
switch (noteUnlikeLuaResultEnum) {
switch (Objects.requireNonNull(noteUnlikeLuaResultEnum)) {
// 布隆过滤器不存在
case NOT_EXIST -> {
//笔记不存在
case NOT_EXIST -> {//笔记不存在
//异步初始化布隆过滤器
threadPoolTaskExecutor.submit(() -> {
// 保底1天+随机秒数
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
batchAddNoteLike2BloomAndExpire(userId, expireSeconds, bloomUserNoteLikeListKey);
// 从数据库中校验笔记是否被点赞
long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
.eq(NoteLikeDO::getUserId, userId)
.eq(NoteLikeDO::getNoteId, noteId)
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
if (count == 0) {
log.info("==> 【笔记取消点赞】用户未点赞该笔记");
throw new ApiException(ResponseCodeEnum.NOTE_NOT_LIKED);
}
});
// 从数据库中校验笔记是否被点赞
long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
.eq(NoteLikeDO::getUserId, userId)
.eq(NoteLikeDO::getNoteId, noteId)
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
if (count == 0) {
log.info("==> 【笔记取消点赞】用户未点赞该笔记");
throw new ApiException(ResponseCodeEnum.NOTE_NOT_LIKED);
}
}
// 布隆过滤器校验目标笔记未被点赞(判断绝对正确)
case NOTE_NOT_LIKED -> throw new ApiException(ResponseCodeEnum.NOTE_NOT_LIKED);
@@ -821,7 +819,14 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
// 用户点赞列表ZsetKey
String userNoteLikeZSetKey = RedisKeyConstants.buildUserNoteLikeZSetKey(userId);
redisTemplate.opsForZSet().remove(userNoteLikeZSetKey, noteId);
// TODO: 后续考虑换掉布隆过滤器。
Long removed = redisTemplate.opsForZSet().remove(userNoteLikeZSetKey, noteId);
if (Objects.nonNull(removed) && removed == 0) {
log.info("==> 【笔记取消点赞】用户未点赞该笔记");
throw new ApiException(ResponseCodeEnum.NOTE_NOT_LIKED);
}
//4. 发送 MQ, 数据更新落库
// 构建MQ消息体
@@ -932,7 +937,6 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
}
}
// 3. 更新用户 ZSET 收藏列表
// 3. 更新用户 ZSET 收藏列表
LocalDateTime now = LocalDateTime.now();
// Lua 脚本路径
@@ -1271,11 +1275,14 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
*/
private void batchAddNoteLike2BloomAndExpire(Long userId, long expireSeconds, String bloomUserNoteLikeListKey) {
try {
log.info("## 异步初始化【笔记点赞】布隆过滤器开始: userId={}", userId);
// 异步全量同步一下,并设置过期时间
List<NoteLikeDO> noteLikeDOS = noteLikeDOService.list(new LambdaQueryWrapper<>(NoteLikeDO.class)
.select(NoteLikeDO::getNoteId)
.eq(NoteLikeDO::getUserId, userId)
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
log.info("## 异步初始化【笔记点赞】布隆过滤器,用户笔记点赞数量: {},笔记ID:{}", noteLikeDOS.size(),
JsonUtils.toJsonString(noteLikeDOS.stream().map(NoteLikeDO::getNoteId).toList()));
if (CollUtil.isNotEmpty(noteLikeDOS)) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
@@ -1288,7 +1295,8 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
List<Object> luaArgs = Lists.newArrayList();
noteLikeDOS.forEach(noteLikeDO -> luaArgs.add(noteLikeDO.getNoteId())); // 将每个点赞的笔记 ID 传入
luaArgs.add(expireSeconds); // 最后一个参数是过期时间(秒)
redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), luaArgs.toArray());
Long result = redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), luaArgs.toArray());
log.info("## 异步初始化【笔记点赞】布隆过滤器结果: {}", result);
}
} catch (Exception e) {
log.error("## 异步初始化布隆过滤器异常: ", e);

View File

@@ -36,6 +36,7 @@ import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Objects;
@SuppressWarnings("UnstableApiUsage")
@Component
@RocketMQMessageListener(
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW, //han_note_group_FollowUnfollowTopic