第 244 章:综合项目实战 - 电商平台
学习目标
- 完整电商系统从 0 到 1
- 微服务架构落地
- 核心业务场景实现
- 性能优化实战
一、项目概览
二、项目结构
2.1 模块划分
yaml
taskflow-ecommerce/
├── taskflow-common/ # 公共组件
│ ├── common-core/ # 工具类、异常
│ ├── common-redis/ # Redis 封装
│ ├── common-mq/ # MQ 封装
│ └── common-web/ # Web 通用配置
├── taskflow-gateway/ # API 网关
├── taskflow-user/ # 用户服务
├── taskflow-product/ # 商品服务
├── taskflow-order/ # 订单服务
├── taskflow-payment/ # 支付服务
├── taskflow-inventory/ # 库存服务
├── taskflow-cart/ # 购物车服务
├── taskflow-marketing/ # 营销服务
├── taskflow-search/ # 搜索服务
└── taskflow-job/ # 定时任务2.2 服务依赖
三、核心业务实现
3.1 商品服务
实体设计
java
@Data
@Entity
@Table(name = "t_product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private BigDecimal price;
private Long categoryId;
private Long brandId;
private String mainImage;
private String images;
private Integer status; // 0:下架 1:上架
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@Data
@Entity
@Table(name = "t_sku")
public class Sku {
@Id
private Long id;
private Long productId;
private String specs; // JSON 规格
private BigDecimal price;
private Integer stock;
private String pic;
}Service 层
java
@Service
@Slf4j
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private SkuMapper skuMapper;
@Autowired
private RedisTemplate<String, Product> redisTemplate;
private static final String CACHE_KEY = "product:";
private static final long CACHE_TTL = 30;
private static final TimeUnit CACHE_UNIT = TimeUnit.MINUTES;
public Product detail(Long id) {
String key = CACHE_KEY + id;
// 1. 缓存查询
Product cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return cached;
}
// 2. DB 查询
Product product = productMapper.selectById(id);
if (product == null) {
throw new BizException("商品不存在");
}
// 3. 回填缓存
redisTemplate.opsForValue().set(key, product, CACHE_TTL, CACHE_UNIT);
return product;
}
@Transactional
public void onSale(Long id) {
Product product = productMapper.selectById(id);
if (product == null) throw new BizException("商品不存在");
product.setStatus(1);
productMapper.updateById(product);
// 清除缓存
redisTemplate.delete(CACHE_KEY + id);
// 发送上架事件
eventBus.publish(new ProductOnSaleEvent(id));
}
}3.2 库存服务
Redis 预扣库存
java
@Service
@Slf4j
public class InventoryService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RedissonClient redisson;
private static final String STOCK_KEY = "stock:sku:";
/**
* 扣减库存(Lua 脚本保证原子)
*/
public boolean deduct(Long skuId, Integer quantity) {
String key = STOCK_KEY + skuId;
String luaScript = """
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock >= tonumber(ARGV[1]) then
redis.call('DECRBY', KEYS[1], ARGV[1])
return 1
end
return 0
""";
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(luaScript);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(key),
quantity.toString());
return result != null && result == 1;
}
/**
* 补偿回滚(扣减失败调用)
*/
public void compensate(Long skuId, Integer quantity) {
String key = STOCK_KEY + skuId;
redisTemplate.opsForValue().increment(key, quantity);
}
/**
* 同步库存到 DB
*/
@Scheduled(fixedRate = 5000)
public void syncToDb() {
// 周期把 Redis 库存同步到 DB
Set<String> keys = redisTemplate.keys(STOCK_KEY + "*");
if (keys != null) {
for (String key : keys) {
Long skuId = Long.parseLong(key.split(":")[2]);
Integer stock = Integer.parseInt(redisTemplate.opsForValue().get(key));
skuMapper.updateStock(skuId, stock);
}
}
}
}3.3 订单服务
下单流程
订单创建
java
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private CouponClient couponClient;
@Autowired
private KafkaTemplate<String, OrderEvent> kafkaTemplate;
@Transactional(rollbackFor = Exception.class)
public OrderCreateResult createOrder(OrderCreateRequest request) {
// 1. 校验商品和价格
BigDecimal totalAmount = BigDecimal.ZERO;
List<OrderItem> items = new ArrayList<>();
for (OrderItemRequest itemReq : request.getItems()) {
Sku sku = productClient.getSku(itemReq.getSkuId());
if (sku == null) {
throw new BizException("商品不存在:" + itemReq.getSkuId());
}
BigDecimal itemAmount = sku.getPrice()
.multiply(BigDecimal.valueOf(itemReq.getQuantity()));
totalAmount = totalAmount.add(itemAmount);
OrderItem item = new OrderItem();
item.setSkuId(sku.getId());
item.setPrice(sku.getPrice());
item.setQuantity(itemReq.getQuantity());
items.add(item);
}
// 2. 扣减库存
for (OrderItem item : items) {
boolean success = inventoryClient.deduct(item.getSkuId(), item.getQuantity());
if (!success) {
throw new BizException("库存不足:SKU=" + item.getSkuId());
}
}
// 3. 使用优惠券
BigDecimal discountAmount = BigDecimal.ZERO;
if (request.getCouponId() != null) {
CouponResult result = couponClient.lock(
request.getUserId(),
request.getCouponId(),
totalAmount
);
if (!result.isSuccess()) {
// 补偿库存
compensateInventory(items);
throw new BizException("优惠券不可用");
}
discountAmount = result.getDiscountAmount();
}
// 4. 创建订单
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(request.getUserId());
order.setTotalAmount(totalAmount);
order.setPayAmount(totalAmount.subtract(discountAmount));
order.setStatus(OrderStatus.CREATED);
order.setItems(items);
orderMapper.insert(order);
// 5. 发送事件
OrderEvent event = new OrderEvent();
event.setOrderId(order.getId());
event.setUserId(order.getUserId());
event.setPayAmount(order.getPayAmount());
kafkaTemplate.send("order-created", event);
return OrderCreateResult.success(order.getOrderNo());
}
private void compensateInventory(List<OrderItem> items) {
for (OrderItem item : items) {
inventoryClient.compensate(item.getSkuId(), item.getQuantity());
}
}
private String generateOrderNo() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))
+ RandomUtil.randomNumbers(6);
}
}3.4 支付服务
异步通知
java
@Service
@Slf4j
public class PaymentService {
@Autowired
private PaymentMapper paymentMapper;
@Autowired
private KafkaTemplate<String, PaymentEvent> kafkaTemplate;
public void handleNotify(PaymentNotifyRequest request) {
// 1. 验签
if (!verifySign(request)) {
throw new BizException("验签失败");
}
// 2. 幂等处理
Payment existing = paymentMapper.findByOrderNo(request.getOrderNo());
if (existing != null && existing.getStatus() == PaymentStatus.SUCCESS) {
log.info("订单已处理:{}", request.getOrderNo());
return;
}
// 3. 更新支付状态
Payment payment = existing != null ? existing : new Payment();
payment.setOrderNo(request.getOrderNo());
payment.setTradeNo(request.getTradeNo());
payment.setAmount(request.getAmount());
payment.setStatus(PaymentStatus.SUCCESS);
payment.setPaidAt(LocalDateTime.now());
if (existing == null) {
paymentMapper.insert(payment);
} else {
paymentMapper.updateById(payment);
}
// 4. 通知订单服务
PaymentEvent event = new PaymentEvent();
event.setOrderNo(request.getOrderNo());
event.setStatus(PaymentStatus.SUCCESS);
kafkaTemplate.send("payment-success", event);
}
}四、性能优化
4.1 多级缓存架构
java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public Cache<Long, Product> productCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build();
}
}
@Service
@Slf4j
public class ProductCacheService {
@Autowired
private Cache<Long, Product> caffeineCache;
@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Autowired
private ProductMapper productMapper;
public Product get(Long id) {
// L1: Caffeine
Product product = caffeineCache.getIfPresent(id);
if (product != null) {
log.debug("命中 L1 Caffeine");
return product;
}
// L2: Redis
String key = "product:" + id;
product = redisTemplate.opsForValue().get(key);
if (product != null) {
log.debug("命中 L2 Redis");
caffeineCache.put(id, product);
return product;
}
// L3: DB
log.debug("从 DB 查询");
product = productMapper.selectById(id);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
caffeineCache.put(id, product);
}
return product;
}
}4.2 接口合并
java
@Service
public class OrderDetailService {
public OrderDetailVO getDetail(Long orderId, Long userId) {
// 并行查询
CompletableFuture<Order> orderFuture = CompletableFuture
.supplyAsync(() -> orderMapper.selectById(orderId));
CompletableFuture<List<OrderItem>> itemsFuture = CompletableFuture
.supplyAsync(() -> orderItemMapper.findByOrderId(orderId));
CompletableFuture<Address> addressFuture = CompletableFuture
.supplyAsync(() -> addressMapper.findDefaultByUserId(userId));
CompletableFuture.allOf(orderFuture, itemsFuture, addressFuture).join();
OrderDetailVO vo = new OrderDetailVO();
vo.setOrder(orderFuture.join());
vo.setItems(itemsFuture.join());
vo.setAddress(addressFuture.join());
return vo;
}
}五、监控告警
5.1 关键指标
yaml
应用层:
- QPS
- P99 延迟
- 错误率
- 线程池使用率
- JVM 内存
业务层:
- 下单成功率
- 支付转化率
- 库存命中率
- 客单价5.2 告警规则
yaml
groups:
- name: order_service
rules:
- alert: OrderCreateErrorRateHigh
expr: |
sum(rate(order_create_total{status="fail"}[5m]))
/ sum(rate(order_create_total[5m])) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: 下单失败率超过 5%
- alert: InventoryLow
expr: |
stock_remaining / stock_total < 0.1
for: 5m
labels:
severity: warning
annotations:
summary: SKU {{ $labels.sku_id }} 库存不足 10%六、压测验证
6.1 压测场景
yaml
场景 1 - 读多写少:
- 详情页 QPS: 50000
- 商品列表 QPS: 20000
- 缓存命中率: > 95%
场景 2 - 秒杀:
- 瞬时 QPS: 100000
- 库存准确: 100%
- 订单不超卖
- P99 < 500ms
场景 3 - 混合:
- 浏览+下单+支付: 20000 QPS
- P99 < 1s6.2 压测报告
| 场景 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 商品详情 QPS | 8000 | 50000 | 6.25x |
| 下单 P99 | 800ms | 200ms | -75% |
| 库存命中率 | 70% | 95% | +25% |
| 错误率 | 1% | 0.05% | -95% |
七、灰度发布
java
@Component
@Slf4j
public class GrayscaleFilter implements GlobalFilter {
@Autowired
private NacosConfigService nacosConfigService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String version = nacosConfigService.getConfig("grayscale.version");
String cookieVersion = getCookie(request, "X-Version");
if ("new".equals(version) && !"new".equals(cookieVersion)) {
// 50% 灰度
if (ThreadLocalRandom.current().nextInt(100) < 50) {
return chain.filter(exchange);
}
}
return chain.filter(exchange);
}
}八、本章小结
| 模块 | 关键能力 |
|---|---|
| 架构 | 微服务 + DDD + Saga |
| 性能 | 多级缓存 + 异步 + 合并 |
| 可靠性 | 幂等 + 补偿 + 监控 |
| 可扩展 | 网关 + 注册中心 + 配置中心 |
| 可观测 | Trace + Metric + Log |
动手练习
- 搭建一个包含 3 个服务的简化电商 demo
- 实现订单创建的库存扣减和补偿
- 用 CompletableFuture 优化订单详情接口
- 部署到 K8s,配置 HPA 自动伸缩
下一章:下一章:第 245 章:综合项目实战 - SaaS 平台