feat(count): 实现关注与粉丝数统计功能

- 新增关注数与粉丝数 MQ 消费者
- 在用户关系服务中新增关注/取关时发送 MQ 消息逻辑
- 新增关注/取关类型枚举类
- 新增用于统计的 MQ 常量定义
- 调整应用主类包路径以符合项目结构(致命,查半天)
- 移除配置文件中不再使用的 MQ 消费者限流配置项
This commit is contained in:
2025-10-15 22:25:59 +08:00
parent e17ab857b9
commit 31ab7c3d86
9 changed files with 181 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
package com.hanserwei.hannote.count.biz.domain;
package com.hanserwei.hannote.count.biz;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;

View File

@@ -0,0 +1,15 @@
package com.hanserwei.hannote.count.biz.constant;
public interface MQConstants {
/**
* Topic: 关注数计数
*/
String TOPIC_COUNT_FOLLOWING = "CountFollowingTopic";
/**
* Topic: 粉丝数计数
*/
String TOPIC_COUNT_FANS = "CountFansTopic";
}

View File

@@ -0,0 +1,20 @@
package com.hanserwei.hannote.count.biz.consumer;
import com.hanserwei.hannote.count.biz.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
@RocketMQMessageListener(
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FANS,
topic = MQConstants.TOPIC_COUNT_FANS
)
@Slf4j
public class CountFansConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String body) {
log.info("## 消费了 MQ [计数:粉丝数]: {}", body);
}
}

View File

@@ -0,0 +1,20 @@
package com.hanserwei.hannote.count.biz.consumer;
import com.hanserwei.hannote.count.biz.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
@RocketMQMessageListener(
consumerGroup = "han_note_group_" + MQConstants.TOPIC_COUNT_FOLLOWING,
topic = MQConstants.TOPIC_COUNT_FOLLOWING
)
@Slf4j
public class CountFollowingConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String body) {
log.info("## 消费了 MQ [计数:关注数]: {}", body);
}
}

View File

@@ -28,7 +28,4 @@ mybatis-plus:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
banner: false
mapper-locations: classpath*:/mapperxml/*.xml
mq-consumer: # MQ 消费者
follow-unfollow: # 关注、取关
rate-limit: 5000 # 每秒限流阈值
mapper-locations: classpath*:/mapperxml/*.xml

View File

@@ -16,4 +16,14 @@ public interface MQConstants {
* 取关标签
*/
String TAG_UNFOLLOW = "Unfollow";
/**
* Topic: 关注数计数
*/
String TOPIC_COUNT_FOLLOWING = "CountFollowingTopic";
/**
* Topic: 粉丝数计数
*/
String TOPIC_COUNT_FANS = "CountFansTopic";
}

View File

@@ -7,6 +7,8 @@ import com.hanserwei.hannote.user.relation.biz.constant.MQConstants;
import com.hanserwei.hannote.user.relation.biz.constant.RedisKeyConstants;
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.enums.FollowUnfollowTypeEnum;
import com.hanserwei.hannote.user.relation.biz.model.dto.CountFollowUnfollowMqDTO;
import com.hanserwei.hannote.user.relation.biz.model.dto.FollowUserMqDTO;
import com.hanserwei.hannote.user.relation.biz.model.dto.UnfollowUserMqDTO;
import com.hanserwei.hannote.user.relation.biz.service.FansDOService;
@@ -15,13 +17,17 @@ import com.hanserwei.hannote.user.relation.biz.util.DateUtils;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
@@ -32,8 +38,8 @@ import java.util.Objects;
@Component
@RocketMQMessageListener(
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW,
topic = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW,
consumerGroup = "han_note_group_" + MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW, //han_note_group_FollowUnfollowTopic
topic = MQConstants.TOPIC_FOLLOW_OR_UNFOLLOW, //FollowUnfollowTopic
consumeMode = ConsumeMode.ORDERLY
)
@Slf4j
@@ -47,6 +53,8 @@ public class FollowUnfollowConsumer implements RocketMQListener<Message> {
private RateLimiter rateLimiter;
@Resource
private RedisTemplate<Object, Object> redisTemplate;
@Resource
private RocketMQTemplate rocketMQTemplate;
@Override
public void onMessage(Message message) {
@@ -114,6 +122,17 @@ public class FollowUnfollowConsumer implements RocketMQListener<Message> {
String fansRedisKey = RedisKeyConstants.buildUserFansKey(unfollowUserId);
// 删除指定粉丝
redisTemplate.opsForZSet().remove(fansRedisKey, userId);
// 发送MQ消息通知计数服务统计关注数
// 构建DTO对象
CountFollowUnfollowMqDTO countFollowUnfollowMqDTO = CountFollowUnfollowMqDTO.builder()
.userId(userId)
.targetUserId(unfollowUserId)
.type(FollowUnfollowTypeEnum.UNFOLLOW.getCode())
.build();
// 发送MQ
sendMQ(countFollowUnfollowMqDTO);
}
}
@@ -177,6 +196,53 @@ public class FollowUnfollowConsumer implements RocketMQListener<Message> {
// 执行Lua脚本
redisTemplate.execute(script, Collections.singletonList(fansZSetKey), userId, timestamp);
// 发送MQ消息通知计数服务统计关注数
// 构建消息体
CountFollowUnfollowMqDTO countFollowUnfollowMqDTO = CountFollowUnfollowMqDTO.builder()
.userId(userId)
.targetUserId(followUserId)
.type(FollowUnfollowTypeEnum.FOLLOW.getCode())
.build();
sendMQ(countFollowUnfollowMqDTO);
}
}
/**
* 发送MQ消息
*
* @param countFollowUnfollowMqDTO 消息体
*/
private void sendMQ(CountFollowUnfollowMqDTO countFollowUnfollowMqDTO) {
// 构建MQ消息体
org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(countFollowUnfollowMqDTO))
.build();
// 异步发送 MQ 消息
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_FOLLOWING, message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("==> 【计数服务关注数】MQ 发送成功SendResult: {}", sendResult);
}
@Override
public void onException(Throwable throwable) {
log.error("==> 【计数服务关注数】MQ 发送异常: ", throwable);
}
});
// 发送 MQ 通知计数服务:统计粉丝数
rocketMQTemplate.asyncSend(MQConstants.TOPIC_COUNT_FANS, message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("==> 【计数服务粉丝数】MQ 发送成功SendResult: {}", sendResult);
}
@Override
public void onException(Throwable throwable) {
log.error("==> 【计数服务粉丝数】MQ 发送异常: ", throwable);
}
});
}
}

View File

@@ -0,0 +1,17 @@
package com.hanserwei.hannote.user.relation.biz.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum FollowUnfollowTypeEnum {
// 关注
FOLLOW(1),
// 取关
UNFOLLOW(0),
;
private final Integer code;
}

View File

@@ -0,0 +1,29 @@
package com.hanserwei.hannote.user.relation.biz.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CountFollowUnfollowMqDTO {
/**
* 原用户
*/
private Long userId;
/**
* 目标用户
*/
private Long targetUserId;
/**
* 1:关注 0:取关
*/
private Integer type;
}