Java通用型支付+电商平台双系统实战 | 完结

40 阅读5分钟

实战支付+电商双系统 玩“赚”Java技术栈

掌握基础增删改查(CRUD)只是 Java 开发的起点,要真正吃透这门技术栈,必须深入业务场景,通过实际项目来磨练架构思维。本文将围绕“电商”与“支付”这两个强关联的核心系统,从技术选型、架构设计到核心代码实现,带你一步步完成从初级开发向架构设计的进阶。

一、 项目背景与技术选型

在实际企业开发中,电商系统与支付系统通常既是协同工作的,又是物理隔离的。电商系统负责商品展示、订单生成;支付系统负责资金流水、渠道对接。

核心技术栈:

  • 后端框架:Spring Boot 3.x + Spring MVC
  • 数据访问:MyBatis-Plus + Druid 连接池
  • 数据库:MySQL 8.0 (主从架构模拟)
  • 缓存中间件:Redis (分布式缓存、分布式锁)
  • 消息队列:RabbitMQ (异步解耦、流量削峰)
  • 服务治理:Nacos (注册中心、配置中心)
  • RPC 调用:OpenFeign

二、 电商订单系统:构建高并发交易基石

电商系统的核心在于处理高并发下的订单一致性。这里我们通过“Redis 预扣库存 + MQ 异步下单”的模式来提升吞吐量。

1. 数据库设计

sql

复制

-- 商品表
CREATE TABLE `product` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '商品名称',
  `stock` int NOT NULL DEFAULT '0' COMMENT '库存',
  `price` decimal(10,2) NOT NULL COMMENT '单价',
  PRIMARY KEY (`id`)
);

-- 订单表
CREATE TABLE `order_info` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_no` varchar(64) NOT NULL COMMENT '订单号',
  `product_id` bigint NOT NULL COMMENT '商品ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `amount` decimal(10,2) NOT NULL COMMENT '订单金额',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待支付,1-已支付',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`)
);

2. Redis 预扣库存逻辑(Lua 脚本保证原子性)

为了防止超卖,不能直接查库扣减,必须利用 Redis 的单线程特性。

java

复制

@Service
@RequiredArgsConstructor
public class StockService {

    private final RedisTemplate<String, Object> redisTemplate;

    // Lua 脚本:检查库存并扣减
    private static final String STOCK_LUA_SCRIPT =
            "if tonumber(redis.call('get', KEYS[1])) >= tonumber(ARGV[1]) then " +
            "   return redis.call('decrby', KEYS[1], ARGV[1]); " +
            "else " +
            "   return -1; " +
            "end";

    public boolean deductStock(Long productId, Integer quantity) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(STOCK_LUA_SCRIPT, Long.class);
        String key = "product:stock:" + productId;
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), quantity);
        return result != null && result >= 0;
    }
}

3. 下单与消息发送

库存扣减成功后,发送消息到 MQ,由消费者异步落库。

java

复制

@Service
@RequiredArgsConstructor
public class OrderService {

    private final RabbitTemplate rabbitTemplate;
    
    @Transactional(rollbackFor = Exception.class)
    public String createOrder(Long productId, Integer quantity, Long userId) {
        // 1. 生成订单号
        String orderNo = UUID.randomUUID().toString().replace("-", "");
        
        // 2. 封装消息对象
        OrderDTO orderDTO = OrderDTO.builder()
                .orderNo(orderNo)
                .productId(productId)
                .userId(userId)
                .quantity(quantity)
                .build();
                
        // 3. 发送消息到订单交换机
        rabbitTemplate.convertAndSend("order.exchange", "order.routing.key", orderDTO);
        
        return orderNo;
    }
}

java

复制

@Component
@RabbitListener(queues = "order.queue")
@RequiredArgsConstructor
public class OrderConsumer {

    private final OrderMapper orderMapper;
    private final ProductMapper productMapper;

    @RabbitHandler
    public void handleOrder(OrderDTO orderDTO) {
        try {
            // 幂等性校验:检查订单是否已存在
            OrderInfo existingOrder = orderMapper.selectByOrderNo(orderDTO.getOrderNo());
            if (existingOrder != null) {
                return; // 避免重复消费
            }

            // 查询商品价格(实际场景会走缓存)
            Product product = productMapper.selectById(orderDTO.getProductId());
            
            // 构建订单实体
            OrderInfo order = OrderInfo.builder()
                    .orderNo(orderDTO.getOrderNo())
                    .productId(orderDTO.getProductId())
                    .userId(orderDTO.getUserId())
                    .amount(product.getPrice().multiply(new BigDecimal(orderDTO.getQuantity())))
                    .status(0) // 待支付
                    .build();
            
            orderMapper.insert(order);
            
            // TODO: 可以在这里触发“支付超时自动取消”的延时消息
        } catch (Exception e) {
            // 实际场景需要记录死信队列或人工介入
            throw new RuntimeException("订单入库失败");
        }
    }
}

三、 支付系统:核心架构设计

支付系统要求极高的稳定性与数据一致性。我们采用“策略模式”对接不同支付渠道(支付宝、微信),并使用“状态机”管理支付状态。

1. 支付核心接口设计

java

复制

// 支付渠道策略接口
public interface PaymentStrategy {
    
    /**
     * 统一下单接口
     */
    PaymentResult unifiedOrder(PaymentRequest request);
    
    /**
     * 支付回调处理
     */
    Boolean handleCallback(Map<String, String> params);
}

// 支付宝实现
@Component("aliPayStrategy")
public class AliPayStrategy implements PaymentStrategy {

    @Override
    public PaymentResult unifiedOrder(PaymentRequest request) {
        // 模拟调用支付宝 SDK
        System.out.println("调用支付宝 API,订单号:" + request.getOrderNo());
        return PaymentResult.builder()
                .payUrl("https://openapi.alipay.com/..." + request.getOrderNo())
                .build();
    }

    @Override
    public Boolean handleCallback(Map<String, String> params) {
        // 1. 验签(必须)
        // 2. 校验金额与订单号
        // 3. 执行业务逻辑
        return true;
    }
}

2. 支付上下文与工厂模式

根据前端传来的渠道类型,动态选择具体的支付实现。

java

复制

@Service
@RequiredArgsConstructor
public class PaymentContext {

    private final Map<String, PaymentStrategy> strategyMap;

    /**
     * 发起支付
     */
    public PaymentResult executePayment(PaymentRequest request) {
        PaymentStrategy strategy = strategyMap.get(request.getChannel() + "PayStrategy");
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付渠道");
        }
        return strategy.unifiedOrder(request);
    }
}

3. 支付回调与分布式事务处理

这是最关键的一步。支付系统收到成功回调后,需要更新自身状态,并通知电商系统更新订单状态。这里使用 Seata 或简单的消息队列实现最终一致性。

java

复制

@RestController
@RequestMapping("/api/payment")
@RequiredArgsConstructor
public class PaymentCallbackController {

    private final PaymentService paymentService;
    private final OrderFeignClient orderFeignClient; // 调用电商系统接口

    @PostMapping("/callback/ali")
    public String aliCallback(@RequestBody Map<String, String> params) {
        // 1. 校验签名与金额
        if (!paymentService.verifyAliPay(params)) {
            return "fail";
        }

        String tradeNo = params.get("out_trade_no"); // 商户订单号
        
        // 2. 更新支付流水状态(加分布式锁防止并发回调)
        PaymentRecord record = paymentService.updatePaymentStatus(tradeNo, "SUCCESS");
        
        if (record != null && "SUCCESS".equals(record.getStatus())) {
            // 3. 通知电商系统:支付成功
            // 为了保证高可用,通常这里结合本地消息表做重试
            try {
                orderFeignClient.updateOrderStatus(record.getOrderNo(), 1);
            } catch (Exception e) {
                // 记录日志,通过补偿任务重试
                log.error("通知电商系统失败", e);
            }
        }
        
        return "success";
    }
}

四、 架构设计要点总结

在从单体应用向微服务演进的过程中,这两个系统的实战经验至关重要:

  1. 高并发处理:电商侧通过 Redis 预扣库存 + MQ 异步解耦,将流量冲击挡在数据库之外。
  2. 分布式事务:支付与订单属于两个不同的数据库,不能使用本地事务。实战中推荐使用 MQ 最终一致性方案(如本例),或者在强一致性要求场景下使用 Seata AT 模式。
  3. 系统解耦:支付系统不应强依赖于电商系统。回调通知失败时,支付系统应有重试机制;电商系统也应提供主动查询支付状态的接口。
  4. 策略模式:支付渠道多变,策略模式能有效隔离代码,新增渠道只需新增实现类,符合开闭原则。

通过以上实战代码,你可以看到 Java 技术栈在处理复杂业务逻辑时的强大能力。真正的架构师不仅仅是会写代码,更是在业务与技术之间寻找最平衡的解决方案。