强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Java 完全指南 / 30 - 实战项目:Spring Boot REST API、微服务、消息队列

30 - 实战项目:Spring Boot REST API、微服务、消息队列

项目结构

order-system/
├── api/                          # API 模块(DTO、接口定义)
│   ├── build.gradle.kts
│   └── src/main/java/com/example/api/
│       ├── dto/
│       │   ├── CreateOrderRequest.java
│       │   ├── OrderResponse.java
│       │   └── ApiResponse.java
│       └── client/
│           └── InventoryClient.java
├── service/                      # 业务模块
│   ├── build.gradle.kts
│   └── src/main/java/com/example/service/
│       ├── Application.java
│       ├── controller/
│       │   └── OrderController.java
│       ├── service/
│       │   ├── OrderService.java
│       │   └── PaymentService.java
│       ├── repository/
│       │   ├── OrderRepository.java
│       │   └── UserRepository.java
│       ├── entity/
│       │   ├── Order.java
│       │   └── User.java
│       ├── event/
│       │   └── OrderEvent.java
│       └── config/
│           ├── SecurityConfig.java
│           └── KafkaConfig.java
├── docker-compose.yml
└── settings.gradle.kts

实体定义

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String orderNo;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @Enumerated(EnumType.STRING)
    private OrderStatus status = OrderStatus.CREATED;

    @Column(nullable = false)
    private BigDecimal totalAmount;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "order")
    private List<OrderItem> items = new ArrayList<>();

    @Version
    private Long version;  // 乐观锁

    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    @PrePersist
    void prePersist() {
        orderNo = generateOrderNo();
        createdAt = LocalDateTime.now();
        updatedAt = createdAt;
    }

    @PreUpdate
    void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

public enum OrderStatus {
    CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}

REST 控制器

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderService orderService;

    @GetMapping
    public Page<OrderResponse> list(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return orderService.findAll(PageRequest.of(page, size))
            .map(OrderResponse::from);
    }

    @GetMapping("/{id}")
    public OrderResponse getById(@PathVariable Long id) {
        return OrderResponse.from(orderService.findById(id));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderResponse create(@Valid @RequestBody CreateOrderRequest request) {
        return OrderResponse.from(orderService.create(request));
    }

    @PatchMapping("/{id}/pay")
    public OrderResponse pay(@PathVariable Long id) {
        return OrderResponse.from(orderService.pay(id));
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void cancel(@PathVariable Long id) {
        orderService.cancel(id);
    }
}

业务服务层

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
    private final OrderRepository orderRepository;
    private final UserRepository userRepository;
    private final InventoryClient inventoryClient;
    private final ApplicationEventPublisher eventPublisher;

    @Transactional
    public Order create(CreateOrderRequest request) {
        // 1. 校验用户
        User user = userRepository.findById(request.getUserId())
            .orElseThrow(() -> new ResourceNotFoundException("用户不存在"));

        // 2. 校验库存(调用库存服务)
        for (OrderItemDTO item : request.getItems()) {
            boolean available = inventoryClient.checkStock(item.getProductId(), item.getQuantity());
            if (!available) {
                throw new BusinessException("OUT_OF_STOCK", "商品库存不足: " + item.getProductId());
            }
        }

        // 3. 创建订单
        Order order = new Order();
        order.setUser(user);
        order.setTotalAmount(calculateTotal(request.getItems()));

        for (OrderItemDTO itemDTO : request.getItems()) {
            OrderItem item = new OrderItem();
            item.setProductId(itemDTO.getProductId());
            item.setQuantity(itemDTO.getQuantity());
            item.setPrice(itemDTO.getPrice());
            order.addItem(item);
        }

        Order saved = orderRepository.save(order);

        // 4. 扣减库存
        request.getItems().forEach(item ->
            inventoryClient.deductStock(item.getProductId(), item.getQuantity()));

        // 5. 发布事件
        eventPublisher.publishEvent(new OrderCreatedEvent(saved.getId(), user.getId()));

        log.info("订单创建成功: orderNo={}, userId={}, amount={}",
                saved.getOrderNo(), user.getId(), saved.getTotalAmount());

        return saved;
    }

    @Transactional
    public Order pay(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("订单不存在"));

        if (order.getStatus() != OrderStatus.CREATED) {
            throw new BusinessException("INVALID_STATUS", "订单状态不允许支付");
        }

        order.setStatus(OrderStatus.PAID);
        Order saved = orderRepository.save(order);

        eventPublisher.publishEvent(new OrderPaidEvent(saved.getId()));

        return saved;
    }

    @Transactional
    public void cancel(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("订单不存在"));

        if (order.getStatus() != OrderStatus.CREATED) {
            throw new BusinessException("INVALID_STATUS", "只有待支付订单可以取消");
        }

        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);

        // 恢复库存
        order.getItems().forEach(item ->
            inventoryClient.restoreStock(item.getProductId(), item.getQuantity()));

        eventPublisher.publishEvent(new OrderCancelledEvent(orderId));
    }
}

Feign 客户端调用微服务

@FeignClient(name = "inventory-service", url = "${inventory.service.url}")
public interface InventoryClient {

    @GetMapping("/api/inventory/{productId}/check")
    boolean checkStock(@PathVariable Long productId, @RequestParam int quantity);

    @PostMapping("/api/inventory/{productId}/deduct")
    void deductStock(@PathVariable Long productId, @RequestParam int quantity);

    @PostMapping("/api/inventory/{productId}/restore")
    void restoreStock(@PathVariable Long productId, @RequestParam int quantity);
}

Kafka 消息队列

// 生产者
@Service
@RequiredArgsConstructor
public class OrderEventPublisher {
    private final KafkaTemplate<String, Object> kafkaTemplate;

    public void publish(OrderCreatedEvent event) {
        kafkaTemplate.send("order-events", "ORDER_CREATED", event);
    }
}

// 消费者
@Component
@Slf4j
public class OrderEventListener {
    @KafkaListener(topics = "order-events", groupId = "notification-group")
    public void handleOrderEvent(String key, OrderEvent event) {
        log.info("收到订单事件: key={}, event={}", key, event);
        switch (event) {
            case OrderCreatedEvent e -> sendOrderConfirmation(e);
            case OrderPaidEvent e -> sendPaymentConfirmation(e);
            case OrderCancelledEvent e -> sendCancellationNotice(e);
        }
    }

    private void sendOrderConfirmation(OrderCreatedEvent event) {
        // 发送短信/邮件通知
    }
}

全局统一响应

public record ApiResponse<T>(int code, String message, T data) {
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data);
    }

    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ApiResponse<Void> handleNotFound(ResourceNotFoundException ex) {
        return ApiResponse.error(404, ex.getMessage());
    }

    @ExceptionHandler(BusinessException.class)
    public ApiResponse<Void> handleBusiness(BusinessException ex) {
        return ApiResponse.error(400, ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleGeneral(Exception ex) {
        log.error("未知错误", ex);
        return ApiResponse.error(500, "服务器内部错误");
    }
}

Docker Compose 部署

# docker-compose.yml
version: '3.8'
services:
  order-service:
    build: ./service
    ports: ["8080:8080"]
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/orders
      SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
    depends_on: [mysql, kafka]

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: orders
    ports: ["3306:3306"]
    volumes:
      - mysql-data:/var/lib/mysql

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    ports: ["9092:9092"]
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092

  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181

volumes:
  mysql-data:

完整业务流程

用户下单 → OrderController.create()
  → OrderService.create()
    → 1. 校验用户
    → 2. 校验库存(调用库存服务)
    → 3. 创建订单(存数据库)
    → 4. 扣减库存(调用库存服务)
    → 5. 发布订单创建事件(Kafka)
      → 通知服务消费事件 → 发送下单通知

用户支付 → OrderController.pay()
  → OrderService.pay()
    → 1. 更新订单状态为 PAID
    → 2. 发布支付成功事件
      → 通知服务 → 发送支付确认
      → 库存服务 → 确认库存扣减

用户取消 → OrderController.cancel()
  → OrderService.cancel()
    → 1. 更新订单状态为 CANCELLED
    → 2. 恢复库存
    → 3. 发布取消事件

⚠️ 注意事项

  1. 分布式事务 — 使用 Saga 模式或最终一致性,不要用 2PC。
  2. 幂等性 — 接口必须支持幂等,防止重复提交。
  3. 限流降级 — 使用 Resilience4j 保护下游服务。
  4. 监控告警 — Prometheus + Grafana 监控关键指标。

💡 技巧

  1. Resilience4j 熔断降级

    @CircuitBreaker(name = "inventory", fallbackMethod = "checkStockFallback")
    @Retry(name = "inventory")
    @RateLimiter(name = "inventory")
    public boolean checkStock(Long productId, int quantity) { ... }
    
  2. API 文档 — SpringDoc/Swagger 自动生成 OpenAPI 文档。

  3. 链路追踪 — Micrometer Tracing + Zipkin。

🏢 业务场景

  • 电商系统: 完整的订单生命周期管理。
  • 分布式架构: 多服务协作,事件驱动。
  • 高可用: 熔断、限流、降级保障系统稳定性。

📖 扩展阅读