系统架构设计
电商预售系统的核心在于处理订单的异步创建和库存的预扣减。我们采用微服务架构,将系统拆分为以下核心模块:
服务划分
- 商品服务:管理预售商品信息、库存
- 订单服务:处理订单创建、状态流转
- 支付服务:对接第三方支付渠道
- 库存服务:负责库存的预扣减和释放
- 定时任务服务:处理超时未支付订单
技术选型
Spring Boot 2.7.15 + MySQL 8.0 + Redis 7.0 + RabbitMQ 3.12
数据库设计
商品表设计
```sql
CREATE TABLE `pre_sale_product` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`product_id` bigint(20) NOT NULL COMMENT '商品ID',
`pre_sale_price` decimal(10,2) NOT NULL COMMENT '预售价格',
`total_stock` int(11) NOT NULL COMMENT '预售总库存',
`locked_stock` int(11) DEFAULT '0' COMMENT '已锁定库存',
`start_time` datetime NOT NULL COMMENT '预售开始时间',
`end_time` datetime NOT NULL COMMENT '预售结束时间',
`delivery_time` datetime NOT NULL COMMENT '预计发货时间',
`deposit_amount` decimal(10,2) NOT NULL COMMENT '定金金额',
`final_payment_start` datetime NOT NULL COMMENT '尾款支付开始时间',
`final_payment_end` datetime NOT NULL COMMENT '尾款支付结束时间',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-未开始 2-进行中 3-已结束',
PRIMARY KEY (`id`),
KEY `idx_product_time` (`product_id`,`start_time`,`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
预售订单表
```sql
CREATE TABLE `pre_sale_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单号',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`product_id` bigint(20) NOT NULL COMMENT '商品ID',
`deposit_amount` decimal(10,2) NOT NULL COMMENT '定金金额',
`final_amount` decimal(10,2) DEFAULT NULL COMMENT '尾款金额',
`total_amount` decimal(10,2) NOT NULL COMMENT '总金额',
`deposit_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '定金状态:1-待支付 2-已支付 3-已退款',
`final_status` tinyint(4) DEFAULT '0' COMMENT '尾款状态:0-未开始 1-待支付 2-已支付 3-已退款',
`order_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '订单状态:1-定金待支付 2-定金已支付 3-尾款待支付 4-尾款已支付 5-已取消 6-已完成',
`lock_expire_time` datetime DEFAULT NULL COMMENT '库存锁定过期时间',
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_status` (`user_id`,`order_status`),
KEY `idx_lock_expire` (`lock_expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
核心功能实现
库存预扣减服务

库存扣减是预售系统的关键,必须保证在高并发下的数据一致性。
```java
@Service
public class StockService {
@Autowired
private RedisTemplate
redisTemplate;
@Autowired
private PreSaleProductMapper productMapper;
/
预扣减库存
@param productId 商品ID
@param quantity 购买数量
@param expireMinutes 锁定过期时间(分钟)
@return 锁定ID,用于后续确认或释放
/
public String preDeductStock(Long productId, Integer quantity, Integer expireMinutes) {
String lockKey = "stock:lock:" + productId;
String stockKey = "stock:pre_sale:" + productId;
// 使用Redis分布式锁
String lockId = UUID.randomUUID().toString();
Boolean lockAcquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockId, 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(lockAcquired)) {
throw new RuntimeException("系统繁忙,请稍后重试");
}
try {
// 检查并扣减Redis中的库存
Long currentStock = redisTemplate.opsForValue()
.decrement(stockKey, quantity);
if (currentStock != null && currentStock < 0) {
// 库存不足,恢复库存
redisTemplate.opsForValue().increment(stockKey, quantity);
throw new RuntimeException("库存不足");
}
// 记录库存锁定
String lockRecordId = UUID.randomUUID().toString();
String lockRecordKey = "stock:lock_record:" + lockRecordId;
Map lockRecord = new HashMap<>();
lockRecord.put("productId", productId.toString());
lockRecord.put("quantity", quantity.toString());
lockRecord.put("expireTime",
LocalDateTime.now().plusMinutes(expireMinutes).toString());
redisTemplate.opsForHash().putAll(lockRecordKey, lockRecord);
redisTemplate.expire(lockRecordKey, expireMinutes + 5, TimeUnit.MINUTES);
return lockRecordId;
} finally {
// 释放分布式锁
String currentLockId = redisTemplate.opsForValue().get(lockKey);
if (lockId.equals(currentLockId)) {
redisTemplate.delete(lockKey);
}
}
}
}
```
订单创建服务
```java
@Service
@Transactional(rollbackFor = Exception.class)
public class OrderService {
@Autowired
private StockService stockService;
@Autowired
private RabbitTemplate rabbitTemplate;
/
创建预售订单
/
public String createPreSaleOrder(CreateOrderDTO dto) {
// 1. 验证预售活动
PreSaleProduct product = validatePreSale(dto.getProductId());
// 2. 预扣减库存
String lockRecordId = stockService.preDeductStock(
dto.getProductId(),
dto.getQuantity(),
30 // 锁定30分钟
);
// 3. 生成订单号
String orderNo = generateOrderNo();
// 4. 创建订单记录
PreSaleOrder order = new PreSaleOrder();
order.setOrderNo(orderNo);
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setDepositAmount(product.getDepositAmount());
order.setTotalAmount(product.getPreSalePrice().multiply(
new BigDecimal(dto.getQuantity())));
order.setLockExpireTime(LocalDateTime.now().plusMinutes(30));
order.setOrderStatus(1); // 定金待支付
preSaleOrderMapper.insert(order);
// 5. 发送延迟消息,30分钟后检查支付状态
OrderCheckMessage message = new OrderCheckMessage();
message.setOrderNo(orderNo);
message.setCheckType("DEPOSIT_PAYMENT");
rabbitTemplate.convertAndSend(
"order.delay.exchange",
"order.check.routingKey",
message,
msg -> {
msg.getMessageProperties().setDelay(30 60 1000); // 30分钟
return msg;
}
);
return orderNo;
}
}
```
支付流程实现
定金支付回调处理
```java
@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@PostMapping("/deposit/callback")
public String handleDepositCallback(@RequestBody PaymentCallbackDTO callback) {
// 验证签名
if (!verifySignature(callback)) {
return "FAIL";
}
// 查询订单
PreSaleOrder order = orderService.getByOrderNo(callback.getOrderNo());
if (order == null || !order.getOrderStatus().equals(1)) {
return "FAIL";
}
// 更新订单状态
order.setDepositStatus(2); // 已支付
order.setOrderStatus(2); // 定金已支付
order.setLockExpireTime(null); // 清除锁定过期时间
orderService.updateOrder(order);
// 发送尾款支付提醒
sendFinalPaymentRemind(order);
return "SUCCESS";
}
}
```
定时任务设计
超时订单处理
```java
@Component
public class OrderTimeoutTask {
@Scheduled(fixedDelay = 60000) // 每分钟执行一次
public void handleTimeoutOrders() {
// 查询超过30分钟未支付定金的订单
List timeoutOrders = orderMapper.selectTimeoutOrders(
LocalDateTime.now().minusMinutes(30));
for (PreSaleOrder order : timeoutOrders) {
try {
// 释放库存
stockService.releaseStock(order.getProductId(), 1);
// 更新订单状态为已取消
order.setOrderStatus(5);
orderMapper.updateById(order);
// 发送订单取消通知
notifyService.sendOrderCanceled(order.getUserId(), order.getOrderNo());
} catch (Exception e) {
log.error("处理超时订单失败: {}", order.getOrderNo(), e);
}
}
}
}
```
配置部署
Redis配置
在application.yml中添加Redis配置:
```yaml
spring:
redis:
host: 127.0.0.1
port: 6379
password: your_password_here
database: 0
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
timeout: 5000ms
```
RabbitMQ配置
```yaml
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin123
virtual-host: /
开启延迟消息插件支持
dynamic: true
确认模式
publisher-confirm-type: correlated
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
```
延迟队列配置
```java
@Configuration
public class RabbitMQConfig {
@Bean
public CustomExchange orderDelayExchange() {
Map args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(
"order.delay.exchange",
"x-delayed-message",
true,
false,
args
);
}
@Bean
public Queue orderCheckQueue() {
return new Queue("order.check.queue", true);
}
@Bean
public Binding orderCheckBinding() {
return BindingBuilder
.bind(orderCheckQueue())
.to(orderDelayExchange())
.with("order.check.routingKey")
.noargs();
}
}
```
压力测试与优化
JMeter测试脚本
创建create_order.jmx文件,模拟高并发下单场景:
```xml
false
true
false
BASE_URL
http://localhost:8080
=
continue
false
100
100
10
false
false
{"userId":${__Random(1,10000)},"productId":1001,"quantity":1}
=
localhost
8080
http
/api/order/create
POST
true
false
true
false
```
性能优化建议
- 数据库层面:为order_status、created_time字段建立联合索引
- 缓存层面:使用Redis集群,设置合理的过期时间
- 代码层面:异步处理非核心逻辑,如发送通知、记录日志
- 监控层面:集成Prometheus监控关键指标,设置报警阈值
错误处理与日志
全局异常处理
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage(), e);
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result> handleException(Exception e) {
log.error("系统异常: {}", e.getMessage(), e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
```
关键日志记录
```java
@Component
public class OrderLogAspect {
@Around("@annotation(com.example.annotation.OperateLog)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("操作成功 | 方法: {} | 耗时: {}ms | 参数: {}",
joinPoint.getSignature().getName(),
endTime - startTime,
Arrays.toString(joinPoint.getArgs()));
return result;
} catch (Exception e) {