目录
一、讲解为什么支付成功后权益发放要用异步MQ,以及整体架构链路设计
[1.1 什么是权益发放?](#1.1 什么是权益发放?)
[1.2 为什么不能在支付回调里直接发放权益](#1.2 为什么不能在支付回调里直接发放权益)
[1.3 整体架构链路](#1.3 整体架构链路)
[1.4 MQ方案选型说明](#1.4 MQ方案选型说明)
[1.5 注意](#1.5 注意)
[1.6 BenefitGrantMQConfig 配置实现(放在 common 模块)](#1.6 BenefitGrantMQConfig 配置实现(放在 common 模块))
[1.7 关键参数说明](#1.7 关键参数说明)
二、权益发放消息体BenefitGrantMessage数据结构设计
[2.1 为什么消息体设计至关重要](#2.1 为什么消息体设计至关重要)
[2.2 字段设计说明](#2.2 字段设计说明)
[2.3 消息体复用设计亮点](#2.3 消息体复用设计亮点)
三、单权益订单发放processSingleBenefit设计和开发
[3.1 什么是单权益订单(BENEFIT_ORDER)](#3.1 什么是单权益订单(BENEFIT_ORDER))
[3.2 processSingleBenefit 实现](#3.2 processSingleBenefit 实现)
一、讲解为什么支付成功后权益发放要用异步MQ,以及整体架构链路设计
1.1 什么是权益发放?
-
核心定义:用户支付成功后,系统根据订单内容给用户开通对应的服务权限
-
打个比方:就像你买了视频会员,支付成功后账号立即获得观看权限
1.2 为什么不能在支付回调里直接发放权益
-
痛点分析
-
微信支付回调要求5秒内响应,超时会触发重试
-
权益发放涉及数据库写入、跨服务调用,耗时不可控
-
如果权益发放失败,直接影响回调响应,会让微信误以为支付处理失败,引发重复回调
-
同步发放导致回调接口承压,系统稳定性差
-
-
核心思路
-
回调接口只做两件事:更新订单状态 + 发 MQ 消息,快速返回 SUCCESS
-
权益发放逻辑异步交给 account-service 消费处理,解耦两个服务
-
1.3 整体架构链路

1.4 MQ方案选型说明
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| RabbitMQ(选用) | 消息可靠、手动ACK、成熟稳定 | 需要额外部署 | 权益发放、支付通知 |
| 直接HTTP调用 | 简单直接 | 同步阻塞、强耦合、易失败 | 实时性要求极高场景 |
| 数据库轮询 | 无额外组件 | 性能差、延迟高 | 低频补偿场景 |
1.5 注意
-
product-service 负责发消息 ,account-service 负责消费消息,两者只通过 MQ 通信
-
TopicExchange 支持通配符路由,扩展性好
-
本章场景使用精确路由键
benefit.grant.routing.key,未来可扩展为benefit.#覆盖多种权益消息 -
该配置类放在 common 模块,product-service 和 account-service 都能使用
1.6 BenefitGrantMQConfig 配置实现(放在 common 模块)
java
/**
* 权益发放 MQ 配置(common 模块共享)
* 同时提供全局 JSON 消息转换器,供 product-service 和 account-service 使用
*/
@Configuration
public class BenefitGrantMQConfig {
// 权益发放交换机
public static final String BENEFIT_GRANT_EXCHANGE = "benefit.grant.exchange";
// 权益发放队列(account-service 消费)
public static final String BENEFIT_GRANT_QUEUE = "benefit.grant.queue";
// 权益发放路由 key
public static final String BENEFIT_GRANT_ROUTING_KEY = "benefit.grant.routing.key";
/**
* 配置 JSON 消息转换器,替代默认的 Java 原生序列化
* Spring AMQP 3.x 默认使用 Java 原生序列化,会校验反序列化白名单导致失败,
* 改用 Jackson2JsonMessageConverter 以 JSON 方式序列化/反序列化消息
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 配置 RabbitTemplate,使用 JSON 消息转换器
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
@Bean
public TopicExchange benefitGrantExchange() {
return new TopicExchange(BENEFIT_GRANT_EXCHANGE, true, false);
}
@Bean
public Queue benefitGrantQueue() {
return new Queue(BENEFIT_GRANT_QUEUE, true, false, false);
}
@Bean
public Binding benefitGrantBinding() {
return BindingBuilder.bind(benefitGrantQueue())
.to(benefitGrantExchange())
.with(BENEFIT_GRANT_ROUTING_KEY);
}
}
1.7 关键参数说明
-
durable=true:持久化,RabbitMQ 重启后 Exchange/Queue 不丢失 -
exclusive=false:非独占,允许多个消费者连接 -
autoDelete=false:不自动删除,队列长期存在
二、权益发放消息体BenefitGrantMessage数据结构设计
2.1 为什么消息体设计至关重要
-
MQ 消息是 product-service 和 account-service 之间的唯一通信载体
-
消息体字段设计好了,消费者无需再次查询数据库,直接基于消息完成权益发放
-
消息体要同时支持两种订单类型:单权益订单(1条权益)和套餐订单(多条权益)
-
BenefitGrantMessage 消息体设计
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BenefitGrantMessage {
private String outTradeNo; // 订单编号(用于幂等判断)
private Long accountId; // 用户账号ID(权益发放给谁)
private Long orderId; // 订单ID(account_benefit表的 product_order_id)
private String orderType; // 订单类型:BENEFIT_ORDER / PACKAGE_ORDER
private List<BenefitItem> benefitItems; // 权益列表
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class BenefitItem {
private Long benefitId; // 权益ID
private String benefitCode; // 权益编码
private Integer count; // 发放次数
private Integer validDays; // 单次有效天数(null 默认 365 天)
}
}
2.2 字段设计说明
| 字段 | 说明 | 设计原因 |
|---|---|---|
outTradeNo |
订单编号 | 用于日志追踪和幂等校验 |
orderId |
订单ID | 写入 account_benefit 表的 product_order_id,供幂等查重 |
orderType |
订单类型 | 消费者可据此判断是否多权益 |
benefitItems |
权益列表 | 单权益=1条,套餐=N条,统一结构 |
validDays |
有效天数 | null 时消费者默认按 365 天计算 |
2.3 消息体复用设计亮点
-
权益订单:
benefitItems只包含 1 条 BenefitItem -
套餐订单:
benefitItems包含套餐下所有权益 ,只发1 条 MQ 消息(避免多次发送) -
消费者逻辑统一:遍历
benefitItems逐条发放,无需区分订单类型
三、单权益订单发放processSingleBenefit设计和开发
3.1 什么是单权益订单(BENEFIT_ORDER)
-
用户直接购买某一项具体权益,例如:购买 10 次 AI 面试次数
-
对应
OrderTypeEnum.BENEFIT_ORDER,订单中有明确的benefitId -
发放逻辑:查出权益信息 → 构建 1 条 BenefitItem → 发 MQ
3.2 processSingleBenefit 实现
java
private void processSingleBenefit(ProductOrderDO orderDO) {
// 1. 根据 benefitId 查询权益基础信息(validDays 等)
BenefitDO benefitDO = benefitMapper.selectById(orderDO.getBenefitId());
if (benefitDO == null) {
log.error("权益不存在,发放失败, benefitId:{}", orderDO.getBenefitId());
return;
}
// 2. 构建单条 BenefitItem
BenefitGrantMessage.BenefitItem item = BenefitGrantMessage.BenefitItem.builder()
.benefitId(benefitDO.getId())
.benefitCode(benefitDO.getBenefitCode())
.count(orderDO.getPurchaseCount()) // 购买数量
.validDays(benefitDO.getValidDays()) // 单次有效天数
.build();
// 3. 构建消息并发送
BenefitGrantMessage message = BenefitGrantMessage.builder()
.outTradeNo(orderDO.getOutTradeNo())
.accountId(orderDO.getAccountId())
.orderId(orderDO.getId())
.orderType(orderDO.getOrderType())
.benefitItems(List.of(item))
.build();
rabbitTemplate.convertAndSend(
BenefitGrantMQConfig.BENEFIT_GRANT_EXCHANGE,
BenefitGrantMQConfig.BENEFIT_GRANT_ROUTING_KEY,
message
);
log.info("单权益发放消息已发送, outTradeNo:{}, benefitId:{}", orderDO.getOutTradeNo(), benefitDO.getId());
}