feat(count): 新增笔记收藏与点赞计数聚合功能,用户维度统计功能
- 新增 AggregationCountCollectedUncollectedNoteMqDTO 和 AggregationCountLikeUnlikeNoteMqDTO 聚合消息体 - 在 CollectUnCollectNoteMqDTO、CountCollectUnCollectNoteMqDTO 和 CountLikeUnlikeNoteMqDTO 中添加 noteCreatorId 字段 - 优化 CountNoteCollect2DBConsumer 和 CountNoteLike2DBConsumer 消费者逻辑,支持事务性更新用户及笔记计数 - 修改 CountNoteCollectConsumer 和 CountNoteLikeConsumer,使用聚合 DTO 替代 Map 结构处理计数逻辑 - 扩展 JsonUtils 工具类,新增 parseList 方法用于解析 JSON 到 List 对象 - 更新 NoteServiceImpl 中点赞和收藏相关方法,补充获取并传递 noteCreatorId 参数 - 在 UserCountDOMapper 及其 XML 映射文件中新增点赞数和收藏数的插入或更新操作接口
This commit is contained in:
@@ -5,13 +5,16 @@ import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||
import com.hanserwei.hannote.count.biz.domain.mapper.NoteCountDOMapper;
|
||||
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountCollectedUncollectedNoteMqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@Component
|
||||
@@ -26,6 +29,10 @@ public class CountNoteCollect2DBConsumer implements RocketMQListener<String> {
|
||||
private final RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||
@Resource
|
||||
private NoteCountDOMapper noteCountDOMapper;
|
||||
@Resource
|
||||
private UserCountDOMapper userCountDOMapper;
|
||||
@Resource
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
@Override
|
||||
public void onMessage(String body) {
|
||||
@@ -34,16 +41,32 @@ public class CountNoteCollect2DBConsumer implements RocketMQListener<String> {
|
||||
|
||||
log.info("## 消费到了 MQ 【计数: 笔记收藏数入库】, {}...", body);
|
||||
|
||||
Map<Long, Integer> countMap = null;
|
||||
List<AggregationCountCollectedUncollectedNoteMqDTO> countList = null;
|
||||
try {
|
||||
countMap = JsonUtils.parseMap(body, Long.class, Integer.class);
|
||||
countList = JsonUtils.parseList(body, AggregationCountCollectedUncollectedNoteMqDTO.class);
|
||||
} catch (Exception e) {
|
||||
log.error("## 解析 JSON 字符串异常", e);
|
||||
log.error("## 解析 JSON 字符串异常");
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(countMap)) {
|
||||
// 判断数据库中 t_note_count 表,若笔记计数记录不存在,则插入;若记录已存在,则直接更新
|
||||
countMap.forEach((k, v) -> noteCountDOMapper.insertOrUpdateCollectTotalByNoteId(v, k));
|
||||
if (CollUtil.isNotEmpty(countList)) {
|
||||
countList.forEach(item -> {
|
||||
Long creatorId = item.getCreatorId();
|
||||
Long noteId = item.getNoteId();
|
||||
Integer count = item.getCount();
|
||||
|
||||
// 编程式事务,保证两条语句的原子性
|
||||
transactionTemplate.execute(status -> {
|
||||
try {
|
||||
noteCountDOMapper.insertOrUpdateCollectTotalByNoteId(count, noteId);
|
||||
userCountDOMapper.insertOrUpdateCollectTotalByUserId(count, creatorId);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
status.setRollbackOnly(); // 标记事务为回滚
|
||||
log.error("", ex);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.hanserwei.hannote.count.biz.consumer;
|
||||
|
||||
import com.github.phantomthief.collection.BufferTrigger;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||
import com.hanserwei.hannote.count.biz.enums.CollectUnCollectNoteTypeEnum;
|
||||
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountCollectedUncollectedNoteMqDTO;
|
||||
import com.hanserwei.hannote.count.biz.model.dto.CountCollectUnCollectNoteMqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -63,14 +64,18 @@ public class CountNoteCollectConsumer implements RocketMQListener<String> {
|
||||
Map<Long, List<CountCollectUnCollectNoteMqDTO>> groupMap = countCollectUnCollectNoteMqDTOS.stream()
|
||||
.collect(Collectors.groupingBy(CountCollectUnCollectNoteMqDTO::getNoteId));
|
||||
// 按组汇总数据,统计出最终的计数
|
||||
// key 为笔记 ID, value 为最终操作的计数
|
||||
Map<Long, Integer> countMap = Maps.newHashMap();
|
||||
List<AggregationCountCollectedUncollectedNoteMqDTO> countList = Lists.newArrayList();
|
||||
for (Map.Entry<Long, List<CountCollectUnCollectNoteMqDTO>> entry : groupMap.entrySet()) {
|
||||
// 笔记 ID
|
||||
Long noteId = entry.getKey();
|
||||
// 笔记发布者 ID
|
||||
Long creatorId = null;
|
||||
List<CountCollectUnCollectNoteMqDTO> list = entry.getValue();
|
||||
// 默认计数为0
|
||||
int finalCount = 0;
|
||||
for (CountCollectUnCollectNoteMqDTO countCollectUnCollectNoteMqDTO : list) {
|
||||
Integer type = countCollectUnCollectNoteMqDTO.getType();
|
||||
creatorId = countCollectUnCollectNoteMqDTO.getNoteCreatorId();
|
||||
// 获取枚举类
|
||||
CollectUnCollectNoteTypeEnum collectUnCollectNoteTypeEnum = CollectUnCollectNoteTypeEnum.valueOf(type);
|
||||
switch (Objects.requireNonNull(collectUnCollectNoteTypeEnum)) {
|
||||
@@ -78,28 +83,46 @@ public class CountNoteCollectConsumer implements RocketMQListener<String> {
|
||||
case UN_COLLECT -> finalCount--;
|
||||
}
|
||||
}
|
||||
// 将分组后统计出的最终计数,存入 countMap 中
|
||||
countMap.put(entry.getKey(), finalCount);
|
||||
// 将分组后统计出的最终计数,存入 countList 中
|
||||
countList.add(AggregationCountCollectedUncollectedNoteMqDTO.builder()
|
||||
.noteId(noteId)
|
||||
.creatorId(creatorId)
|
||||
.count(finalCount)
|
||||
.build());
|
||||
}
|
||||
log.info("==> 【笔记收藏数】最终结果, {}", JsonUtils.toJsonString(countMap));
|
||||
log.info("==> 【笔记收藏数】最终结果, {}", JsonUtils.toJsonString(countList));
|
||||
|
||||
// 更新 Redis
|
||||
countMap.forEach((k, v) -> {
|
||||
// Redis Hash Key
|
||||
String redisKey = RedisKeyConstants.buildCountNoteKey(k);
|
||||
// 判断 Redis 中 Hash 是否存在
|
||||
boolean isExisted = redisTemplate.hasKey(redisKey);
|
||||
countList.forEach(item -> {
|
||||
// 笔记发布者 ID
|
||||
Long creatorId = item.getCreatorId();
|
||||
// 笔记 ID
|
||||
Long noteId = item.getNoteId();
|
||||
// 聚合后的计数
|
||||
Integer count = item.getCount();
|
||||
|
||||
// 笔记维度计数 Redis Key
|
||||
String countNoteRedisKey = RedisKeyConstants.buildCountNoteKey(noteId);
|
||||
// 判断Redis 中 Hash 是否存在
|
||||
boolean isCountNoteExisted = redisTemplate.hasKey(countNoteRedisKey);
|
||||
// 若存在才会更新
|
||||
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||
if (isExisted) {
|
||||
// 对目标用户 Hash 中的收藏总数字段进行计数操作
|
||||
redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_COLLECT_TOTAL, v);
|
||||
if (isCountNoteExisted) {
|
||||
// 对目标用户 Hash 中的点赞数字段进行计数操作
|
||||
redisTemplate.opsForHash().increment(countNoteRedisKey, RedisKeyConstants.FIELD_COLLECT_TOTAL, count);
|
||||
}
|
||||
|
||||
// 更新 Redis 用户维度收藏数
|
||||
String countUserRedisKey = RedisKeyConstants.buildCountUserKey(creatorId);
|
||||
Boolean isCountUserExisted = redisTemplate.hasKey(countUserRedisKey);
|
||||
if (isCountUserExisted) {
|
||||
// 对目标用户 Hash 中的收藏数字段进行计数操作
|
||||
redisTemplate.opsForHash().increment(countUserRedisKey, RedisKeyConstants.FIELD_COLLECT_TOTAL, count);
|
||||
}
|
||||
});
|
||||
|
||||
// 发送 MQ, 笔记收藏数据落库
|
||||
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countMap))
|
||||
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countList))
|
||||
.build();
|
||||
|
||||
// 异步发送 MQ 消息
|
||||
|
||||
@@ -5,13 +5,16 @@ import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||
import com.hanserwei.hannote.count.biz.domain.mapper.NoteCountDOMapper;
|
||||
import com.hanserwei.hannote.count.biz.domain.mapper.UserCountDOMapper;
|
||||
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountLikeUnlikeNoteMqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@@ -26,6 +29,10 @@ public class CountNoteLike2DBConsumer implements RocketMQListener<String> {
|
||||
private final RateLimiter rateLimiter = RateLimiter.create(5000);
|
||||
@Resource
|
||||
private NoteCountDOMapper noteCountDOMapper;
|
||||
@Resource
|
||||
private UserCountDOMapper userCountDOMapper;
|
||||
@Resource
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
@Override
|
||||
public void onMessage(String body) {
|
||||
@@ -34,16 +41,33 @@ public class CountNoteLike2DBConsumer implements RocketMQListener<String> {
|
||||
|
||||
log.info("## 消费到了 MQ 【计数: 笔记点赞数入库】, {}...", body);
|
||||
|
||||
Map<Long, Integer> countMap = null;
|
||||
List<AggregationCountLikeUnlikeNoteMqDTO> countList = null;
|
||||
try {
|
||||
countMap = JsonUtils.parseMap(body, Long.class, Integer.class);
|
||||
countList = JsonUtils.parseList(body, AggregationCountLikeUnlikeNoteMqDTO.class);
|
||||
} catch (Exception e) {
|
||||
log.error("## 解析 JSON 字符串异常", e);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(countMap)) {
|
||||
// 判断数据库中 t_note_count 表,若笔记计数记录不存在,则插入;若记录已存在,则直接更新
|
||||
countMap.forEach((k, v) -> noteCountDOMapper.insertOrUpdateLikeTotalByNoteId(v, k));
|
||||
if (CollUtil.isNotEmpty(countList)) {
|
||||
// 判断数据库中 t_user_count 和 t_note_count 表,若笔记计数记录不存在,则插入;若记录已存在,则直接更新
|
||||
countList.forEach(item -> {
|
||||
Long creatorId = item.getCreatorId();
|
||||
Long noteId = item.getNoteId();
|
||||
Integer count = item.getCount();
|
||||
|
||||
// 编程式事务,保证两条语句的原子性
|
||||
transactionTemplate.execute(status -> {
|
||||
try {
|
||||
noteCountDOMapper.insertOrUpdateLikeTotalByNoteId(count, noteId);
|
||||
userCountDOMapper.insertOrUpdateLikeTotalByUserId(count, creatorId);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
status.setRollbackOnly(); // 标记事务为回滚
|
||||
log.error("", ex);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.hanserwei.hannote.count.biz.consumer;
|
||||
|
||||
import com.github.phantomthief.collection.BufferTrigger;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||
import com.hanserwei.hannote.count.biz.constant.MQConstants;
|
||||
import com.hanserwei.hannote.count.biz.constant.RedisKeyConstants;
|
||||
import com.hanserwei.hannote.count.biz.enums.LikeUnlikeNoteTypeEnum;
|
||||
import com.hanserwei.hannote.count.biz.model.dto.AggregationCountLikeUnlikeNoteMqDTO;
|
||||
import com.hanserwei.hannote.count.biz.model.dto.CountLikeUnlikeNoteMqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -60,13 +61,18 @@ public class CountNoteLikeConsumer implements RocketMQListener<String> {
|
||||
.collect(Collectors.groupingBy(CountLikeUnlikeNoteMqDTO::getNoteId));
|
||||
|
||||
// 按组汇总统计处最终计数
|
||||
// key为笔记ID,value为最终操作计数
|
||||
Map<Long, Integer> countMap = Maps.newHashMap();
|
||||
List<AggregationCountLikeUnlikeNoteMqDTO> countList = Lists.newArrayList();
|
||||
for (Map.Entry<Long, List<CountLikeUnlikeNoteMqDTO>> entry : groupMap.entrySet()) {
|
||||
// 笔记 ID
|
||||
Long noteId = entry.getKey();
|
||||
// 笔记发布者 ID
|
||||
Long creatorId = null;
|
||||
List<CountLikeUnlikeNoteMqDTO> list = entry.getValue();
|
||||
// 最终计数默认为0
|
||||
// 最终地计数值,默认为 0
|
||||
int finalCount = 0;
|
||||
for (CountLikeUnlikeNoteMqDTO countLikeUnlikeNoteMqDTO : list) {
|
||||
// 设置笔记发布者用户 ID
|
||||
creatorId = countLikeUnlikeNoteMqDTO.getNoteCreatorId();
|
||||
Integer type = countLikeUnlikeNoteMqDTO.getType();
|
||||
LikeUnlikeNoteTypeEnum likeUnlikeNoteTypeEnum = LikeUnlikeNoteTypeEnum.valueOf(type);
|
||||
if (likeUnlikeNoteTypeEnum == null) {
|
||||
@@ -77,26 +83,45 @@ public class CountNoteLikeConsumer implements RocketMQListener<String> {
|
||||
case UNLIKE -> finalCount--;
|
||||
}
|
||||
}
|
||||
countMap.put(entry.getKey(), finalCount);
|
||||
// 将分组后统计出的最终计数,存入 countList 中
|
||||
countList.add(AggregationCountLikeUnlikeNoteMqDTO.builder()
|
||||
.noteId(noteId)
|
||||
.creatorId(creatorId)
|
||||
.count(finalCount)
|
||||
.build());
|
||||
}
|
||||
log.info("## 【笔记点赞数】聚合后的计数数据: {}", JsonUtils.toJsonString(countMap));
|
||||
log.info("## 【笔记点赞数】聚合后的计数数据: {}", JsonUtils.toJsonString(countList));
|
||||
// 更新 Redis
|
||||
countMap.forEach((k, v) -> {
|
||||
// Redis Key
|
||||
String redisKey = RedisKeyConstants.buildCountNoteKey(k);
|
||||
countList.forEach(item -> {
|
||||
// 笔记发布者 ID
|
||||
Long creatorId = item.getCreatorId();
|
||||
// 笔记 ID
|
||||
Long noteId = item.getNoteId();
|
||||
// 聚合后的计数
|
||||
Integer count = item.getCount();
|
||||
|
||||
// 笔记维度计数 Redis Key
|
||||
String countNoteRedisKey = RedisKeyConstants.buildCountNoteKey(noteId);
|
||||
// 判断 Redis 中 Hash 是否存在
|
||||
boolean isExisted = redisTemplate.hasKey(redisKey);
|
||||
boolean isCountNoteExisted = redisTemplate.hasKey(countNoteRedisKey);
|
||||
|
||||
// 若存在才会更新
|
||||
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||
if (isExisted) {
|
||||
if (isCountNoteExisted) {
|
||||
// 对目标用户 Hash 中的点赞数字段进行计数操作
|
||||
redisTemplate.opsForHash().increment(redisKey, RedisKeyConstants.FIELD_LIKE_TOTAL, v);
|
||||
redisTemplate.opsForHash().increment(countNoteRedisKey, RedisKeyConstants.FIELD_LIKE_TOTAL, count);
|
||||
}
|
||||
|
||||
// 更新 Redis 用户维度点赞数
|
||||
String countUserRedisKey = RedisKeyConstants.buildCountUserKey(creatorId);
|
||||
boolean isCountUserExisted = redisTemplate.hasKey(countUserRedisKey);
|
||||
if (isCountUserExisted) {
|
||||
redisTemplate.opsForHash().increment(countUserRedisKey, RedisKeyConstants.FIELD_LIKE_TOTAL, count);
|
||||
}
|
||||
});
|
||||
|
||||
// 发送 MQ, 笔记点赞数据落库
|
||||
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countMap))
|
||||
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countList))
|
||||
.build();
|
||||
|
||||
// 异步发送 MQ 消息
|
||||
|
||||
@@ -25,4 +25,22 @@ public interface UserCountDOMapper extends BaseMapper<UserCountDO> {
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insertOrUpdateFollowingTotalByUserId(@Param("count") Integer count, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 添加记录或更新笔记点赞数
|
||||
*
|
||||
* @param count 点赞数
|
||||
* @param userId 用户ID
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insertOrUpdateLikeTotalByUserId(@Param("count") Integer count, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 添加记录或更新笔记收藏数
|
||||
*
|
||||
* @param count 收藏数
|
||||
* @param userId 用户ID
|
||||
* @return 影响行数
|
||||
*/
|
||||
int insertOrUpdateCollectTotalByUserId(@Param("count") Integer count, @Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.hanserwei.hannote.count.biz.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 聚合后的收藏、取消收藏笔记消息体
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class AggregationCountCollectedUncollectedNoteMqDTO {
|
||||
|
||||
/**
|
||||
* 笔记发布者 ID
|
||||
*/
|
||||
private Long creatorId;
|
||||
|
||||
/**
|
||||
* 笔记 ID
|
||||
*/
|
||||
private Long noteId;
|
||||
|
||||
/**
|
||||
* 聚合后的计数
|
||||
*/
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.hanserwei.hannote.count.biz.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 聚合后的点赞、取消点赞笔记消息体
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class AggregationCountLikeUnlikeNoteMqDTO {
|
||||
|
||||
/**
|
||||
* 笔记发布者 ID
|
||||
*/
|
||||
private Long creatorId;
|
||||
|
||||
/**
|
||||
* 笔记 ID
|
||||
*/
|
||||
private Long noteId;
|
||||
|
||||
/**
|
||||
* 聚合后的计数
|
||||
*/
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
@@ -23,4 +23,9 @@ public class CountCollectUnCollectNoteMqDTO {
|
||||
private Integer type;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 笔记发布者 ID
|
||||
*/
|
||||
private Long noteCreatorId;
|
||||
}
|
||||
@@ -23,4 +23,9 @@ public class CountLikeUnlikeNoteMqDTO {
|
||||
private Integer type;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 笔记发布者 ID
|
||||
*/
|
||||
private Long noteCreatorId;
|
||||
}
|
||||
@@ -28,4 +28,16 @@
|
||||
VALUES (#{userId}, #{count})
|
||||
ON DUPLICATE KEY UPDATE following_total = following_total + (#{count});
|
||||
</insert>
|
||||
|
||||
<insert id="insertOrUpdateLikeTotalByUserId" parameterType="map">
|
||||
INSERT INTO t_user_count (user_id, like_total)
|
||||
VALUES (#{userId}, #{count})
|
||||
ON DUPLICATE KEY UPDATE like_total = like_total + (#{count});
|
||||
</insert>
|
||||
|
||||
<insert id="insertOrUpdateCollectTotalByUserId" parameterType="map">
|
||||
INSERT INTO t_user_count (user_id, collect_total)
|
||||
VALUES (#{userId}, #{count})
|
||||
ON DUPLICATE KEY UPDATE collect_total = collect_total + (#{count});
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -23,4 +23,9 @@ public class CollectUnCollectNoteMqDTO {
|
||||
private Integer type;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 笔记发布者 ID
|
||||
*/
|
||||
private Long noteCreatorId;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,9 @@ public class LikeUnlikeNoteMqDTO {
|
||||
private Integer type;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 笔记发布者 ID
|
||||
*/
|
||||
private Long noteCreatorId;
|
||||
}
|
||||
@@ -571,7 +571,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
public Response<?> likeNote(LikeNoteReqVO likeNoteReqVO) {
|
||||
Long noteId = likeNoteReqVO.getId();
|
||||
// 1. 校验被点赞的笔记是否存在
|
||||
checkNoteIsExist(noteId);
|
||||
Long creatorId = checkNoteIsExistAndGetCreatorId(noteId);
|
||||
// 2. 判断目标笔记,是否已经点赞过
|
||||
Long userId = LoginUserContextHolder.getUserId();
|
||||
|
||||
@@ -593,7 +593,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
switch (noteLikeLuaResultEnum) {
|
||||
// Redis 中布隆过滤器不存在
|
||||
case NOT_EXIST -> {
|
||||
// TODO: 从数据库中校验笔记是否被点赞,并异步初始化布隆过滤器,设置过期时间
|
||||
//从数据库中校验笔记是否被点赞,并异步初始化布隆过滤器,设置过期时间
|
||||
long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
||||
.eq(NoteLikeDO::getNoteId, noteId)
|
||||
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
|
||||
@@ -689,6 +689,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
.noteId(noteId)
|
||||
.type(LikeStatusEnum.LIKE.getCode()) // 点赞
|
||||
.createTime(now)
|
||||
.noteCreatorId(creatorId)
|
||||
.build();
|
||||
// 构建消息,将DTO转换为JSON字符串设置到消息体中
|
||||
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(likeUnlikeNoteMqDTO)).build();
|
||||
@@ -717,7 +718,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
Long noteId = unlikeNoteReqVO.getId();
|
||||
|
||||
// 1. 校验笔记是否真实存在
|
||||
checkNoteIsExist(noteId);
|
||||
Long creatorId = checkNoteIsExistAndGetCreatorId(noteId);
|
||||
|
||||
// 2. 校验笔记是否被点赞过
|
||||
// 当前登录用户ID
|
||||
@@ -774,6 +775,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
.noteId(noteId)
|
||||
.type(LikeUnlikeNoteTypeEnum.UNLIKE.getCode()) // 取消点赞笔记
|
||||
.createTime(LocalDateTime.now())
|
||||
.noteCreatorId(creatorId)
|
||||
.build();
|
||||
|
||||
// 构建消息,将DTO转换为JSON字符串设置到消息体中
|
||||
@@ -806,7 +808,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
Long noteId = collectNoteReqVO.getId();
|
||||
|
||||
// 1. 校验被收藏的笔记是否存在
|
||||
checkNoteIsExist(noteId);
|
||||
Long creatorId = checkNoteIsExistAndGetCreatorId(noteId);
|
||||
|
||||
// 2. 判断目标笔记,是否已经收藏过
|
||||
// 当前登录用户ID
|
||||
@@ -828,8 +830,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
|
||||
NoteCollectLuaResultEnum noteCollectLuaResultEnum = NoteCollectLuaResultEnum.valueOf(result);
|
||||
log.info("==> 【笔记收藏】Lua 脚本返回结果: {}", noteCollectLuaResultEnum);
|
||||
assert noteCollectLuaResultEnum != null;
|
||||
switch (noteCollectLuaResultEnum) {
|
||||
switch (Objects.requireNonNull(noteCollectLuaResultEnum)) {
|
||||
// 布隆过滤器不存在
|
||||
case NOT_EXIST -> {
|
||||
// 从数据库中校验笔记是否被收藏,并异步初始化布隆过滤器,设置过期时间
|
||||
@@ -932,6 +933,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
.noteId(noteId)
|
||||
.type(CollectUnCollectNoteTypeEnum.COLLECT.getCode()) // 收藏笔记
|
||||
.createTime(now)
|
||||
.noteCreatorId(creatorId)
|
||||
.build();
|
||||
|
||||
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||
@@ -965,7 +967,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
Long noteId = unCollectNoteReqVO.getId();
|
||||
|
||||
// 1. 校验笔记是否真实存在
|
||||
checkNoteIsExist(noteId);
|
||||
Long creatorId = checkNoteIsExistAndGetCreatorId(noteId);
|
||||
|
||||
// 2. 校验笔记是否被收藏过
|
||||
// 当前登录用户ID
|
||||
@@ -1021,6 +1023,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
.noteId(noteId)
|
||||
.type(CollectUnCollectNoteTypeEnum.UN_COLLECT.getCode()) // 取消收藏笔记
|
||||
.createTime(LocalDateTime.now())
|
||||
.noteCreatorId(creatorId)
|
||||
.build();
|
||||
|
||||
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||
@@ -1238,17 +1241,17 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验笔记是否存在
|
||||
* 校验笔记是否存在,若存在,则获取笔记的发布者 ID
|
||||
*
|
||||
* @param noteId 笔记 ID
|
||||
*/
|
||||
private void checkNoteIsExist(Long noteId) {
|
||||
private Long checkNoteIsExistAndGetCreatorId(Long noteId) {
|
||||
// 先从本地缓存中检验
|
||||
String findNoteDetailRspVOStrLocalCache = LOCAL_CACHE.getIfPresent(noteId);
|
||||
// 解析 JSON 为 FindNoteDetailRspVO
|
||||
FindNoteDetailRspVO findNoteDetailRspVO = JsonUtils.parseObject(findNoteDetailRspVOStrLocalCache, FindNoteDetailRspVO.class);
|
||||
|
||||
// 若缓存不存在
|
||||
// 若本地缓存不存在
|
||||
if (Objects.isNull(findNoteDetailRspVO)) {
|
||||
// 从 Redis 中获取
|
||||
String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
|
||||
@@ -1260,10 +1263,15 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
// 若 Redis 中不存在,则从数据库中获取
|
||||
|
||||
if (Objects.isNull(findNoteDetailRspVO)) {
|
||||
boolean isExist = this.exists(new LambdaQueryWrapper<>(NoteDO.class)
|
||||
// 查询笔记的发布者用户 ID
|
||||
NoteDO noteDO = this.getOne(new LambdaQueryWrapper<>(NoteDO.class)
|
||||
.select(NoteDO::getCreatorId)
|
||||
.eq(NoteDO::getId, noteId)
|
||||
.eq(NoteDO::getStatus, NoteStatusEnum.NORMAL.getCode()));
|
||||
if (!isExist) {
|
||||
// 笔记发布者用户 ID
|
||||
Long creatorId = noteDO.getCreatorId();
|
||||
// 若数据库中也不存在,提示用户
|
||||
if (Objects.isNull(creatorId)) {
|
||||
throw new ApiException(ResponseCodeEnum.NOTE_NOT_FOUND);
|
||||
}
|
||||
// 缓存
|
||||
@@ -1271,8 +1279,11 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
||||
FindNoteDetailReqVO findNoteDetailReqVO = FindNoteDetailReqVO.builder().id(noteId).build();
|
||||
findNoteDetail(findNoteDetailReqVO);
|
||||
});
|
||||
return creatorId;
|
||||
}
|
||||
}
|
||||
|
||||
return findNoteDetailRspVO.getCreatorId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,10 +4,12 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JsonUtils {
|
||||
@@ -74,4 +76,23 @@ public class JsonUtils {
|
||||
// 将 JSON 字符串转换为 Map
|
||||
return OBJECT_MAPPER.readValue(jsonStr, OBJECT_MAPPER.getTypeFactory().constructMapType(Map.class, keyClass, valueClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 字符串解析为指定类型的 List 对象
|
||||
*
|
||||
* @param jsonStr JSON 字符串
|
||||
* @param clazz 目标对象类型
|
||||
* @param <T> 目标对象类型
|
||||
* @return List 对象
|
||||
* @throws Exception 抛出异常
|
||||
*/
|
||||
public static <T> List<T> parseList(String jsonStr, Class<T> clazz) throws Exception {
|
||||
// 使用 TypeReference 指定 List<T> 的泛型类型
|
||||
return OBJECT_MAPPER.readValue(jsonStr, new TypeReference<List<T>>() {
|
||||
@Override
|
||||
public CollectionType getType() {
|
||||
return OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user