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. 发布取消事件
⚠️ 注意事项
- 分布式事务 — 使用 Saga 模式或最终一致性,不要用 2PC。
- 幂等性 — 接口必须支持幂等,防止重复提交。
- 限流降级 — 使用 Resilience4j 保护下游服务。
- 监控告警 — Prometheus + Grafana 监控关键指标。
💡 技巧
Resilience4j 熔断降级:
@CircuitBreaker(name = "inventory", fallbackMethod = "checkStockFallback") @Retry(name = "inventory") @RateLimiter(name = "inventory") public boolean checkStock(Long productId, int quantity) { ... }API 文档 — SpringDoc/Swagger 自动生成 OpenAPI 文档。
链路追踪 — Micrometer Tracing + Zipkin。
🏢 业务场景
- 电商系统: 完整的订单生命周期管理。
- 分布式架构: 多服务协作,事件驱动。
- 高可用: 熔断、限流、降级保障系统稳定性。