feat(data-align): bug修复,目前取消点赞接口可以重复访问,已经修改,但后续考虑换掉布隆过滤器。
- 新增 MQ 常量定义文件 MQConstants.java - 新增 RocketMQ 配置类 RocketMQConfig.java - 新增笔记点赞增量数据消费类 TodayNoteLikeIncrementData2DBConsumer- 引入 Jackson 和 RocketMQ Starter 依赖 -优化笔记取消点赞逻辑,增强布隆过滤器处理 - 增强日志记录,包括布隆过滤器初始化和执行结果- 添加空值检查以提高代码健壮性 - 修复笔记服务中重复注释问题
This commit is contained in:
@@ -17,7 +17,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
@SuppressWarnings("ALL")
|
@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
|
topic = MQConstants.TOPIC_COUNT_FOLLOWING_2_DB // 主题 Topic
|
||||||
)
|
)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -83,6 +83,19 @@
|
|||||||
<groupId>com.xuxueli</groupId>
|
<groupId>com.xuxueli</groupId>
|
||||||
<artifactId>xxl-job-core</artifactId>
|
<artifactId>xxl-job-core</artifactId>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.hanserwei.hannote.data.align.constant;
|
||||||
|
|
||||||
|
public interface MQConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 计数 - 笔记点赞数
|
||||||
|
*/
|
||||||
|
String TOPIC_COUNT_NOTE_LIKE = "CountNoteLikeTopic";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,7 @@ public class LikeUnlikeNoteConsumer implements RocketMQListener<Message> {
|
|||||||
|
|
||||||
// 执行更新
|
// 执行更新
|
||||||
boolean update = noteLikeDOService.update(updateEntity, wrapper);
|
boolean update = noteLikeDOService.update(updateEntity, wrapper);
|
||||||
|
log.info("==> 【取消点赞笔记】更新数据库成功,update: {}", update);
|
||||||
|
|
||||||
if (!update) {
|
if (!update) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -792,16 +792,15 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
|
|
||||||
NoteUnlikeLuaResultEnum noteUnlikeLuaResultEnum = NoteUnlikeLuaResultEnum.valueOf(result);
|
NoteUnlikeLuaResultEnum noteUnlikeLuaResultEnum = NoteUnlikeLuaResultEnum.valueOf(result);
|
||||||
log.info("==> 【笔记取消点赞】Lua 脚本返回结果: {}", noteUnlikeLuaResultEnum);
|
log.info("==> 【笔记取消点赞】Lua 脚本返回结果: {}", noteUnlikeLuaResultEnum);
|
||||||
assert noteUnlikeLuaResultEnum != null;
|
switch (Objects.requireNonNull(noteUnlikeLuaResultEnum)) {
|
||||||
switch (noteUnlikeLuaResultEnum) {
|
|
||||||
// 布隆过滤器不存在
|
// 布隆过滤器不存在
|
||||||
case NOT_EXIST -> {
|
case NOT_EXIST -> {//笔记不存在
|
||||||
//笔记不存在
|
|
||||||
//异步初始化布隆过滤器
|
//异步初始化布隆过滤器
|
||||||
threadPoolTaskExecutor.submit(() -> {
|
threadPoolTaskExecutor.submit(() -> {
|
||||||
// 保底1天+随机秒数
|
// 保底1天+随机秒数
|
||||||
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
|
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
|
||||||
batchAddNoteLike2BloomAndExpire(userId, expireSeconds, bloomUserNoteLikeListKey);
|
batchAddNoteLike2BloomAndExpire(userId, expireSeconds, bloomUserNoteLikeListKey);
|
||||||
|
});
|
||||||
// 从数据库中校验笔记是否被点赞
|
// 从数据库中校验笔记是否被点赞
|
||||||
long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
||||||
.eq(NoteLikeDO::getUserId, userId)
|
.eq(NoteLikeDO::getUserId, userId)
|
||||||
@@ -811,7 +810,6 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
log.info("==> 【笔记取消点赞】用户未点赞该笔记");
|
log.info("==> 【笔记取消点赞】用户未点赞该笔记");
|
||||||
throw new ApiException(ResponseCodeEnum.NOTE_NOT_LIKED);
|
throw new ApiException(ResponseCodeEnum.NOTE_NOT_LIKED);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// 布隆过滤器校验目标笔记未被点赞(判断绝对正确)
|
// 布隆过滤器校验目标笔记未被点赞(判断绝对正确)
|
||||||
case NOTE_NOT_LIKED -> 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
|
// 用户点赞列表ZsetKey
|
||||||
String userNoteLikeZSetKey = RedisKeyConstants.buildUserNoteLikeZSetKey(userId);
|
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, 数据更新落库
|
//4. 发送 MQ, 数据更新落库
|
||||||
// 构建MQ消息体
|
// 构建MQ消息体
|
||||||
@@ -932,7 +937,6 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 更新用户 ZSET 收藏列表
|
|
||||||
// 3. 更新用户 ZSET 收藏列表
|
// 3. 更新用户 ZSET 收藏列表
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
// Lua 脚本路径
|
// Lua 脚本路径
|
||||||
@@ -1271,11 +1275,14 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
*/
|
*/
|
||||||
private void batchAddNoteLike2BloomAndExpire(Long userId, long expireSeconds, String bloomUserNoteLikeListKey) {
|
private void batchAddNoteLike2BloomAndExpire(Long userId, long expireSeconds, String bloomUserNoteLikeListKey) {
|
||||||
try {
|
try {
|
||||||
|
log.info("## 异步初始化【笔记点赞】布隆过滤器开始: userId={}", userId);
|
||||||
// 异步全量同步一下,并设置过期时间
|
// 异步全量同步一下,并设置过期时间
|
||||||
List<NoteLikeDO> noteLikeDOS = noteLikeDOService.list(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
List<NoteLikeDO> noteLikeDOS = noteLikeDOService.list(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
||||||
.select(NoteLikeDO::getNoteId)
|
.select(NoteLikeDO::getNoteId)
|
||||||
.eq(NoteLikeDO::getUserId, userId)
|
.eq(NoteLikeDO::getUserId, userId)
|
||||||
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
|
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
|
||||||
|
log.info("## 异步初始化【笔记点赞】布隆过滤器,用户笔记点赞数量: {},笔记ID:{}", noteLikeDOS.size(),
|
||||||
|
JsonUtils.toJsonString(noteLikeDOS.stream().map(NoteLikeDO::getNoteId).toList()));
|
||||||
|
|
||||||
if (CollUtil.isNotEmpty(noteLikeDOS)) {
|
if (CollUtil.isNotEmpty(noteLikeDOS)) {
|
||||||
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
@@ -1288,7 +1295,8 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
List<Object> luaArgs = Lists.newArrayList();
|
List<Object> luaArgs = Lists.newArrayList();
|
||||||
noteLikeDOS.forEach(noteLikeDO -> luaArgs.add(noteLikeDO.getNoteId())); // 将每个点赞的笔记 ID 传入
|
noteLikeDOS.forEach(noteLikeDO -> luaArgs.add(noteLikeDO.getNoteId())); // 将每个点赞的笔记 ID 传入
|
||||||
luaArgs.add(expireSeconds); // 最后一个参数是过期时间(秒)
|
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) {
|
} catch (Exception e) {
|
||||||
log.error("## 异步初始化布隆过滤器异常: ", e);
|
log.error("## 异步初始化布隆过滤器异常: ", e);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
@Component
|
@Component
|
||||||
@RocketMQMessageListener(
|
@RocketMQMessageListener(
|
||||||
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW, //han_note_group_FollowUnfollowTopic
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW, //han_note_group_FollowUnfollowTopic
|
||||||
|
|||||||
Reference in New Issue
Block a user