点赞服务完整消息流转过程详解(原方案,未使用Redis)
第一步:用户发起点赞请求
用户在前端点击点赞按钮,会向后端发送POST请求:
POST /likes
Content-Type: application/json{"bizId": 1001,"bizType": "QA","liked": true
}
对应Controller代码:
@PostMapping
public void addLikeRecord(@Valid @RequestBody LikeRecordFormDTO recordFormDTO){likedRecordService.addLikeRecord(recordFormDTO);
}
@Data
@ApiModel(description = "点赞记录表单实体")
public class LikeRecordFormDTO {@ApiModelProperty("点赞业务id")@NotNull(message = "业务id不能为空")private Long bizId;@ApiModelProperty("点赞业务类型")@NotNull(message = "业务类型不能为空")private String bizType;@ApiModelProperty("是否点赞,true:点赞;false:取消点赞")@NotNull(message = "是否点赞不能为空")private Boolean liked;
}
第二步:处理点赞业务逻辑
Controller调用服务层处理点赞请求:
@Override
public void addLikeRecord(LikeRecordFormDTO recordFormDTO) {Long userId = UserContext.getUser();// 判断是点赞还是取消点赞boolean result = recordFormDTO.getLiked();boolean success;if(result){success = like(recordFormDTO); // 点赞操作}else{success = unlike(recordFormDTO); // 取消点赞操作}// 判断是否执行成功,失败直接结束if(!success){return;}// 统计最新的点赞次数Integer count = this.lambdaQuery().eq(LikedRecord::getBizId, recordFormDTO.getBizId()).count();// 发送MQ通知mqHelper.send(LIKE_RECORD_EXCHANGE, // 交换机: "like.record.topic"StringUtils.format(LIKED_TIMES_KEY_TEMPLATE,recordFormDTO.getBizType()), // 路由键: "QA.times.changed"LikedTimesDTO.of(recordFormDTO.getBizId(), count) // 消息内容);
}
第三步:执行点赞操作
private boolean like(LikeRecordFormDTO recordFormDTO) {Long userId = UserContext.getUser();// 先检查是否点赞过Integer count = this.lambdaQuery().eq(LikedRecord::getUserId, userId).eq(LikedRecord::getBizId, recordFormDTO.getBizId()).count();// 点赞过直接结束if(count > 0){return false;}// 没有点赞过则新增点赞记录LikedRecord record = new LikedRecord();record.setUserId(userId);record.setBizId(recordFormDTO.getBizId());record.setBizType(recordFormDTO.getBizType());record.setCreateTime(LocalDateTime.now());record.setUpdateTime(LocalDateTime.now());boolean save = this.save(record);return save;
}
第四步:统计点赞次数并发送消息
在点赞操作成功后,统计该业务ID的总点赞数,并通过[RabbitMqHelper]发送消息:
public <T> void send(String exchange, String routingKey, T t) {log.debug("准备发送消息,exchange:{}, RoutingKey:{}, message:{}", exchange, routingKey, t);// 1.设置消息标示,用于消息确认String id = UUID.randomUUID().toString(true);CorrelationData correlationData = new CorrelationData(id);// 2.设置发送超时时间为500毫秒rabbitTemplate.setReplyTimeout(500);// 3.发送消息,同时设置消息idrabbitTemplate.convertAndSend(exchange, routingKey, t, processor, correlationData);
}
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class LikedTimesDTO {private Long bizId; // 业务ID(评论ID)private Integer likedTimes; // 点赞次数
}
第五步:RabbitMQ路由消息
RabbitMQ接收到消息后,根据交换机类型(topic)和路由键(QA.times.changed)将消息路由到绑定的队列。
在学习服务中,我们有如下监听器配置:
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "qa.liked.times.queue", durable = "true"), // 队列exchange = @Exchange(name = LIKE_RECORD_EXCHANGE, type = ExchangeTypes.TOPIC), // 交换机key = QA_LIKED_TIMES_KEY // 路由键: "QA.times.changed"
))
public void listenReplyLikedTimesChange(LikedTimesDTO dto){log.debug("监听到回答或评论{}的点赞数变更:{}", dto.getBizId(), dto.getLikedTimes());// 更新数据库中对应记录的点赞次数InteractionReply r = new InteractionReply();r.setId(dto.getBizId());r.setLikedTimes(dto.getLikedTimes());replyService.updateById(r);
}
第六步:学习服务接收并处理消息
当学习服务接收到消息后,执行以下操作:
1. 日志记录:`log.debug("监听到回答或评论{}的点赞数变更:{}", dto.getBizId(), dto.getLikedTimes());`
2. 更新数据库:
InteractionReply r = new InteractionReply();
r.setId(dto.getBizId()); // 设置评论ID
r.setLikedTimes(dto.getLikedTimes()); // 设置新的点赞次数
replyService.updateById(r); // 更新数据库记录
@Data
@TableName("interaction_reply")
@ApiModel(value="InteractionReply对象", description="互动问题的回答或评论")
public class InteractionReply implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "互动问题的回答id")@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;// ... 其他字段@ApiModelProperty(value = "点赞数量")private Integer likedTimes; // 点赞次数字段// ... 其他字段
}
第七步:前端展示更新后的点赞数
当用户再次查询评论列表时,可以直接从数据库中获取最新的点赞数,而不需要实时统计
// InteractionReplyServiceImpl.java 中查询回复列表的部分代码
Page<InteractionReply> page = this.lambdaQuery()// ... 查询条件.page(query.toMpPage(/*排序条件*/));// 返回的InteractionReply对象中likedTimes字段已经是最新值
完整流程图
[用户点击点赞]
↓
[前端发送HTTP请求 → POST /likes]
↓
[Controller接收请求 → LikedRecordController.addLikeRecord()]
↓
[Service处理业务 → LikedRecordServiceImpl.addLikeRecord()]
↓
[执行点赞操作 → like() 或 unlike()]
↓
[统计点赞总数 → lambdaQuery().count()]
↓
[发送MQ消息 → RabbitMqHelper.send()]
↓
[RabbitMQ路由消息]
↓
[学习服务接收消息 → LikeTimesChangeListener.listenReplyLikedTimesChange()]
↓
[更新数据库 → replyService.updateById()]
↓
[用户查询时直接获取最新点赞数]
总结
这个消息流转过程的关键优势:
1. 解耦合:点赞服务和学习服务通过消息队列解耦,不需要直接调用对方接口
2. 异步处理:点赞操作不需要等待数据库更新完成,提高响应速度
3. 最终一致性:通过消息机制保证点赞数在各个服务间最终一致
4. 可靠性:使用持久化队列和消息确认机制,确保消息不丢失
5. 扩展性:可以轻松增加其他服务监听相同的点赞事件
这就是整个点赞服务的消息流转过程,从用户操作到数据更新的完整链路。