feat(count): 实现笔记发布与删除的计数更新功能
- 新增笔记操作 MQ 消费者 CountNotePublishConsumer - 支持处理笔记发布和删除消息,更新 Redis 和数据库计数 - 新增笔记操作相关常量:TOPIC_NOTE_OPERATE、TAG_NOTE_PUBLISH、TAG_NOTE_DELETE - 定义笔记操作 DTO:NoteOperateMqDTO,用于 MQ 消息传递 - 在笔记服务中发送笔记发布和删除的 MQ 消息 - 新增 Redis Hash 字段 noteTotal 用于存储笔记总数 - 新增数据库操作 insertOrUpdateNoteTotalByUserId 用于更新笔记总数
This commit is contained in:
@@ -42,4 +42,19 @@ public interface MQConstants {
|
|||||||
*/
|
*/
|
||||||
String TOPIC_COUNT_NOTE_COLLECT_2_DB = "CountNoteCollect2DBTTopic";
|
String TOPIC_COUNT_NOTE_COLLECT_2_DB = "CountNoteCollect2DBTTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 笔记操作(发布、删除)
|
||||||
|
*/
|
||||||
|
String TOPIC_NOTE_OPERATE = "NoteOperateTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:笔记发布
|
||||||
|
*/
|
||||||
|
String TAG_NOTE_PUBLISH = "publishNote";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:笔记删除
|
||||||
|
*/
|
||||||
|
String TAG_NOTE_DELETE = "deleteNote";
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,11 @@ public class RedisKeyConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String FIELD_FOLLOWING_TOTAL = "followingTotal";
|
public static final String FIELD_FOLLOWING_TOTAL = "followingTotal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash Field: 笔记发布总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_NOTE_TOTAL = "noteTotal";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户维度计数 Key 前缀
|
* 用户维度计数 Key 前缀
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.hanserwei.hannote.count.biz.consumer;
|
||||||
|
|
||||||
|
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.domain.mapper.UserCountDOMapper;
|
||||||
|
import com.hanserwei.hannote.count.biz.model.dto.NoteOperateMqDTO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.common.message.Message;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
consumerGroup = "han_note_group_" + MQConstants.TOPIC_NOTE_OPERATE,
|
||||||
|
topic = MQConstants.TOPIC_NOTE_OPERATE
|
||||||
|
)
|
||||||
|
public class CountNotePublishConsumer implements RocketMQListener<Message> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private UserCountDOMapper userCountDOMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(Message message) {
|
||||||
|
// 消息体
|
||||||
|
String bodyJsonStr = new String(message.getBody());
|
||||||
|
// 标签
|
||||||
|
String tags = message.getTags();
|
||||||
|
|
||||||
|
log.info("==> CountNotePublishConsumer 消费了消息 {}, tags: {}", bodyJsonStr, tags);
|
||||||
|
|
||||||
|
// 根据 MQ 标签,判断笔记操作类型
|
||||||
|
if (Objects.equals(tags, MQConstants.TAG_NOTE_PUBLISH)) { // 笔记发布
|
||||||
|
handleTagMessage(bodyJsonStr, 1);
|
||||||
|
} else if (Objects.equals(tags, MQConstants.TAG_NOTE_DELETE)) { // 笔记删除
|
||||||
|
handleTagMessage(bodyJsonStr, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理笔记发布和笔记删除的 MQ 消息
|
||||||
|
*
|
||||||
|
* @param bodyJsonStr 笔记发布或删除的 MQ 消息体
|
||||||
|
* @param count 笔记发布或删除的计数
|
||||||
|
*/
|
||||||
|
private void handleTagMessage(String bodyJsonStr, long count) {
|
||||||
|
// 消息体 JSON 字符串转 DTO
|
||||||
|
NoteOperateMqDTO noteOperateMqDTO = JsonUtils.parseObject(bodyJsonStr, NoteOperateMqDTO.class);
|
||||||
|
|
||||||
|
if (Objects.isNull(noteOperateMqDTO)) return;
|
||||||
|
|
||||||
|
// 笔记发布者 ID
|
||||||
|
Long creatorId = noteOperateMqDTO.getCreatorId();
|
||||||
|
|
||||||
|
// 更新 Redis 中用户维度的计数 Hash
|
||||||
|
String countUserRedisKey = RedisKeyConstants.buildCountUserKey(creatorId);
|
||||||
|
// 判断 Redis 中 Hash 是否存在
|
||||||
|
boolean isCountUserExisted = redisTemplate.hasKey(countUserRedisKey);
|
||||||
|
|
||||||
|
// 若存在才会更新
|
||||||
|
// (因为缓存设有过期时间,考虑到过期后,缓存会被删除,这里需要判断一下,存在才会去更新,而初始化工作放在查询计数来做)
|
||||||
|
if (isCountUserExisted) {
|
||||||
|
// 对目标用户 Hash 中的笔记发布总数,进行加减操作
|
||||||
|
redisTemplate.opsForHash().increment(countUserRedisKey, RedisKeyConstants.FIELD_NOTE_TOTAL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 t_user_count 表
|
||||||
|
userCountDOMapper.insertOrUpdateNoteTotalByUserId(count, creatorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -43,4 +43,13 @@ public interface UserCountDOMapper extends BaseMapper<UserCountDO> {
|
|||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int insertOrUpdateCollectTotalByUserId(@Param("count") Integer count, @Param("userId") Long userId);
|
int insertOrUpdateCollectTotalByUserId(@Param("count") Integer count, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加记录或更新笔记发布数
|
||||||
|
*
|
||||||
|
* @param count 笔记发布数
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int insertOrUpdateNoteTotalByUserId(@Param("count") Long count, @Param("userId") Long userId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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 NoteOperateMqDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记发布者 ID
|
||||||
|
*/
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记 ID
|
||||||
|
*/
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型: 0 - 笔记删除; 1:笔记发布;
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,4 +40,10 @@
|
|||||||
VALUES (#{userId}, #{count})
|
VALUES (#{userId}, #{count})
|
||||||
ON DUPLICATE KEY UPDATE collect_total = collect_total + (#{count});
|
ON DUPLICATE KEY UPDATE collect_total = collect_total + (#{count});
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<insert id="insertOrUpdateNoteTotalByUserId" parameterType="map">
|
||||||
|
INSERT INTO t_user_count (user_id, note_total)
|
||||||
|
VALUES (#{userId}, #{count})
|
||||||
|
ON DUPLICATE KEY UPDATE note_total = note_total + (#{count});
|
||||||
|
</insert>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -32,6 +32,21 @@ public interface MQConstants {
|
|||||||
*/
|
*/
|
||||||
String TOPIC_COUNT_NOTE_COLLECT = "CountNoteCollectTopic";
|
String TOPIC_COUNT_NOTE_COLLECT = "CountNoteCollectTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topic: 笔记操作(发布、删除)
|
||||||
|
*/
|
||||||
|
String TOPIC_NOTE_OPERATE = "NoteOperateTopic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:笔记发布
|
||||||
|
*/
|
||||||
|
String TAG_NOTE_PUBLISH = "publishNote";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag 标签:笔记删除
|
||||||
|
*/
|
||||||
|
String TAG_NOTE_DELETE = "deleteNote";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点赞标签
|
* 点赞标签
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.hanserwei.hannote.note.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum NoteOperateEnum {
|
||||||
|
// 笔记发布
|
||||||
|
PUBLISH(1),
|
||||||
|
// 笔记删除
|
||||||
|
DELETE(0),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.hanserwei.hannote.note.biz.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class NoteOperateMqDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记发布者 ID
|
||||||
|
*/
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记 ID
|
||||||
|
*/
|
||||||
|
private Long noteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型: 0 - 笔记删除; 1:笔记发布;
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import com.hanserwei.hannote.note.biz.domain.mapper.NoteDOMapper;
|
|||||||
import com.hanserwei.hannote.note.biz.enums.*;
|
import com.hanserwei.hannote.note.biz.enums.*;
|
||||||
import com.hanserwei.hannote.note.biz.model.dto.CollectUnCollectNoteMqDTO;
|
import com.hanserwei.hannote.note.biz.model.dto.CollectUnCollectNoteMqDTO;
|
||||||
import com.hanserwei.hannote.note.biz.model.dto.LikeUnlikeNoteMqDTO;
|
import com.hanserwei.hannote.note.biz.model.dto.LikeUnlikeNoteMqDTO;
|
||||||
|
import com.hanserwei.hannote.note.biz.model.dto.NoteOperateMqDTO;
|
||||||
import com.hanserwei.hannote.note.biz.model.vo.*;
|
import com.hanserwei.hannote.note.biz.model.vo.*;
|
||||||
import com.hanserwei.hannote.note.biz.rpc.DistributedIdGeneratorRpcService;
|
import com.hanserwei.hannote.note.biz.rpc.DistributedIdGeneratorRpcService;
|
||||||
import com.hanserwei.hannote.note.biz.rpc.KeyValueRpcService;
|
import com.hanserwei.hannote.note.biz.rpc.KeyValueRpcService;
|
||||||
@@ -40,7 +41,6 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.apache.rocketmq.client.producer.SendCallback;
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
import org.apache.rocketmq.client.producer.SendResult;
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
@@ -88,7 +88,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
.build();
|
.build();
|
||||||
@Resource
|
@Resource
|
||||||
private NoteLikeDOService noteLikeDOService;
|
private NoteLikeDOService noteLikeDOService;
|
||||||
@Autowired
|
@Resource
|
||||||
private NoteCollectionDOService noteCollectionDOService;
|
private NoteCollectionDOService noteCollectionDOService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -190,6 +190,33 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 发送 MQ
|
||||||
|
// 构建消息体 DTO
|
||||||
|
NoteOperateMqDTO noteOperateMqDTO = NoteOperateMqDTO.builder()
|
||||||
|
.creatorId(creatorId)
|
||||||
|
.noteId(Long.valueOf(snowflakeId))
|
||||||
|
.type(NoteOperateEnum.PUBLISH.getCode()) // 发布笔记
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(noteOperateMqDTO))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 通过冒号连接, 可让 MQ 发送给主题 Topic 时,携带上标签 Tag
|
||||||
|
String destination = MQConstants.TOPIC_NOTE_OPERATE + ":" + MQConstants.TAG_NOTE_PUBLISH;
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息,提升接口响应速度
|
||||||
|
rocketMQTemplate.asyncSend(destination, message, 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();
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,6 +525,34 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
rocketMQTemplate.syncSend(MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, noteId);
|
rocketMQTemplate.syncSend(MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, noteId);
|
||||||
log.info("====> MQ:删除笔记,删除本地缓存发送成功...");
|
log.info("====> MQ:删除笔记,删除本地缓存发送成功...");
|
||||||
|
|
||||||
|
// 发送 MQ
|
||||||
|
// 构建消息体 DTO
|
||||||
|
NoteOperateMqDTO noteOperateMqDTO = NoteOperateMqDTO.builder()
|
||||||
|
.creatorId(selectNoteDO.getCreatorId())
|
||||||
|
.noteId(noteId)
|
||||||
|
.type(NoteOperateEnum.DELETE.getCode()) // 删除笔记
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中
|
||||||
|
Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(noteOperateMqDTO))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 通过冒号连接, 可让 MQ 发送给主题 Topic 时,携带上标签 Tag
|
||||||
|
String destination = MQConstants.TOPIC_NOTE_OPERATE + ":" + MQConstants.TAG_NOTE_DELETE;
|
||||||
|
|
||||||
|
// 异步发送 MQ 消息,提升接口响应速度
|
||||||
|
rocketMQTemplate.asyncSend(destination, message, 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();
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ Authorization: Bearer {{token}}
|
|||||||
"imgUris": [
|
"imgUris": [
|
||||||
"https://cdn.pixabay.com/photo/2025/10/05/15/06/autumn-9875155_1280.jpg"
|
"https://cdn.pixabay.com/photo/2025/10/05/15/06/autumn-9875155_1280.jpg"
|
||||||
],
|
],
|
||||||
"title": "图文笔记测试",
|
"title": "第三篇图文笔记",
|
||||||
"content": "这个是图文笔记的测试",
|
"content": "这个是第三篇图文笔记的测试",
|
||||||
"topicId": 1
|
"topicId": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +120,15 @@ Authorization: Bearer {{token}}
|
|||||||
"topicId": 1
|
"topicId": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### 删除笔记
|
||||||
|
POST http://localhost:8000/note/note/delete
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1979849112022941780
|
||||||
|
}
|
||||||
|
|
||||||
### 关注自己
|
### 关注自己
|
||||||
POST http://localhost:8000/relation/relation/follow
|
POST http://localhost:8000/relation/relation/follow
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|||||||
Reference in New Issue
Block a user