使用责任链模式设计电商下单流程(Java 实战)
适用技术栈:Spring Boot + Spring MVC + MyBatis/JPA(不限)
记得点赞加收藏哦😁😁😁
1. 业务场景与目标
电商系统下单通常包含这些步骤(简化版):
- 基础校验:校验用户是否登录、参数是否完整、收货地址是否存在等
- 商品与库存校验:商品是否上架、是否可售、库存是否足够
- 价格与优惠计算:商品价格、运费、优惠券、满减活动等
- 风控校验:黑名单用户、异常地址、下单频率等
- 库存锁定:预占库存,防止超卖
- 订单落库 :写
order、order_item等表 - 后置动作:发送消息(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静态排序) - 支付前后的责任链:下单责任链、支付回调责任链、公用一套抽象
以上代码都是真实项目可以直接落地的写法,你可以按需裁剪字段与服务名,把这套责任链结构嵌入你现有的订单系统即可。