diff --git a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/MQConstants.java b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/MQConstants.java index 40b09c3..65c2ba3 100644 --- a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/MQConstants.java +++ b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/MQConstants.java @@ -42,4 +42,19 @@ public interface MQConstants { */ 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"; + } \ No newline at end of file diff --git a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/RedisKeyConstants.java b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/RedisKeyConstants.java index e6dffcb..29b6284 100644 --- a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/RedisKeyConstants.java +++ b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/constant/RedisKeyConstants.java @@ -12,6 +12,11 @@ public class RedisKeyConstants { */ public static final String FIELD_FOLLOWING_TOTAL = "followingTotal"; + /** + * Hash Field: 笔记发布总数 + */ + public static final String FIELD_NOTE_TOTAL = "noteTotal"; + /** * 用户维度计数 Key 前缀 */ diff --git a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNotePublishConsumer.java b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNotePublishConsumer.java new file mode 100644 index 0000000..5a14cf2 --- /dev/null +++ b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/consumer/CountNotePublishConsumer.java @@ -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 { + + @Resource + private RedisTemplate 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); + } + +} diff --git a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/domain/mapper/UserCountDOMapper.java b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/domain/mapper/UserCountDOMapper.java index 80a19a3..ad2f858 100644 --- a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/domain/mapper/UserCountDOMapper.java +++ b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/domain/mapper/UserCountDOMapper.java @@ -43,4 +43,13 @@ public interface UserCountDOMapper extends BaseMapper { * @return 影响行数 */ 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); } \ No newline at end of file diff --git a/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/model/dto/NoteOperateMqDTO.java b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/model/dto/NoteOperateMqDTO.java new file mode 100644 index 0000000..d79b526 --- /dev/null +++ b/han-note-count/han-note-count-biz/src/main/java/com/hanserwei/hannote/count/biz/model/dto/NoteOperateMqDTO.java @@ -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; + +} \ No newline at end of file diff --git a/han-note-count/han-note-count-biz/src/main/resources/mapperxml/UserCountDOMapper.xml b/han-note-count/han-note-count-biz/src/main/resources/mapperxml/UserCountDOMapper.xml index ed1c31f..120fbfd 100644 --- a/han-note-count/han-note-count-biz/src/main/resources/mapperxml/UserCountDOMapper.xml +++ b/han-note-count/han-note-count-biz/src/main/resources/mapperxml/UserCountDOMapper.xml @@ -40,4 +40,10 @@ VALUES (#{userId}, #{count}) ON DUPLICATE KEY UPDATE collect_total = collect_total + (#{count}); + + + INSERT INTO t_user_count (user_id, note_total) + VALUES (#{userId}, #{count}) + ON DUPLICATE KEY UPDATE note_total = note_total + (#{count}); + \ 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/MQConstants.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/constant/MQConstants.java index 4ae7ef4..31e5793 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 @@ -32,6 +32,21 @@ public interface MQConstants { */ String TOPIC_COUNT_NOTE_COLLECT = "CountNoteCollectTopic"; + /** + * Topic: 笔记操作(发布、删除) + */ + String TOPIC_NOTE_OPERATE = "NoteOperateTopic"; + + /** + * Tag 标签:笔记发布 + */ + String TAG_NOTE_PUBLISH = "publishNote"; + + /** + * Tag 标签:笔记删除 + */ + String TAG_NOTE_DELETE = "deleteNote"; + /** * 点赞标签 */ diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteOperateEnum.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteOperateEnum.java new file mode 100644 index 0000000..8ac0bcd --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/enums/NoteOperateEnum.java @@ -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; + +} \ No newline at end of file diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/model/dto/NoteOperateMqDTO.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/model/dto/NoteOperateMqDTO.java new file mode 100644 index 0000000..fe52539 --- /dev/null +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/model/dto/NoteOperateMqDTO.java @@ -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; + +} \ 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 8f4a4d7..fe5bf64 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 @@ -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.model.dto.CollectUnCollectNoteMqDTO; 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.rpc.DistributedIdGeneratorRpcService; 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.SendResult; import org.apache.rocketmq.spring.core.RocketMQTemplate; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; @@ -88,7 +88,7 @@ public class NoteServiceImpl extends ServiceImpl implement .build(); @Resource private NoteLikeDOService noteLikeDOService; - @Autowired + @Resource private NoteCollectionDOService noteCollectionDOService; @Override @@ -190,6 +190,33 @@ public class NoteServiceImpl extends ServiceImpl implement } } } + // 发送 MQ + // 构建消息体 DTO + NoteOperateMqDTO noteOperateMqDTO = NoteOperateMqDTO.builder() + .creatorId(creatorId) + .noteId(Long.valueOf(snowflakeId)) + .type(NoteOperateEnum.PUBLISH.getCode()) // 发布笔记 + .build(); + + // 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中 + Message 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(); } @@ -498,6 +525,34 @@ public class NoteServiceImpl extends ServiceImpl implement rocketMQTemplate.syncSend(MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, noteId); log.info("====> MQ:删除笔记,删除本地缓存发送成功..."); + // 发送 MQ + // 构建消息体 DTO + NoteOperateMqDTO noteOperateMqDTO = NoteOperateMqDTO.builder() + .creatorId(selectNoteDO.getCreatorId()) + .noteId(noteId) + .type(NoteOperateEnum.DELETE.getCode()) // 删除笔记 + .build(); + + // 构建消息对象,并将 DTO 转成 Json 字符串设置到消息体中 + Message 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(); } diff --git a/http-client/gateApi.http b/http-client/gateApi.http index 690198a..2b32c21 100644 --- a/http-client/gateApi.http +++ b/http-client/gateApi.http @@ -77,8 +77,8 @@ Authorization: Bearer {{token}} "imgUris": [ "https://cdn.pixabay.com/photo/2025/10/05/15/06/autumn-9875155_1280.jpg" ], - "title": "图文笔记测试", - "content": "这个是图文笔记的测试", + "title": "第三篇图文笔记", + "content": "这个是第三篇图文笔记的测试", "topicId": 1 } @@ -120,6 +120,15 @@ Authorization: Bearer {{token}} "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 Content-Type: application/json