From 362c32cbd6957a34f4748aa46fa089cf365f4631 Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Sun, 12 Oct 2025 21:17:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(user-relation):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=85=B3=E6=B3=A8=E4=B8=8E=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E5=85=B3=E6=B3=A8=E5=8A=9F=E8=83=BD=20-=20=E5=9C=A8=20t=5Ffoll?= =?UTF-8?q?owing=20=E5=92=8C=20t=5Ffans=20=E8=A1=A8=E4=B8=AD=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=81=94=E5=90=88=E5=94=AF=E4=B8=80=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=85=B3=E6=B3=A8=E5=85=B3=E7=B3=BB?= =?UTF-8?q?=E7=9A=84=E5=B9=82=E7=AD=89=E6=80=A7-=20=E6=96=B0=E5=A2=9E=20Ro?= =?UTF-8?q?cketMQ=20=E6=B6=88=E8=B4=B9=E8=80=85=20FollowUnfollowConsumer?= =?UTF-8?q?=EF=BC=8C=E5=A4=84=E7=90=86=E5=85=B3=E6=B3=A8=E5=92=8C=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E5=85=B3=E6=B3=A8=E6=B6=88=E6=81=AF=20-=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=85=B3=E6=B3=A8=E9=80=BB=E8=BE=91=EF=BC=8C=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E4=BA=8B=E5=8A=A1=E4=BF=9D=E8=AF=81=E5=85=B3=E6=B3=A8?= =?UTF-8?q?=E8=A1=A8=E5=92=8C=E7=B2=89=E4=B8=9D=E8=A1=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=20-=20=E4=BF=AE=E6=94=B9=20DeleteNo?= =?UTF-8?q?teLocalCacheConsumer=20=E7=9A=84=20consumerGroup=20=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=EF=BC=8C=E9=81=BF=E5=85=8D=E6=B6=88=E8=B4=B9=E8=80=85?= =?UTF-8?q?=E7=BB=84=E5=86=B2=E7=AA=81=EF=BC=8C=E5=90=A6=E5=88=99=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E9=81=87=E5=88=B0=E6=B6=88=E8=B4=B9=E8=80=85=E4=B8=8D?= =?UTF-8?q?=E6=B6=88=E8=B4=B9=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeleteNoteLocalCacheConsumer.java | 2 +- .../biz/consumer/FollowUnfollowConsumer.java | 102 ++++++++++++++++++ http-client/gateApi.http | 8 +- sql/createTable.sql | 4 + 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java diff --git a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/DeleteNoteLocalCacheConsumer.java b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/DeleteNoteLocalCacheConsumer.java index 2f0a3fd..e9d75cf 100644 --- a/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/DeleteNoteLocalCacheConsumer.java +++ b/han-note-note/han-note-note-biz/src/main/java/com/hanserwei/hannote/note/biz/comsumer/DeleteNoteLocalCacheConsumer.java @@ -10,7 +10,7 @@ import org.springframework.stereotype.Component; @Component @Slf4j @RocketMQMessageListener( - consumerGroup = "han_note_group", + consumerGroup = "han_note_group_" + MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, topic = MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, messageModel = MessageModel.BROADCASTING ) diff --git a/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java b/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java new file mode 100644 index 0000000..5e6e052 --- /dev/null +++ b/han-note-user-relation/han-note-user-relation-biz/src/main/java/com/hanserwei/hannote/user/relation/biz/consumer/FollowUnfollowConsumer.java @@ -0,0 +1,102 @@ +package com.hanserwei.hannote.user.relation.biz.consumer; + +import com.hanserwei.framework.common.utils.JsonUtils; +import com.hanserwei.hannote.user.relation.biz.constant.MQConstants; +import com.hanserwei.hannote.user.relation.biz.domain.dataobject.FansDO; +import com.hanserwei.hannote.user.relation.biz.domain.dataobject.FollowingDO; +import com.hanserwei.hannote.user.relation.biz.model.dto.FollowUserMqDTO; +import com.hanserwei.hannote.user.relation.biz.service.FansDOService; +import com.hanserwei.hannote.user.relation.biz.service.FollowingDOService; +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.stereotype.Component; +import org.springframework.transaction.support.TransactionTemplate; + +import java.time.LocalDateTime; +import java.util.Objects; + +@Component +@RocketMQMessageListener( + consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW, + topic = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW +) +@Slf4j +public class FollowUnfollowConsumer implements RocketMQListener { + private final TransactionTemplate transactionTemplate; + private final FollowingDOService followingDOService; + private final FansDOService fansDOService; + + public FollowUnfollowConsumer(TransactionTemplate transactionTemplate, FollowingDOService followingDOService, FansDOService fansDOService) { + this.transactionTemplate = transactionTemplate; + this.followingDOService = followingDOService; + this.fansDOService = fansDOService; + } + + @Override + public void onMessage(Message message) { + // 消息体 + String bodyJsonStr = new String(message.getBody()); + // 标签 + String tags = message.getTags(); + + log.info("==> FollowUnfollowConsumer 消费了消息 {}, tags: {}", bodyJsonStr, tags); + // 根据MQ标签判断操作类型 + if (Objects.equals(tags, MQConstants.TAG_FOLLOW)){ + // 关注 + handleFollowTagMessage(bodyJsonStr); + } else if (Objects.equals(tags, MQConstants.TAG_UNFOLLOW)) { + // 取关 + // TODO: 待实现 + } + } + + /** + * 关注 + * @param bodyJsonStr 消息体 + */ + private void handleFollowTagMessage(String bodyJsonStr) { + // 解析消息体转换为DTO对象 + FollowUserMqDTO followUserMqDTO = JsonUtils.parseObject(bodyJsonStr, FollowUserMqDTO.class); + + // 判空 + if (Objects.isNull(followUserMqDTO)) { + return; + } + + // 幂等性:通过联合唯一索引保证 + + Long userId = followUserMqDTO.getUserId(); + Long followUserId = followUserMqDTO.getFollowUserId(); + LocalDateTime createTime = followUserMqDTO.getCreateTime(); + + // 编程式事物 + boolean isSuccess = Boolean.TRUE.equals(transactionTemplate.execute(status -> { + try { + // 关注成功需往数据库添加两条记录 + // 关注表:一条记录 + boolean followRecordSaved = followingDOService.save(FollowingDO.builder() + .userId(userId) + .followingUserId(followUserId) + .createTime(createTime) + .build()); + // 粉丝表:一条记录 + if (followRecordSaved){ + return fansDOService.save(FansDO.builder() + .userId(followUserId) + .fansUserId(userId) + .createTime(createTime) + .build()); + } + }catch (Exception e){ + status.setRollbackOnly(); + log.error("## 添加关注关系失败, userId: {}, followUserId: {}, createTime: {}", userId, followUserId, createTime); + } + return false; + })); + + log.info("## 数据库添加记录结果: {}", isSuccess); + // TODO: 更新 Redis 中被关注用户的 ZSet 粉丝列表 + } +} diff --git a/http-client/gateApi.http b/http-client/gateApi.http index 10b35a1..d6c98c7 100644 --- a/http-client/gateApi.http +++ b/http-client/gateApi.http @@ -3,7 +3,7 @@ POST http://localhost:8000/auth/verification/code/send Content-Type: application/json { - "email": "ssw010723@gmail.com" + "email": "2628273921@qq.com" } ### 登录/注册 @@ -11,8 +11,8 @@ POST http://localhost:8000/auth/login Content-Type: application/json { - "email": "ssw010723@gmail.com", - "code": "135466", + "email": "2628273921@qq.com", + "code": "825004", "type": 1 } @@ -144,5 +144,5 @@ Content-Type: application/json Authorization: Bearer {{token}} { - "followUserId":{{otherUserId}} + "followUserId": {{otherUserId}} } \ No newline at end of file diff --git a/sql/createTable.sql b/sql/createTable.sql index 92fec34..e035da5 100644 --- a/sql/createTable.sql +++ b/sql/createTable.sql @@ -175,4 +175,8 @@ CREATE TABLE `t_fans` DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='用户粉丝表'; +ALTER TABLE t_following ADD UNIQUE uk_user_id_following_user_id(user_id, following_user_id); + +ALTER TABLE t_fans ADD UNIQUE uk_user_id_fans_user_id(user_id, fans_user_id); +