使用责任链模式设计电商下单流程(Java 实战)

使用责任链模式设计电商下单流程(Java 实战)

适用技术栈:Spring Boot + Spring MVC + MyBatis/JPA(不限)


记得点赞加收藏哦😁😁😁

1. 业务场景与目标

电商系统下单通常包含这些步骤(简化版):

  1. 基础校验:校验用户是否登录、参数是否完整、收货地址是否存在等
  2. 商品与库存校验:商品是否上架、是否可售、库存是否足够
  3. 价格与优惠计算:商品价格、运费、优惠券、满减活动等
  4. 风控校验:黑名单用户、异常地址、下单频率等
  5. 库存锁定:预占库存,防止超卖
  6. 订单落库 :写 orderorder_item 等表
  7. 后置动作:发送消息(MQ)、更新缓存、记录日志等

需求:

  • 各步骤可插拔可扩展
  • 新增 / 删除 / 调整顺序,不影响主流程代码
  • 能结合 Spring 容器自动装配、按顺序执行

这就是典型的 责任链模式(Chain of Responsibility) 使用场景。


2. 总体设计

2.1 核心思路

  • 定义一个统一的 处理器接口OrderCreateHandler
  • 每个步骤实现一个 Handler,只关心自己那一块逻辑
  • 使用一个 责任链执行器 :按顺序依次执行所有 Handler
  • 通过 Spring 的 @Component + @Order 自动注入 + 排序

2.2 关键对象

  • OrderSubmitRequest:下单请求 DTO(前端传参)
  • OrderContext:下单上下文,贯穿整个责任链,存放中间结果
  • OrderCreateHandler:责任链节点接口
  • OrderCreateChain:责任链执行器,会依次执行所有 Handler

3. 核心抽象设计

3.1 下单请求 DTO

java 复制代码
@Data
public class OrderSubmitRequest {

    private Long userId;

    private Long addressId;

    // 商品明细
    private List<OrderItemDTO> items;

    // 优惠券 id
    private Long couponId;

    // 支付渠道
    private String payChannel;

    // 其他扩展字段...
}

@Data
public class OrderItemDTO {
    private Long skuId;
    private Integer quantity;
}

3.2 下单上下文 OrderContext

用来在 Handler 之间传递数据,不用反复查库 / 传一堆参数。

java 复制代码
@Data
public class OrderContext {

    /** 前端请求参数 */
    private OrderSubmitRequest request;

    /** 用户信息 */
    private UserInfoDTO userInfo;

    /** 收货地址信息 */
    private AddressDTO address;

    /** 商品信息(含价格、库存等) */
    private List<SkuInfoDTO> skuInfos;

    /** 优惠后的金额等 */
    private BigDecimal totalAmount;
    private BigDecimal freightAmount;
    private BigDecimal discountAmount;
    private BigDecimal payAmount;

    /** 订单实体(即将落库的) */
    private OrderEntity orderEntity;
    private List<OrderItemEntity> orderItemEntities;

    /** 其他上下文使用到的扩展字段 */
    private Map<String, Object> extMap = new HashMap<>();
}

3.3 统一处理器接口

java 复制代码
public interface OrderCreateHandler {

    /**
     * 是否支持当前上下文(可选:做一些前置条件判断,不需要就直接返回 true)
     */
    default boolean support(OrderContext context) {
        return true;
    }

    /**
     * 核心处理逻辑
     */
    void handle(OrderContext context);
}

可以选择加上 OrderCreateException 自定义异常,统一抛出业务错误。


4. 责任链执行器

java 复制代码
@Slf4j
@Component
public class OrderCreateChain {

    private final List<OrderCreateHandler> handlers;

    /**
     * Spring 会自动将所有实现 OrderCreateHandler 的 Bean 注入进来,
     * 再根据 @Order 注解进行排序。
     */
    public OrderCreateChain(List<OrderCreateHandler> handlers) {
        this.handlers = handlers;
    }

    public void execute(OrderContext context) {
        for (OrderCreateHandler handler : handlers) {
            // 可选:按需跳过不支持的 handler
            if (!handler.support(context)) {
                continue;
            }
            String handlerName = handler.getClass().getSimpleName();
            log.info("OrderCreateChain start handler: {}", handlerName);
            handler.handle(context);
            log.info("OrderCreateChain end handler: {}", handlerName);
        }
    }
}

5. 具体 Handler 实现示例

5.1 基础参数与用户校验处理器

java 复制代码
@Slf4j
@Component
@Order(10)
public class BasicValidateHandler implements OrderCreateHandler {

    @Resource
    private UserService userService;

    @Resource
    private AddressService addressService;

    @Override
    public void handle(OrderContext context) {
        OrderSubmitRequest request = context.getRequest();
        if (request == null || CollectionUtils.isEmpty(request.getItems())) {
            throw new BizException("订单明细不能为空");
        }

        if (request.getUserId() == null) {
            throw new BizException("用户未登录");
        }

        // 查询并缓存用户信息
        UserInfoDTO userInfo = userService.findById(request.getUserId());
        if (userInfo == null) {
            throw new BizException("用户不存在");
        }
        context.setUserInfo(userInfo);

        // 收货地址校验
        AddressDTO addressDTO = addressService.findById(request.getAddressId());
        if (addressDTO == null || !addressDTO.getUserId().equals(request.getUserId())) {
            throw new BizException("收货地址不合法");
        }
        context.setAddress(addressDTO);

        log.info("BasicValidateHandler success");
    }
}

5.2 商品与库存校验处理器

java 复制代码
@Slf4j
@Component
@Order(20)
public class SkuValidateHandler implements OrderCreateHandler {

    @Resource
    private SkuService skuService;

    @Override
    public void handle(OrderContext context) {
        List<OrderItemDTO> items = context.getRequest().getItems();
        List<Long> skuIds = items.stream()
                .map(OrderItemDTO::getSkuId)
                .collect(Collectors.toList());

        List<SkuInfoDTO> skuInfos = skuService.listSkuInfos(skuIds);
        if (skuInfos.size() != skuIds.size()) {
            throw new BizException("部分商品不存在");
        }

        // 校验上架状态与库存
        Map<Long, SkuInfoDTO> skuMap = skuInfos.stream()
                .collect(Collectors.toMap(SkuInfoDTO::getId, s -> s));

        for (OrderItemDTO item : items) {
            SkuInfoDTO skuInfo = skuMap.get(item.getSkuId());
            if (!skuInfo.getOnSale()) {
                throw new BizException("商品已下架:" + skuInfo.getName());
            }
            if (skuInfo.getStock() < item.getQuantity()) {
                throw new BizException("库存不足:" + skuInfo.getName());
            }
        }

        context.setSkuInfos(skuInfos);
        log.info("SkuValidateHandler success");
    }
}

5.3 价格与优惠计算处理器

java 复制代码
@Slf4j
@Component
@Order(30)
public class PriceCalculateHandler implements OrderCreateHandler {

    @Resource
    private PromotionService promotionService;

    @Override
    public void handle(OrderContext context) {
        OrderSubmitRequest request = context.getRequest();
        List<OrderItemDTO> items = request.getItems();
        Map<Long, SkuInfoDTO> skuMap = context.getSkuInfos().stream()
                .collect(Collectors.toMap(SkuInfoDTO::getId, s -> s));

        BigDecimal totalAmount = BigDecimal.ZERO;

        for (OrderItemDTO item : items) {
            SkuInfoDTO skuInfo = skuMap.get(item.getSkuId());
            BigDecimal lineAmount = skuInfo.getPrice()
                    .multiply(BigDecimal.valueOf(item.getQuantity()));
            totalAmount = totalAmount.add(lineAmount);
        }

        // 运费、优惠券、满减等
        BigDecimal freightAmount = promotionService.calcFreight(context);
        BigDecimal discountAmount = promotionService.calcDiscount(context, request.getCouponId());

        BigDecimal payAmount = totalAmount.add(freightAmount).subtract(discountAmount);
        if (payAmount.compareTo(BigDecimal.ZERO) <= 0) {
            payAmount = new BigDecimal("0.01"); // 最低 0.01
        }

        context.setTotalAmount(totalAmount);
        context.setFreightAmount(freightAmount);
        context.setDiscountAmount(discountAmount);
        context.setPayAmount(payAmount);

        log.info("PriceCalculateHandler success, payAmount={}", payAmount);
    }
}

5.4 风控校验处理器

java 复制代码
@Slf4j
@Component
@Order(40)
public class RiskCheckHandler implements OrderCreateHandler {

    @Resource
    private RiskService riskService;

    @Override
    public void handle(OrderContext context) {
        boolean pass = riskService.checkOrderRisk(context);
        if (!pass) {
            throw new BizException("订单风险校验不通过");
        }
        log.info("RiskCheckHandler success");
    }
}

5.5 库存锁定处理器

java 复制代码
@Slf4j
@Component
@Order(50)
public class StockLockHandler implements OrderCreateHandler {

    @Resource
    private StockService stockService;

    @Override
    public void handle(OrderContext context) {
        boolean locked = stockService.lockStock(context);
        if (!locked) {
            throw new BizException("库存锁定失败");
        }
        log.info("StockLockHandler success");
    }
}

5.6 订单落库处理器

java 复制代码
@Slf4j
@Component
@Order(60)
public class OrderPersistHandler implements OrderCreateHandler {

    @Resource
    private OrderRepository orderRepository;

    @Override
    public void handle(OrderContext context) {
        // 构建 OrderEntity、OrderItemEntity
        OrderEntity orderEntity = buildOrderEntity(context);
        List<OrderItemEntity> itemEntities = buildOrderItemEntities(context);

        // 落库(注意加上事务)
        orderRepository.saveOrder(orderEntity, itemEntities);

        context.setOrderEntity(orderEntity);
        context.setOrderItemEntities(itemEntities);

        log.info("OrderPersistHandler success, orderNo={}", orderEntity.getOrderNo());
    }

    private OrderEntity buildOrderEntity(OrderContext context) {
        OrderEntity entity = new OrderEntity();
        entity.setOrderNo(generateOrderNo());
        entity.setUserId(context.getUserInfo().getId());
        entity.setTotalAmount(context.getTotalAmount());
        entity.setFreightAmount(context.getFreightAmount());
        entity.setDiscountAmount(context.getDiscountAmount());
        entity.setPayAmount(context.getPayAmount());
        entity.setStatus(OrderStatusEnum.CREATED.getCode());
        // 其他字段...
        return entity;
    }

    private List<OrderItemEntity> buildOrderItemEntities(OrderContext context) {
        // 根据 context 中 skuInfos、请求 items 拼装
        // 省略具体字段赋值...
        return new ArrayList<>();
    }

    private String generateOrderNo() {
        // 可使用分布式 ID / 雪花算法等
        return "O" + System.currentTimeMillis();
    }
}

5.7 后置动作处理器(MQ、日志等)

java 复制代码
@Slf4j
@Component
@Order(70)
public class AfterOrderCreateHandler implements OrderCreateHandler {

    @Resource
    private OrderEventPublisher orderEventPublisher;

    @Override
    public void handle(OrderContext context) {
        // 发送 MQ 供其他系统消费(发券、积分、短信等)
        orderEventPublisher.publishOrderCreatedEvent(context.getOrderEntity());
        log.info("AfterOrderCreateHandler success");
    }
}

6. Controller 层如何使用责任链

java 复制代码
@RestController
@RequestMapping("/api/order")
public class OrderController {

    @Resource
    private OrderCreateChain orderCreateChain;

    @PostMapping("/submit")
    public ApiResponse<OrderCreateResultVO> submitOrder(@RequestBody @Valid OrderSubmitRequest request) {

        OrderContext context = new OrderContext();
        context.setRequest(request);

        // 事务最好放在 Service 层,这里简化为直接调用
        orderCreateChain.execute(context);

        OrderEntity orderEntity = context.getOrderEntity();

        OrderCreateResultVO resultVO = new OrderCreateResultVO();
        resultVO.setOrderNo(orderEntity.getOrderNo());
        resultVO.setPayAmount(orderEntity.getPayAmount());
        resultVO.setPayChannel(request.getPayChannel());

        return ApiResponse.success(resultVO);
    }
}

7. 加事务位置建议

更严谨的写法:让 Service 层包一层事务,Controller 只负责接收请求。

java 复制代码
@Service
public class OrderService {

    @Resource
    private OrderCreateChain orderCreateChain;

    @Transactional(rollbackFor = Exception.class)
    public OrderContext createOrder(OrderSubmitRequest request) {
        OrderContext context = new OrderContext();
        context.setRequest(request);
        orderCreateChain.execute(context);
        return context;
    }
}

Controller 调用:

java 复制代码
@PostMapping("/submit")
public ApiResponse<OrderCreateResultVO> submitOrder(@RequestBody @Valid OrderSubmitRequest request) {
    OrderContext context = orderService.createOrder(request);
    // ...拼装返回结果
}

8. 优点总结

  • 解耦:每个校验 / 步骤都在独立 Handler 中,互不影响
  • 可扩展 :新增一个步骤,只需要新实现一个 OrderCreateHandler,加上 @Component + @Order 即可
  • 可复用:同一条责任链可以在多个入口重用,比如普通订单、预售订单只需部分 Handler 差异
  • 可测试:每个 Handler 都可以单测,专门测某个环节的逻辑

9. 可以扩展的方向

  • 引入 灰度/AB 实验 :某些 Handler 只对部分用户 / 渠道生效(在 support() 中判断)
  • 动态调整顺序:从数据库 / 配置中心读取顺序(替换掉 @Order 静态排序)
  • 支付前后的责任链:下单责任链、支付回调责任链、公用一套抽象

以上代码都是真实项目可以直接落地的写法,你可以按需裁剪字段与服务名,把这套责任链结构嵌入你现有的订单系统即可。

相关推荐
喝养乐多长不高2 小时前
Rabbit MQ:概述
java·rabbitmq·mq·amqp
IT_陈寒2 小时前
Spring Boot 3.2震撼发布:5个必知的新特性让你开发效率提升50%
前端·人工智能·后端
拾忆,想起2 小时前
Dubbo异步调用实战指南:提升微服务并发性能
java·服务器·网络协议·微服务·云原生·架构·dubbo
李慕婉学姐2 小时前
Springboot加盟平台推荐可视化系统ktdx2ldg(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
q***31893 小时前
微服务生态组件之Spring Cloud LoadBalancer详解和源码分析
java·spring cloud·微服务
Victor3563 小时前
Redis(127)Redis的内部数据结构是什么?
后端
Victor3563 小时前
Redis(126) Redis在实时统计中的应用有哪些?
后端
程序员爱钓鱼5 小时前
Python 综合项目实战:学生成绩管理系统(命令行版)
后端·python·ipython