支付订单成功异步MQ权益发放设计和开发

目录

一、讲解为什么支付成功后权益发放要用异步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());
}
相关推荐
acd120092 小时前
Nginx 反向代理的缓冲区坑:后端响应大时接口偶发 502
java·运维·nginx
高冷小鱼殿下2 小时前
内部类Lambda与枚举
java·开发语言
小蒜学长3 小时前
编程语言在线学习平台的设计与实现(代码+数据库+LW)
java·spring boot·后端·编程语言在线学习平台·在线编码
步行cgn3 小时前
Spring MVC 执行流程详解:一次请求的完整旅程
java·spring·mvc
增量星球3 小时前
Spring AI 2.0 Tool Calling 到底怎么跑起来的:从 @Tool 到两次模型调用
java·agent·springai·ai coding
步行cgn3 小时前
Spring MVC 九大核心角色详解:请求处理链路中的每一个关键组件
java·spring·mvc
智恒百亿3 小时前
一台 5090 八卡服务器怎么分给一个团队用?多模型多任务的显存分配实战
java·大数据·人工智能
skywalk81633 小时前
lightmem记忆系统实战存取.小浣熊一句话:从lightmem记忆中,取出FreeBSD linux仿真环境安装启动workbuddy的信息,整理成文档
人工智能·硬件架构·记忆·lightmem
瑞码空间4 小时前
面向接口编程与开闭原则在模拟 JDBC 驱动中的应用
java·jdbc