Skip to content
第 244 / 250 章架构⏱ 20 分钟阅读

第 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 < 1s

6.2 压测报告

场景优化前优化后改善
商品详情 QPS8000500006.25x
下单 P99800ms200ms-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

动手练习

  1. 搭建一个包含 3 个服务的简化电商 demo
  2. 实现订单创建的库存扣减和补偿
  3. 用 CompletableFuture 优化订单详情接口
  4. 部署到 K8s,配置 HPA 自动伸缩

下一章:下一章:第 245 章:综合项目实战 - SaaS 平台

本站基于 VitePress 构建 · 由 Codebook 团队维护