3.1 服务调用概述

微服务间通信方式

在微服务架构中,服务间通信是核心问题之一。常见的通信方式包括:

  1. 同步通信

    • HTTP/REST API
    • RPC调用
    • GraphQL
  2. 异步通信

    • 消息队列
    • 事件驱动
    • 发布订阅

Spring Cloud 服务调用组件

┌─────────────────────────────────────────────────────────────────┐
│                    Spring Cloud 服务调用架构                      │
├─────────────────────────────────────────────────────────────────┤
│  服务消费者 (Order Service)                                      │
│  ┌─────────────────┐    ┌─────────────────┐                    │
│  │   OpenFeign     │    │   RestTemplate  │                    │
│  │  (声明式调用)    │    │   (编程式调用)   │                    │
│  └─────────┬───────┘    └─────────┬───────┘                    │
│            │                      │                            │
│            ▼                      ▼                            │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │              LoadBalancer / Ribbon                     │   │
│  │                (客户端负载均衡)                          │   │
│  └─────────────────────┬───────────────────────────────────┘   │
├────────────────────────┼───────────────────────────────────────┤
│                        │ HTTP请求                               │
│                        ▼                                       │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│  │ User Service    │  │ User Service    │  │ User Service    │ │
│  │ Instance 1      │  │ Instance 2      │  │ Instance 3      │ │
│  │ 192.168.1.10    │  │ 192.168.1.11    │  │ 192.168.1.12    │ │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

3.2 OpenFeign 声明式服务调用

OpenFeign 简介

OpenFeign是一个声明式的Web服务客户端,它让编写Web服务客户端变得更加容易。只需要创建一个接口并在接口上添加注解即可。

核心特性

  1. 声明式API:通过注解定义服务调用接口
  2. 集成负载均衡:自动集成Ribbon负载均衡
  3. 支持熔断器:集成Hystrix或Resilience4j
  4. 请求/响应拦截器:支持自定义拦截器
  5. 多种编码器/解码器:支持JSON、XML等格式

添加依赖

pom.xml

<dependencies>
    <!-- OpenFeign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    <!-- LoadBalancer (替代Ribbon) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    
    <!-- 熔断器支持 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>
</dependencies>

启用 OpenFeign

OrderServiceApplication.java

package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * 订单服务启动类
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients  // 启用Feign客户端
public class OrderServiceApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

创建 Feign 客户端

1. 用户服务客户端

UserServiceClient.java

package com.example.orderservice.client;

import com.example.orderservice.dto.User;
import com.example.orderservice.fallback.UserServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * 用户服务Feign客户端
 * 
 * @FeignClient 注解参数说明:
 * - name/value: 服务名称,对应Eureka中注册的服务名
 * - url: 直接指定服务地址(可选)
 * - fallback: 降级处理类
 * - fallbackFactory: 降级处理工厂类
 * - configuration: 自定义配置类
 */
@FeignClient(
    name = "user-service",
    fallback = UserServiceFallback.class,
    configuration = FeignConfig.class
)
public interface UserServiceClient {
    
    /**
     * 根据ID获取用户信息
     */
    @GetMapping("/api/users/{id}")
    User getUserById(@PathVariable("id") Long id);
    
    /**
     * 根据用户名获取用户信息
     */
    @GetMapping("/api/users/username/{username}")
    User getUserByUsername(@PathVariable("username") String username);
    
    /**
     * 创建用户
     */
    @PostMapping("/api/users")
    User createUser(@RequestBody User user);
    
    /**
     * 更新用户信息
     */
    @PutMapping("/api/users/{id}")
    User updateUser(@PathVariable("id") Long id, @RequestBody User user);
    
    /**
     * 检查用户名是否存在
     */
    @GetMapping("/api/users/check/username/{username}")
    Map<String, Boolean> checkUsername(@PathVariable("username") String username);
    
    /**
     * 获取用户统计信息
     */
    @GetMapping("/api/users/stats")
    Map<String, Object> getUserStats();
    
    /**
     * 健康检查
     */
    @GetMapping("/api/users/health")
    Map<String, Object> health();
}

2. 商品服务客户端

ProductServiceClient.java

package com.example.orderservice.client;

import com.example.orderservice.dto.Product;
import com.example.orderservice.fallback.ProductServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
 * 商品服务Feign客户端
 */
@FeignClient(
    name = "product-service",
    fallback = ProductServiceFallback.class
)
public interface ProductServiceClient {
    
    /**
     * 根据ID获取商品信息
     */
    @GetMapping("/api/products/{id}")
    Product getProductById(@PathVariable("id") Long id);
    
    /**
     * 批量获取商品信息
     */
    @PostMapping("/api/products/batch")
    List<Product> getProductsByIds(@RequestBody List<Long> ids);
    
    /**
     * 检查商品库存
     */
    @GetMapping("/api/products/{id}/stock")
    Map<String, Object> checkStock(@PathVariable("id") Long id);
    
    /**
     * 扣减库存
     */
    @PostMapping("/api/products/{id}/stock/reduce")
    boolean reduceStock(@PathVariable("id") Long id, @RequestParam("quantity") Integer quantity);
    
    /**
     * 恢复库存
     */
    @PostMapping("/api/products/{id}/stock/restore")
    boolean restoreStock(@PathVariable("id") Long id, @RequestParam("quantity") Integer quantity);
}

Feign 配置

1. 全局配置

FeignConfig.java

package com.example.orderservice.config;

import feign.Logger;
import feign.Request;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;

import java.util.concurrent.TimeUnit;

/**
 * Feign全局配置
 */
@Configuration
public class FeignConfig {
    
    /**
     * 日志级别配置
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
    /**
     * 请求超时配置
     */
    @Bean
    public Request.Options options() {
        return new Request.Options(
            5000,  // 连接超时时间(毫秒)
            10000  // 读取超时时间(毫秒)
        );
    }
    
    /**
     * 重试配置
     */
    @Bean
    public Retryer retryer() {
        // 最大重试次数为3,初始重试间隔为100ms,最大重试间隔为1s
        return new Retryer.Default(100, 1000, 3);
    }
    
    /**
     * 编码器配置
     */
    @Bean
    public Encoder encoder() {
        return new SpringEncoder(() -> new HttpMessageConvertersConfiguration().messageConverters());
    }
    
    /**
     * 解码器配置
     */
    @Bean
    public Decoder decoder() {
        return new ResponseEntityDecoder(new SpringDecoder(() -> new HttpMessageConvertersConfiguration().messageConverters()));
    }
    
    /**
     * 错误解码器
     */
    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }
}

2. 自定义错误解码器

CustomErrorDecoder.java

package com.example.orderservice.config;

import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

/**
 * 自定义Feign错误解码器
 */
@Component
@Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
    
    private final ErrorDecoder defaultErrorDecoder = new Default();
    
    @Override
    public Exception decode(String methodKey, Response response) {
        log.error("Feign client error: method={}, status={}, reason={}", 
                 methodKey, response.status(), response.reason());
        
        HttpStatus httpStatus = HttpStatus.valueOf(response.status());
        
        switch (httpStatus) {
            case NOT_FOUND:
                return new ResourceNotFoundException("Resource not found: " + methodKey);
            case BAD_REQUEST:
                return new IllegalArgumentException("Bad request: " + methodKey);
            case UNAUTHORIZED:
                return new UnauthorizedException("Unauthorized: " + methodKey);
            case FORBIDDEN:
                return new ForbiddenException("Forbidden: " + methodKey);
            case INTERNAL_SERVER_ERROR:
                return new ServiceUnavailableException("Service unavailable: " + methodKey);
            case SERVICE_UNAVAILABLE:
                return new ServiceUnavailableException("Service unavailable: " + methodKey);
            default:
                return defaultErrorDecoder.decode(methodKey, response);
        }
    }
    
    // 自定义异常类
    public static class ResourceNotFoundException extends RuntimeException {
        public ResourceNotFoundException(String message) {
            super(message);
        }
    }
    
    public static class UnauthorizedException extends RuntimeException {
        public UnauthorizedException(String message) {
            super(message);
        }
    }
    
    public static class ForbiddenException extends RuntimeException {
        public ForbiddenException(String message) {
            super(message);
        }
    }
    
    public static class ServiceUnavailableException extends RuntimeException {
        public ServiceUnavailableException(String message) {
            super(message);
        }
    }
}

3. 请求拦截器

FeignRequestInterceptor.java

package com.example.orderservice.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

/**
 * Feign请求拦截器
 */
@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
    
    @Override
    public void apply(RequestTemplate template) {
        // 添加请求ID用于链路追踪
        String requestId = UUID.randomUUID().toString();
        template.header("X-Request-ID", requestId);
        
        // 传递认证信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            
            // 传递Authorization头
            String authorization = request.getHeader("Authorization");
            if (authorization != null) {
                template.header("Authorization", authorization);
            }
            
            // 传递用户信息
            String userId = request.getHeader("X-User-ID");
            if (userId != null) {
                template.header("X-User-ID", userId);
            }
        }
        
        // 添加服务标识
        template.header("X-Service-Name", "order-service");
        
        log.debug("Feign request: {} {}, headers: {}", 
                 template.method(), template.url(), template.headers());
    }
}

降级处理

1. 用户服务降级

UserServiceFallback.java

package com.example.orderservice.fallback;

import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 用户服务降级处理
 */
@Component
@Slf4j
public class UserServiceFallback implements UserServiceClient {
    
    @Override
    public User getUserById(Long id) {
        log.warn("User service is unavailable, using fallback for getUserById: {}", id);
        
        return User.builder()
            .id(id)
            .username("unknown-user-" + id)
            .email("unknown@example.com")
            .fullName("Unknown User")
            .status(User.UserStatus.INACTIVE)
            .build();
    }
    
    @Override
    public User getUserByUsername(String username) {
        log.warn("User service is unavailable, using fallback for getUserByUsername: {}", username);
        
        return User.builder()
            .id(-1L)
            .username(username)
            .email("unknown@example.com")
            .fullName("Unknown User")
            .status(User.UserStatus.INACTIVE)
            .build();
    }
    
    @Override
    public User createUser(User user) {
        log.error("User service is unavailable, cannot create user: {}", user.getUsername());
        throw new ServiceUnavailableException("用户服务暂时不可用,无法创建用户");
    }
    
    @Override
    public User updateUser(Long id, User user) {
        log.error("User service is unavailable, cannot update user: {}", id);
        throw new ServiceUnavailableException("用户服务暂时不可用,无法更新用户");
    }
    
    @Override
    public Map<String, Boolean> checkUsername(String username) {
        log.warn("User service is unavailable, using fallback for checkUsername: {}", username);
        
        Map<String, Boolean> result = new HashMap<>();
        result.put("exists", false);
        return result;
    }
    
    @Override
    public Map<String, Object> getUserStats() {
        log.warn("User service is unavailable, using fallback for getUserStats");
        
        Map<String, Object> stats = new HashMap<>();
        stats.put("activeUsers", 0);
        stats.put("status", "unavailable");
        return stats;
    }
    
    @Override
    public Map<String, Object> health() {
        log.warn("User service is unavailable, using fallback for health check");
        
        Map<String, Object> health = new HashMap<>();
        health.put("status", "DOWN");
        health.put("service", "user-service");
        health.put("message", "Service is currently unavailable");
        return health;
    }
    
    public static class ServiceUnavailableException extends RuntimeException {
        public ServiceUnavailableException(String message) {
            super(message);
        }
    }
}

2. 降级工厂类

UserServiceFallbackFactory.java

package com.example.orderservice.fallback;

import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 用户服务降级工厂类
 * 可以获取到具体的异常信息
 */
@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceClient> {
    
    @Override
    public UserServiceClient create(Throwable cause) {
        return new UserServiceClient() {
            
            @Override
            public User getUserById(Long id) {
                log.error("Failed to get user by id: {}, cause: {}", id, cause.getMessage());
                
                return User.builder()
                    .id(id)
                    .username("fallback-user-" + id)
                    .email("fallback@example.com")
                    .fullName("Fallback User")
                    .status(User.UserStatus.INACTIVE)
                    .build();
            }
            
            @Override
            public User getUserByUsername(String username) {
                log.error("Failed to get user by username: {}, cause: {}", username, cause.getMessage());
                
                return User.builder()
                    .id(-1L)
                    .username(username)
                    .email("fallback@example.com")
                    .fullName("Fallback User")
                    .status(User.UserStatus.INACTIVE)
                    .build();
            }
            
            @Override
            public User createUser(User user) {
                log.error("Failed to create user: {}, cause: {}", user.getUsername(), cause.getMessage());
                throw new RuntimeException("用户服务不可用: " + cause.getMessage());
            }
            
            @Override
            public User updateUser(Long id, User user) {
                log.error("Failed to update user: {}, cause: {}", id, cause.getMessage());
                throw new RuntimeException("用户服务不可用: " + cause.getMessage());
            }
            
            @Override
            public Map<String, Boolean> checkUsername(String username) {
                log.error("Failed to check username: {}, cause: {}", username, cause.getMessage());
                
                Map<String, Boolean> result = new HashMap<>();
                result.put("exists", false);
                return result;
            }
            
            @Override
            public Map<String, Object> getUserStats() {
                log.error("Failed to get user stats, cause: {}", cause.getMessage());
                
                Map<String, Object> stats = new HashMap<>();
                stats.put("activeUsers", 0);
                stats.put("status", "error");
                stats.put("error", cause.getMessage());
                return stats;
            }
            
            @Override
            public Map<String, Object> health() {
                log.error("Failed to check health, cause: {}", cause.getMessage());
                
                Map<String, Object> health = new HashMap<>();
                health.put("status", "DOWN");
                health.put("service", "user-service");
                health.put("error", cause.getMessage());
                return health;
            }
        };
    }
}

3.3 负载均衡

Spring Cloud LoadBalancer

Spring Cloud LoadBalancer是Spring Cloud自研的负载均衡器,用于替代Netflix Ribbon。

负载均衡算法

  1. 轮询(Round Robin):默认算法
  2. 随机(Random):随机选择实例
  3. 加权轮询(Weighted Round Robin):根据权重分配
  4. 最少连接(Least Connections):选择连接数最少的实例

自定义负载均衡配置

LoadBalancerConfig.java

package com.example.orderservice.config;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
 * 负载均衡配置
 */
@Configuration
public class LoadBalancerConfig {
    
    /**
     * 自定义负载均衡算法
     * 这里使用随机算法
     */
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        
        return new RandomLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name
        );
    }
}

自定义负载均衡算法

WeightedLoadBalancer.java

package com.example.orderservice.loadbalancer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 基于权重的负载均衡算法
 */
@Slf4j
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    
    private final String serviceId;
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    
    public WeightedLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                               String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }
    
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
            .getIfAvailable(NoopServiceInstanceListSupplier::new);
        
        return supplier.get(request)
            .next()
            .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }
    
    private Response<ServiceInstance> processInstanceResponse(
            ServiceInstanceListSupplier supplier,
            List<ServiceInstance> serviceInstances) {
        
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
        
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        
        return serviceInstanceResponse;
    }
    
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            log.warn("No servers available for service: {}", serviceId);
            return new EmptyResponse();
        }
        
        // 基于权重选择实例
        ServiceInstance selectedInstance = selectByWeight(instances);
        
        return new DefaultResponse(selectedInstance);
    }
    
    /**
     * 基于权重选择服务实例
     */
    private ServiceInstance selectByWeight(List<ServiceInstance> instances) {
        // 计算总权重
        int totalWeight = 0;
        for (ServiceInstance instance : instances) {
            int weight = getWeight(instance);
            totalWeight += weight;
        }
        
        if (totalWeight <= 0) {
            // 如果没有权重配置,使用随机算法
            return instances.get(ThreadLocalRandom.current().nextInt(instances.size()));
        }
        
        // 根据权重随机选择
        int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight);
        int currentWeight = 0;
        
        for (ServiceInstance instance : instances) {
            currentWeight += getWeight(instance);
            if (randomWeight < currentWeight) {
                log.debug("Selected instance: {} with weight: {}", 
                         instance.getInstanceId(), getWeight(instance));
                return instance;
            }
        }
        
        // 默认返回第一个实例
        return instances.get(0);
    }
    
    /**
     * 获取实例权重
     */
    private int getWeight(ServiceInstance instance) {
        Map<String, String> metadata = instance.getMetadata();
        String weightStr = metadata.get("weight");
        
        if (weightStr != null) {
            try {
                return Integer.parseInt(weightStr);
            } catch (NumberFormatException e) {
                log.warn("Invalid weight value: {} for instance: {}", 
                        weightStr, instance.getInstanceId());
            }
        }
        
        // 默认权重为1
        return 1;
    }
}

配置负载均衡策略

application.yml

spring:
  cloud:
    loadbalancer:
      # 负载均衡缓存配置
      cache:
        enabled: true
        ttl: 35s
        capacity: 256
      # 健康检查配置
      health-check:
        initial-delay: 0
        interval: 25s
        path:
          user-service: /actuator/health
          product-service: /actuator/health
      # 重试配置
      retry:
        enabled: true
        max-retries-on-same-service-instance: 1
        max-retries-on-next-service-instance: 1
        retry-on-all-operations: false

# Feign配置
feign:
  client:
    config:
      default:
        # 连接超时
        connect-timeout: 5000
        # 读取超时
        read-timeout: 10000
        # 日志级别
        logger-level: full
      # 特定服务配置
      user-service:
        connect-timeout: 3000
        read-timeout: 8000
        logger-level: basic
  # 启用熔断器
  circuitbreaker:
    enabled: true
  # 压缩配置
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true

# 日志配置
logging:
  level:
    com.example.orderservice.client: DEBUG
    org.springframework.cloud.openfeign: DEBUG
    org.springframework.cloud.loadbalancer: DEBUG

3.4 服务调用实战

订单服务实现

1. 订单实体

Order.java

package com.example.orderservice.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 订单实体
 */
@Entity
@Table(name = "orders")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_no", unique = true, nullable = false)
    private String orderNo;
    
    @Column(name = "user_id", nullable = false)
    private Long userId;
    
    @Column(name = "user_name")
    private String userName;
    
    @Column(name = "user_email")
    private String userEmail;
    
    @Column(name = "total_amount", nullable = false)
    private BigDecimal totalAmount;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    @Builder.Default
    private OrderStatus status = OrderStatus.PENDING;
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<OrderItem> items;
    
    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
        if (orderNo == null) {
            orderNo = generateOrderNo();
        }
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
    
    private String generateOrderNo() {
        return "ORD" + System.currentTimeMillis();
    }
    
    /**
     * 订单状态枚举
     */
    public enum OrderStatus {
        PENDING,    // 待处理
        CONFIRMED,  // 已确认
        PAID,       // 已支付
        SHIPPED,    // 已发货
        DELIVERED,  // 已送达
        CANCELLED,  // 已取消
        REFUNDED    // 已退款
    }
}

2. 订单项实体

OrderItem.java

package com.example.orderservice.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.math.BigDecimal;

/**
 * 订单项实体
 */
@Entity
@Table(name = "order_items")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderItem {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id", nullable = false)
    private Order order;
    
    @Column(name = "product_id", nullable = false)
    private Long productId;
    
    @Column(name = "product_name")
    private String productName;
    
    @Column(name = "product_price", nullable = false)
    private BigDecimal productPrice;
    
    @Column(nullable = false)
    private Integer quantity;
    
    @Column(name = "subtotal", nullable = false)
    private BigDecimal subtotal;
}

3. 订单服务实现

OrderServiceImpl.java

package com.example.orderservice.service.impl;

import com.example.orderservice.client.ProductServiceClient;
import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.dto.CreateOrderRequest;
import com.example.orderservice.dto.Product;
import com.example.orderservice.dto.User;
import com.example.orderservice.entity.Order;
import com.example.orderservice.entity.OrderItem;
import com.example.orderservice.repository.OrderRepository;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 订单服务实现类
 */
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class OrderServiceImpl implements OrderService {
    
    private final OrderRepository orderRepository;
    private final UserServiceClient userServiceClient;
    private final ProductServiceClient productServiceClient;
    
    @Override
    public Order createOrder(CreateOrderRequest request) {
        log.info("Creating order for user: {}", request.getUserId());
        
        // 1. 验证用户信息
        User user = validateUser(request.getUserId());
        
        // 2. 验证商品信息和库存
        List<Product> products = validateProducts(request.getItems());
        
        // 3. 扣减库存
        boolean stockReduced = reduceStock(request.getItems());
        if (!stockReduced) {
            throw new RuntimeException("库存扣减失败");
        }
        
        try {
            // 4. 创建订单
            Order order = buildOrder(user, request, products);
            Order savedOrder = orderRepository.save(order);
            
            log.info("Order created successfully: {}", savedOrder.getOrderNo());
            return savedOrder;
            
        } catch (Exception e) {
            // 5. 如果订单创建失败,恢复库存
            log.error("Failed to create order, restoring stock", e);
            restoreStock(request.getItems());
            throw new RuntimeException("订单创建失败: " + e.getMessage());
        }
    }
    
    private User validateUser(Long userId) {
        try {
            User user = userServiceClient.getUserById(userId);
            if (user == null || user.getId() == null) {
                throw new IllegalArgumentException("用户不存在: " + userId);
            }
            
            if (user.getStatus() != User.UserStatus.ACTIVE) {
                throw new IllegalArgumentException("用户状态异常: " + user.getStatus());
            }
            
            return user;
            
        } catch (Exception e) {
            log.error("Failed to validate user: {}", userId, e);
            throw new RuntimeException("用户验证失败: " + e.getMessage());
        }
    }
    
    private List<Product> validateProducts(List<CreateOrderRequest.OrderItemRequest> items) {
        List<Long> productIds = items.stream()
            .map(CreateOrderRequest.OrderItemRequest::getProductId)
            .collect(Collectors.toList());
        
        try {
            List<Product> products = productServiceClient.getProductsByIds(productIds);
            
            if (products.size() != productIds.size()) {
                throw new IllegalArgumentException("部分商品不存在");
            }
            
            // 验证商品状态和库存
            for (CreateOrderRequest.OrderItemRequest item : items) {
                Product product = products.stream()
                    .filter(p -> p.getId().equals(item.getProductId()))
                    .findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("商品不存在: " + item.getProductId()));
                
                if (product.getStatus() != Product.ProductStatus.ACTIVE) {
                    throw new IllegalArgumentException("商品已下架: " + product.getName());
                }
                
                Map<String, Object> stockInfo = productServiceClient.checkStock(product.getId());
                Integer availableStock = (Integer) stockInfo.get("availableStock");
                
                if (availableStock < item.getQuantity()) {
                    throw new IllegalArgumentException("商品库存不足: " + product.getName());
                }
            }
            
            return products;
            
        } catch (Exception e) {
            log.error("Failed to validate products", e);
            throw new RuntimeException("商品验证失败: " + e.getMessage());
        }
    }
    
    private boolean reduceStock(List<CreateOrderRequest.OrderItemRequest> items) {
        try {
            for (CreateOrderRequest.OrderItemRequest item : items) {
                boolean success = productServiceClient.reduceStock(item.getProductId(), item.getQuantity());
                if (!success) {
                    // 如果某个商品库存扣减失败,需要恢复之前已扣减的库存
                    log.error("Failed to reduce stock for product: {}", item.getProductId());
                    return false;
                }
            }
            return true;
            
        } catch (Exception e) {
            log.error("Error reducing stock", e);
            return false;
        }
    }
    
    private void restoreStock(List<CreateOrderRequest.OrderItemRequest> items) {
        try {
            for (CreateOrderRequest.OrderItemRequest item : items) {
                productServiceClient.restoreStock(item.getProductId(), item.getQuantity());
            }
        } catch (Exception e) {
            log.error("Failed to restore stock", e);
        }
    }
    
    private Order buildOrder(User user, CreateOrderRequest request, List<Product> products) {
        List<OrderItem> orderItems = new ArrayList<>();
        BigDecimal totalAmount = BigDecimal.ZERO;
        
        Order order = Order.builder()
            .userId(user.getId())
            .userName(user.getUsername())
            .userEmail(user.getEmail())
            .status(Order.OrderStatus.PENDING)
            .build();
        
        for (CreateOrderRequest.OrderItemRequest itemRequest : request.getItems()) {
            Product product = products.stream()
                .filter(p -> p.getId().equals(itemRequest.getProductId()))
                .findFirst()
                .orElseThrow();
            
            BigDecimal subtotal = product.getPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
            totalAmount = totalAmount.add(subtotal);
            
            OrderItem orderItem = OrderItem.builder()
                .order(order)
                .productId(product.getId())
                .productName(product.getName())
                .productPrice(product.getPrice())
                .quantity(itemRequest.getQuantity())
                .subtotal(subtotal)
                .build();
            
            orderItems.add(orderItem);
        }
        
        order.setItems(orderItems);
        order.setTotalAmount(totalAmount);
        
        return order;
    }
    
    @Override
    @Transactional(readOnly = true)
    public Optional<Order> getOrderById(Long id) {
        return orderRepository.findById(id);
    }
    
    @Override
    @Transactional(readOnly = true)
    public List<Order> getOrdersByUserId(Long userId) {
        return orderRepository.findByUserIdOrderByCreatedAtDesc(userId);
    }
    
    @Override
    public Order updateOrderStatus(Long orderId, Order.OrderStatus status) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new IllegalArgumentException("订单不存在: " + orderId));
        
        order.setStatus(status);
        return orderRepository.save(order);
    }
    
    @Override
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new IllegalArgumentException("订单不存在: " + orderId));
        
        if (order.getStatus() != Order.OrderStatus.PENDING) {
            throw new IllegalStateException("只能取消待处理状态的订单");
        }
        
        // 恢复库存
        List<CreateOrderRequest.OrderItemRequest> items = order.getItems().stream()
            .map(item -> CreateOrderRequest.OrderItemRequest.builder()
                .productId(item.getProductId())
                .quantity(item.getQuantity())
                .build())
            .collect(Collectors.toList());
        
        restoreStock(items);
        
        // 更新订单状态
        order.setStatus(Order.OrderStatus.CANCELLED);
        orderRepository.save(order);
        
        log.info("Order cancelled: {}", order.getOrderNo());
    }
}

4. 订单控制器

OrderController.java

package com.example.orderservice.controller;

import com.example.orderservice.dto.CreateOrderRequest;
import com.example.orderservice.entity.Order;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

/**
 * 订单控制器
 */
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
@Slf4j
@Validated
public class OrderController {
    
    private final OrderService orderService;
    
    /**
     * 创建订单
     */
    @PostMapping
    public ResponseEntity<Order> createOrder(@Valid @RequestBody CreateOrderRequest request) {
        log.info("Creating order for user: {}", request.getUserId());
        
        try {
            Order order = orderService.createOrder(request);
            return ResponseEntity.status(HttpStatus.CREATED).body(order);
        } catch (IllegalArgumentException e) {
            log.error("Invalid request: {}", e.getMessage());
            return ResponseEntity.badRequest().build();
        } catch (Exception e) {
            log.error("Failed to create order", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    /**
     * 根据ID获取订单
     */
    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
        return orderService.getOrderById(id)
            .map(order -> ResponseEntity.ok().body(order))
            .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 获取用户订单列表
     */
    @GetMapping("/user/{userId}")
    public ResponseEntity<List<Order>> getOrdersByUserId(@PathVariable Long userId) {
        List<Order> orders = orderService.getOrdersByUserId(userId);
        return ResponseEntity.ok(orders);
    }
    
    /**
     * 更新订单状态
     */
    @PatchMapping("/{id}/status")
    public ResponseEntity<Order> updateOrderStatus(
            @PathVariable Long id,
            @RequestParam Order.OrderStatus status) {
        
        try {
            Order order = orderService.updateOrderStatus(id, status);
            return ResponseEntity.ok(order);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.notFound().build();
        }
    }
    
    /**
     * 取消订单
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> cancelOrder(@PathVariable Long id) {
        try {
            orderService.cancelOrder(id);
            return ResponseEntity.noContent().build();
        } catch (IllegalArgumentException e) {
            return ResponseEntity.notFound().build();
        } catch (IllegalStateException e) {
            return ResponseEntity.badRequest().build();
        }
    }
}

3.5 总结

本章详细介绍了Spring Cloud中的服务调用与负载均衡:

核心技术

  1. OpenFeign:声明式服务调用,简化服务间通信
  2. LoadBalancer:客户端负载均衡,替代Ribbon
  3. 降级处理:提供服务不可用时的备选方案

最佳实践

  1. 接口设计:遵循RESTful API设计原则
  2. 异常处理:完善的异常处理和降级机制
  3. 超时配置:合理设置连接和读取超时时间
  4. 重试机制:配置适当的重试策略
  5. 监控日志:完善的日志记录和监控

注意事项

  1. 服务依赖:避免循环依赖
  2. 数据一致性:处理分布式事务问题
  3. 性能优化:合理使用缓存和连接池
  4. 安全认证:传递认证信息

在下一章中,我们将学习Spring Cloud Gateway API网关的使用,包括路由配置、过滤器、限流等功能。