掌握基础增删改查(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";
}
}
四、 架构设计要点总结
在从单体应用向微服务演进的过程中,这两个系统的实战经验至关重要:
- 高并发处理:电商侧通过 Redis 预扣库存 + MQ 异步解耦,将流量冲击挡在数据库之外。
- 分布式事务:支付与订单属于两个不同的数据库,不能使用本地事务。实战中推荐使用 MQ 最终一致性方案(如本例),或者在强一致性要求场景下使用 Seata AT 模式。
- 系统解耦:支付系统不应强依赖于电商系统。回调通知失败时,支付系统应有重试机制;电商系统也应提供主动查询支付状态的接口。
- 策略模式:支付渠道多变,策略模式能有效隔离代码,新增渠道只需新增实现类,符合开闭原则。
通过以上实战代码,你可以看到 Java 技术栈在处理复杂业务逻辑时的强大能力。真正的架构师不仅仅是会写代码,更是在业务与技术之间寻找最平衡的解决方案。