Spring Cloud Feign 进阶详解:契约测试、负载均衡、文件上传与原生API

1. 与 Spring Cloud Contract 集成进行消费者驱动契约测试

1.1 消费者驱动契约测试(CDC)基础理论

1.1.1 什么是CDC

消费者驱动契约测试是一种微服务测试方法,通过定义服务间的契约来确保API的兼容性。它解决了微服务架构中服务独立部署和演进的难题。

核心概念:

  • 生产者(Provider): 提供API服务的应用程序

  • 消费者(Consumer): 使用API服务的应用程序

  • 契约(Contract): 描述API请求和响应格式的规范文件

  • 存根(Stub): 基于契约生成的模拟服务,用于消费者测试

1.2 Spring Cloud Contract 架构解析

xml

复制代码
<!-- Maven 依赖配置 -->
<dependencies>
    <!-- 生产者端依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-contract-verifier</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- 消费者端依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- 可选:使用WireMock作为存根服务器 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
1.3 生产者端配置与实现
1.3.1 基础配置

yaml

复制代码
# application.yml
spring:
  cloud:
    contract:
      verifier:
        # 用于生成测试的基础类
        base-class-for-tests: com.example.contract.BaseTestClass
        # 生成的测试包名
        base-package-for-tests: com.example.contract
      # 存储契约的目录
      contractsDslDir: src/test/resources/contracts
      # 生成的测试输出目录
      generatedTestSourcesDir: ${project.build.directory}/generated-test-sources/contracts
1.3.2 契约定义示例

groovy

复制代码
// src/test/resources/contracts/userService/shouldReturnUser.groovy
package contracts.userService

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    description "根据用户ID查询用户信息"
    
    request {
        method GET()
        url "/api/users/123"
        headers {
            contentType(applicationJson())
        }
    }
    
    response {
        status 200
        headers {
            contentType(applicationJson())
        }
        body([
            id: 123,
            name: "张三",
            email: "zhangsan@example.com",
            age: value(consumer(25), producer(25))
        ])
    }
}
1.3.3 高级契约特性

groovy

复制代码
// 复杂的契约示例
Contract.make {
    description "创建新用户"
    
    request {
        method POST()
        url "/api/users"
        headers {
            contentType(applicationJson())
            header("X-Request-ID", regex('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'))
        }
        body([
            name: $(consumer(regex('[a-zA-Z\\s]{3,50}')), producer('李四')),
            email: $(consumer(regex(email())), producer('lisi@example.com')),
            age: $(consumer(optional(18)), producer(18))
        ])
    }
    
    response {
        status 201
        headers {
            contentType(applicationJson())
            header('Location', $(consumer('/api/users/456'), producer(regex('/api/users/[0-9]+'))))
        }
        body([
            id: $(producer(456), consumer(anyNumber())),
            name: fromRequest().body('$.name'),
            email: fromRequest().body('$.email'),
            createdAt: $(producer(regex('[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}')), consumer(anyIso8601WithOffset()))
        ])
    }
}
1.3.4 生成并运行生产者测试

java

复制代码
// 生成的测试基类
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@Import(ContractVerifierConfig.class)
public abstract class BaseTestClass {
    
    @Autowired
    private WebApplicationContext context;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Before
    public void setup() {
        RestAssuredMockMvc.webAppContextSetup(this.context);
    }
    
    // 可选:设置数据库状态
    @Before
    public void setupDb() {
        // 准备测试数据
    }
}

// 生成的测试类
public class UserServiceContractTest extends BaseTestClass {
    
    @Test
    public void validate_shouldReturnUser() throws Exception {
        // 自动生成的测试代码
        given()
            .when()
                .get("/api/users/123")
            .then()
                .statusCode(200)
                .body("id", equalTo(123))
                .body("name", equalTo("张三"))
                .body("email", equalTo("zhangsan@example.com"));
    }
}
1.4 消费者端配置与实现
1.4.1 消费者端配置

yaml

复制代码
# application-test.yml
spring:
  cloud:
    contract:
      stubrunner:
        ids: 'com.example:user-service:+:stubs:8080'
        repositoryRoot: http://artifactory.example.com/libs-release-local
        stubs-per-consumer: true
        consumer-name: 'order-service'
1.4.2 消费者测试示例

java

复制代码
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureStubRunner(
    ids = "com.example:user-service:+:stubs:8080",
    stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
@ActiveProfiles("test")
public class UserServiceConsumerTest {
    
    @Autowired
    private UserServiceClient userServiceClient;
    
    @Test
    public void shouldGetUserById() {
        // 使用存根服务器进行测试
        User user = userServiceClient.getUserById(123L);
        
        assertThat(user).isNotNull();
        assertThat(user.getId()).isEqualTo(123L);
        assertThat(user.getName()).isEqualTo("张三");
        assertThat(user.getEmail()).isEqualTo("zhangsan@example.com");
    }
    
    @Test
    public void shouldCreateUser() {
        CreateUserRequest request = new CreateUserRequest();
        request.setName("李四");
        request.setEmail("lisi@example.com");
        request.setAge(25);
        
        User createdUser = userServiceClient.createUser(request);
        
        assertThat(createdUser).isNotNull();
        assertThat(createdUser.getId()).isNotNull();
        assertThat(createdUser.getName()).isEqualTo("李四");
        assertThat(createdUser.getEmail()).isEqualTo("lisi@example.com");
    }
}
1.5 进阶特性与最佳实践
1.5.1 契约版本管理

groovy

复制代码
// 版本化的契约定义
package contracts.userService.v1

Contract.make {
    name "user_service_v1"
    
    request {
        method GET()
        urlPath "/api/v1/users/123"
    }
    
    response {
        // v1响应格式
    }
}

// v2契约
package contracts.userService.v2

Contract.make {
    name "user_service_v2"
    
    request {
        method GET()
        urlPath "/api/v2/users/123"
    }
    
    response {
        // v2响应格式(扩展字段)
    }
}
1.5.2 使用Contract DSL扩展

groovy

复制代码
// 自定义DSL扩展
class CustomContractDsl extends Contract {
    
    // 自定义方法
    static Map<String, Object> paginatedResponse(Map params = [:]) {
        return [
            content: params.content ?: [],
            page: params.page ?: 0,
            size: params.size ?: 20,
            totalElements: params.totalElements ?: 0,
            totalPages: params.totalPages ?: 1
        ]
    }
}

// 在契约中使用自定义DSL
Contract.make {
    request {
        method GET()
        url "/api/users"
        queryParameters {
            parameter("page", "0")
            parameter("size", "20")
        }
    }
    
    response {
        status 200
        body(
            CustomContractDsl.paginatedResponse(
                content: [
                    [id: 1, name: "User1"],
                    [id: 2, name: "User2"]
                ],
                totalElements: 50
            )
        )
    }
}
1.5.3 异步消息契约

groovy

复制代码
// 消息契约示例
Contract.make {
    description "用户创建事件"
    
    // 输入消息(消费者发送)
    input {
        // 触发消息
        triggeredBy('userCreated()')
    }
    
    // 输出消息(生产者发送)
    outputMessage {
        // 发送到哪个目的地
        sentTo('user-created-events')
        
        // 消息体
        body([
            userId: $(producer(anyUuid()), consumer('123e4567-e89b-12d3-a456-426614174000')),
            userName: $(producer(anyNonBlankString()), consumer('张三')),
            eventType: 'USER_CREATED',
            timestamp: $(producer(anyIso8601WithOffset()), consumer('2024-01-01T00:00:00Z'))
        ])
        
        // 消息头
        headers {
            header('contentType', applicationJson())
            header('messageId', $(anyUuid()))
        }
    }
}
1.5.4 集成测试策略

java

复制代码
// 分层测试策略
public class ContractTestingStrategy {
    
    // 1. 单元测试层 - 快速反馈
    @Test
    public void unitTest() {
        // 测试业务逻辑,不涉及外部依赖
    }
    
    // 2. 契约测试层 - 验证接口兼容性
    @Test
    public void contractTest() {
        // 使用Spring Cloud Contract验证接口契约
    }
    
    // 3. 集成测试层 - 端到端验证
    @Test
    public void integrationTest() {
        // 使用WireMock或Testcontainers进行集成测试
    }
    
    // 4. 组件测试层 - 验证组件功能
    @Test
    public void componentTest() {
        // 测试完整的业务场景
    }
}

// 使用Testcontainers进行集成测试
@Testcontainers
@SpringBootTest
public class IntegrationTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13");
    
    @Container
    @ServiceConnection
    static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine");
    
    @Test
    public void testCompleteFlow() {
        // 使用真实容器进行测试
    }
}
1.5.5 CI/CD流水线集成

yaml

复制代码
# GitLab CI配置示例
stages:
  - build
  - contract-test
  - integration-test
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"

cache:
  paths:
    - .m2/repository/

build:
  stage: build
  script:
    - mvn clean compile -DskipTests

contract-tests-producer:
  stage: contract-test
  script:
    - mvn clean test -Dtest=*ContractTest -Pcontract-tests
    - mvn spring-cloud-contract:convert
    - mvn spring-cloud-contract:generateStubs
  artifacts:
    paths:
      - target/stubs/
    expire_in: 1 week

contract-tests-consumer:
  stage: contract-test
  script:
    - mvn clean test -Dtest=*ConsumerTest -Pcontract-tests
  dependencies:
    - contract-tests-producer

integration-tests:
  stage: integration-test
  services:
    - postgres:13
    - redis:7-alpine
  script:
    - mvn verify -Pintegration-tests
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy-staging:
  stage: deploy
  script:
    - mvn deploy -DskipTests -Pstaging
  environment:
    name: staging
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
1.5.6 监控与报告

java

复制代码
// 契约测试报告扩展
@Configuration
public class ContractReportingConfig {
    
    @Bean
    public ContractVerifierListener contractVerifierListener() {
        return new ContractVerifierListener() {
            @Override
            public void after(File contractFile, ContractVerifierConfig config) {
                // 生成自定义报告
                generateCustomReport(contractFile);
            }
            
            @Override
            public void afterAll() {
                // 生成聚合报告
                generateAggregateReport();
            }
        };
    }
    
    private void generateCustomReport(File contractFile) {
        // 实现自定义报告生成逻辑
        Map<String, Object> reportData = new HashMap<>();
        reportData.put("contractName", contractFile.getName());
        reportData.put("testTimestamp", Instant.now());
        reportData.put("status", "SUCCESS");
        
        // 保存到数据库或发送到监控系统
        saveToMonitoringSystem(reportData);
    }
}

// 使用Micrometer监控契约测试
@Configuration
public class ContractMetricsConfig {
    
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> registry.config()
            .commonTags("application", "contract-verifier");
    }
}

2. 更深入的负载均衡器(Spring Cloud LoadBalancer)配置示例

2.1 Spring Cloud LoadBalancer 架构深度解析

2.1.1 核心组件

java

复制代码
// LoadBalancer的核心接口
public interface ReactorLoadBalancer<T> {
    Mono<Response<T>> choose(Request request);
    
    default Mono<Response<T>> choose() {
        return choose(Request.EMPTY);
    }
}

// 服务实例选择器
public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {
    String getServiceId();
    
    default Flux<List<ServiceInstance>> get() {
        return get(Request.EMPTY);
    }
    
    Flux<List<ServiceInstance>> get(Request request);
}

// 负载均衡客户端
public interface LoadBalancerClient {
    ServiceInstance choose(String serviceId, Request request);
    
    <T> T execute(String serviceId, ServiceInstance instance, 
                  LoadBalancerRequest<T> request) throws IOException;
    
    URI reconstructURI(ServiceInstance instance, URI original);
}
2.2 基础配置详解
2.2.1 配置文件方式

yaml

复制代码
# application.yml
spring:
  cloud:
    loadbalancer:
      # 全局配置
      cache:
        enabled: true
        ttl: 30s
        capacity: 256
      
      # 服务特定配置
      clients:
        user-service:
          # 负载均衡算法
          nfloadbalancer:
            rule: round-robin
          # 健康检查配置
          health-check:
            initial-delay: 5s
            interval: 30s
            enabled: true
          # 重试配置
          retry:
            enabled: true
            max-attempts: 3
            backoff:
              initial-interval: 100ms
              max-interval: 1s
              multiplier: 2.0
        
        order-service:
          nfloadbalancer:
            rule: random
          health-check:
            enabled: false
2.2.2 Java配置方式

java

复制代码
@Configuration
@LoadBalancerClient(
    name = "user-service",
    configuration = UserServiceLoadBalancerConfig.class
)
public class UserServiceLoadBalancerConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name
        );
    }
    
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        
        return ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withCaching()
                .withHealthChecks()
                .build(context);
    }
    
    @Bean
    public LoadBalancerProperties loadBalancerProperties() {
        LoadBalancerProperties properties = new LoadBalancerProperties();
        properties.setStickySession(true);
        properties.setStickySessionCookieName("LB_SESSION_ID");
        return properties;
    }
}
2.3 高级负载均衡策略实现
2.3.1 自定义权重负载均衡器

java

复制代码
@Slf4j
public class WeightedLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
    
    private final String serviceId;
    private final ObjectProvider<ServiceInstanceListSupplier> supplierProvider;
    private final WeightedLoadBalancerConfig config;
    
    // 权重配置
    private final Map<String, Integer> instanceWeights = new ConcurrentHashMap<>();
    private final Random random = new Random();
    
    public WeightedLoadBalancer(
            ObjectProvider<ServiceInstanceListSupplier> supplierProvider,
            String serviceId,
            WeightedLoadBalancerConfig config) {
        this.supplierProvider = supplierProvider;
        this.serviceId = serviceId;
        this.config = config;
    }
    
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = supplierProvider.getIfAvailable();
        
        return supplier.get(request).next()
            .map(instances -> processInstanceResponse(instances, request))
            .doOnError(e -> log.error("Error choosing instance", e));
    }
    
    private Response<ServiceInstance> processInstanceResponse(
            List<ServiceInstance> instances, Request request) {
        
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + serviceId);
            return new EmptyResponse();
        }
        
        // 应用权重逻辑
        ServiceInstance selectedInstance = selectByWeight(instances, request);
        
        // 记录选择日志
        if (log.isDebugEnabled()) {
            log.debug("Selected instance {} for service {}", 
                     selectedInstance.getInstanceId(), serviceId);
        }
        
        return new DefaultResponse(selectedInstance);
    }
    
    private ServiceInstance selectByWeight(
            List<ServiceInstance> instances, Request request) {
        
        // 1. 获取实例权重(可从元数据或外部配置读取)
        List<WeightedInstance> weightedInstances = instances.stream()
            .map(instance -> {
                int weight = getInstanceWeight(instance);
                return new WeightedInstance(instance, weight);
            })
            .collect(Collectors.toList());
        
        // 2. 计算总权重
        int totalWeight = weightedInstances.stream()
            .mapToInt(WeightedInstance::getWeight)
            .sum();
        
        // 3. 根据权重随机选择
        int randomWeight = random.nextInt(totalWeight);
        int currentWeight = 0;
        
        for (WeightedInstance weightedInstance : weightedInstances) {
            currentWeight += weightedInstance.getWeight();
            if (randomWeight < currentWeight) {
                return weightedInstance.getInstance();
            }
        }
        
        // 回退到第一个实例
        return instances.get(0);
    }
    
    private int getInstanceWeight(ServiceInstance instance) {
        // 从实例元数据获取权重
        String weightStr = instance.getMetadata().get("weight");
        if (weightStr != null) {
            try {
                return Integer.parseInt(weightStr);
            } catch (NumberFormatException e) {
                log.warn("Invalid weight value for instance {}: {}", 
                        instance.getInstanceId(), weightStr);
            }
        }
        
        // 默认权重
        return config.getDefaultWeight();
    }
    
    // 权重实例包装类
    @Data
    @AllArgsConstructor
    private static class WeightedInstance {
        private ServiceInstance instance;
        private int weight;
    }
}

// 权重负载均衡器配置
@Configuration
@EnableConfigurationProperties(WeightedLoadBalancerConfig.class)
public class WeightedLoadBalancerConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory,
            WeightedLoadBalancerConfig config) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new WeightedLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name,
            config
        );
    }
}

// 权重配置属性
@Data
@ConfigurationProperties("spring.cloud.loadbalancer.weighted")
public class WeightedLoadBalancerConfig {
    private int defaultWeight = 100;
    private boolean enabled = true;
    private Map<String, Integer> serviceWeights = new HashMap<>();
}
2.3.2 基于响应时间的自适应负载均衡

java

复制代码
public class AdaptiveLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
    
    // 响应时间统计
    private final ConcurrentHashMap<String, ResponseTimeStats> statsMap = 
        new ConcurrentHashMap<>();
    
    // 冷却时间窗口
    private final long cooldownWindowMs = 60000; // 1分钟
    private final ConcurrentHashMap<String, Long> cooldownMap = 
        new ConcurrentHashMap<>();
    
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = supplierProvider.getIfAvailable();
        
        return supplier.get(request).next()
            .map(instances -> {
                // 过滤掉冷却中的实例
                List<ServiceInstance> availableInstances = instances.stream()
                    .filter(instance -> !isInCooldown(instance.getInstanceId()))
                    .collect(Collectors.toList());
                
                if (availableInstances.isEmpty()) {
                    return new EmptyResponse();
                }
                
                // 根据响应时间选择实例
                ServiceInstance selected = selectByResponseTime(availableInstances);
                return new DefaultResponse(selected);
            });
    }
    
    private ServiceInstance selectByResponseTime(List<ServiceInstance> instances) {
        // 获取各实例的响应时间统计
        List<ScoredInstance> scoredInstances = instances.stream()
            .map(instance -> {
                double score = calculateScore(instance.getInstanceId());
                return new ScoredInstance(instance, score);
            })
            .sorted(Comparator.comparingDouble(ScoredInstance::getScore).reversed())
            .collect(Collectors.toList());
        
        // 使用softmax选择
        return selectBySoftmax(scoredInstances);
    }
    
    private double calculateScore(String instanceId) {
        ResponseTimeStats stats = statsMap.get(instanceId);
        if (stats == null || stats.getRequestCount() < 10) {
            return 1.0; // 新实例默认分数
        }
        
        // 计算分数:响应时间越短,分数越高
        double avgResponseTime = stats.getAverageResponseTime();
        double successRate = stats.getSuccessRate();
        
        // 加权计算分数
        return (successRate * 0.7) + (1.0 / Math.log1p(avgResponseTime) * 0.3);
    }
    
    // 记录响应时间
    public void recordResponseTime(String instanceId, long responseTime, boolean success) {
        statsMap.compute(instanceId, (key, stats) -> {
            if (stats == null) {
                stats = new ResponseTimeStats();
            }
            stats.record(responseTime, success);
            return stats;
        });
    }
    
    // 标记实例进入冷却
    public void markForCooldown(String instanceId) {
        cooldownMap.put(instanceId, System.currentTimeMillis());
    }
    
    private boolean isInCooldown(String instanceId) {
        Long cooldownStart = cooldownMap.get(instanceId);
        if (cooldownStart == null) {
            return false;
        }
        
        long elapsed = System.currentTimeMillis() - cooldownStart;
        if (elapsed > cooldownWindowMs) {
            cooldownMap.remove(instanceId);
            return false;
        }
        
        return true;
    }
    
    // 响应时间统计类
    @Data
    private static class ResponseTimeStats {
        private double averageResponseTime = 0;
        private int requestCount = 0;
        private int successCount = 0;
        
        public synchronized void record(long responseTime, boolean success) {
            // 指数加权移动平均
            averageResponseTime = 0.9 * averageResponseTime + 0.1 * responseTime;
            requestCount++;
            if (success) {
                successCount++;
            }
        }
        
        public double getSuccessRate() {
            return requestCount == 0 ? 1.0 : (double) successCount / requestCount;
        }
    }
}
2.4 区域感知负载均衡
2.4.1 区域感知配置

java

复制代码
@Configuration
public class ZoneAwareLoadBalancerConfig {
    
    @Bean
    public ServiceInstanceListSupplier zoneAwareServiceInstanceListSupplier(
            ConfigurableApplicationContext context,
            DiscoveryClient discoveryClient,
            @Value("${spring.cloud.loadbalancer.zone:default}") String currentZone) {
        
        return ServiceInstanceListSupplier.builder()
            .withDiscoveryClient()
            .withCaching()
            .withZoneAffinity(currentZone) // 区域亲和性
            .withHealthChecks()
            .withRequestBasedStickySession() // 基于请求的粘性会话
            .build(context);
    }
    
    @Bean
    public ReactorLoadBalancer<ServiceInstance> zoneAwareLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        
        return new ZoneAwareLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name,
            loadBalancerClientFactory
        );
    }
}

// 区域感知负载均衡器实现
public class ZoneAwareLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
    
    private final String serviceId;
    private final ObjectProvider<ServiceInstanceListSupplier> supplierProvider;
    private final LoadBalancerClientFactory clientFactory;
    
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        return supplierProvider.getIfAvailable()
            .get(request)
            .next()
            .map(instances -> {
                // 按区域分组
                Map<String, List<ServiceInstance>> zoneInstances = instances.stream()
                    .collect(Collectors.groupingBy(
                        instance -> instance.getMetadata().getOrDefault("zone", "default")
                    ));
                
                // 优先选择本区域实例
                String localZone = getLocalZone();
                List<ServiceInstance> localInstances = zoneInstances.getOrDefault(localZone, 
                    Collections.emptyList());
                
                if (!localInstances.isEmpty()) {
                    // 本区域有可用实例
                    return chooseFromZone(localInstances, request);
                }
                
                // 本区域无实例,选择其他区域
                for (Map.Entry<String, List<ServiceInstance>> entry : zoneInstances.entrySet()) {
                    if (!entry.getValue().isEmpty()) {
                        return chooseFromZone(entry.getValue(), request);
                    }
                }
                
                return new EmptyResponse();
            });
    }
    
    private Response<ServiceInstance> chooseFromZone(
            List<ServiceInstance> instances, Request request) {
        
        // 应用负载均衡策略(如轮询、随机等)
        LoadBalancerProperties properties = clientFactory.getProperties(serviceId);
        String rule = properties.getRule();
        
        switch (rule) {
            case "round-robin":
                return roundRobinChoose(instances);
            case "random":
                return randomChoose(instances);
            case "weighted":
                return weightedChoose(instances);
            default:
                return roundRobinChoose(instances);
        }
    }
    
    private String getLocalZone() {
        // 从配置或环境变量获取当前区域
        return System.getenv("ZONE") != null ? 
               System.getenv("ZONE") : "default";
    }
}
2.4.2 跨区域故障转移

yaml

复制代码
# application.yml
spring:
  cloud:
    loadbalancer:
      zone-aware:
        enabled: true
        # 首选区域
        preferred-zones: 
          - ${ZONE:default}
          - zone-b
          - zone-c
        # 跨区域故障转移策略
        failover:
          enabled: true
          # 最大跨区域调用比例(0.0-1.0)
          max-cross-zone-ratio: 0.3
          # 是否启用延迟感知
          latency-aware: true
          # 最大额外延迟容忍度(毫秒)
          max-latency-tolerance: 100
2.5 负载均衡与熔断器集成
2.5.1 Resilience4j集成

java

复制代码
@Configuration
public class LoadBalancerResilienceConfig {
    
    @Bean
    public Customizer<ReactiveResilience4JCircuitBreakerFactory> circuitBreakerFactoryCustomizer() {
        return factory -> factory.configureDefault(id -> 
            Resilience4JConfigBuilder.of(id)
                .circuitBreakerConfig(CircuitBreakerConfig.custom()
                    .slidingWindowSize(100)
                    .failureRateThreshold(50)
                    .waitDurationInOpenState(Duration.ofSeconds(30))
                    .permittedNumberOfCallsInHalfOpenState(10)
                    .slowCallDurationThreshold(Duration.ofSeconds(2))
                    .slowCallRateThreshold(50)
                    .build())
                .timeLimiterConfig(TimeLimiterConfig.custom()
                    .timeoutDuration(Duration.ofSeconds(5))
                    .build())
                .build());
    }
    
    @Bean
    public LoadBalancedExchangeFilterFunction loadBalancedExchangeFilterFunction(
            LoadBalancerClientFactory clientFactory,
            ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory) {
        
        return new LoadBalancedExchangeFilterFunction(clientFactory,
            new LoadBalancerRetryPolicy() {
                @Override
                public boolean retryOnSameServiceInstance(
                        LoadBalancerRetryContext context) {
                    // 在相同实例上重试的条件
                    return context.getRetryCount() < 2 && 
                           isRetryableException(context.getException());
                }
                
                @Override
                public boolean retryOnNextServiceInstance(
                        LoadBalancerRetryContext context) {
                    // 切换到下一个实例重试的条件
                    return context.getRetryCount() < 3;
                }
                
                private boolean isRetryableException(Throwable exception) {
                    return exception instanceof IOException || 
                           exception instanceof TimeoutException ||
                           exception instanceof SocketException;
                }
            },
            circuitBreakerFactory);
    }
}

// 使用负载均衡的WebClient
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder(
        LoadBalancedExchangeFilterFunction lbFunction) {
    
    return WebClient.builder()
        .filter(lbFunction)
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .clientConnector(new ReactorClientHttpConnector(
            HttpClient.create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .doOnConnected(conn -> 
                    conn.addHandlerLast(new ReadTimeoutHandler(10))
                        .addHandlerLast(new WriteTimeoutHandler(10))
                )
        ));
}
2.5.2 请求级别的负载均衡策略

java

复制代码
@Aspect
@Component
public class RequestAwareLoadBalancerAspect {
    
    private final ThreadLocal<RequestContext> requestContext = 
        new ThreadLocal<>();
    
    @Before("@annotation(RequestAwareLB)")
    public void captureRequestContext(JoinPoint joinPoint) {
        HttpServletRequest request = 
            ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest();
        
        RequestContext context = new RequestContext();
        context.setRequestId(request.getHeader("X-Request-ID"));
        context.setUserId(request.getHeader("X-User-ID"));
        context.setClientIp(request.getRemoteAddr());
        context.setRequestPath(request.getRequestURI());
        
        requestContext.set(context);
    }
    
    @After("@annotation(RequestAwareLB)")
    public void clearRequestContext() {
        requestContext.remove();
    }
    
    @Component
    public static class RequestAwareServiceInstanceListSupplier 
            implements ServiceInstanceListSupplier {
        
        @Override
        public Flux<List<ServiceInstance>> get(Request request) {
            return delegate.get(request)
                .map(instances -> filterInstancesByRequest(instances, request));
        }
        
        private List<ServiceInstance> filterInstancesByRequest(
                List<ServiceInstance> instances, Request request) {
            
            RequestContext context = requestContext.get();
            if (context == null) {
                return instances;
            }
            
            // 基于请求上下文过滤实例
            return instances.stream()
                .filter(instance -> 
                    matchesRequestRequirements(instance, context))
                .collect(Collectors.toList());
        }
        
        private boolean matchesRequestRequirements(
                ServiceInstance instance, RequestContext context) {
            
            Map<String, String> metadata = instance.getMetadata();
            
            // 示例:根据用户ID路由到特定实例组
            if (context.getUserId() != null) {
                String userGroup = getUserGroup(context.getUserId());
                String instanceGroup = metadata.get("user-group");
                return userGroup.equals(instanceGroup);
            }
            
            return true;
        }
        
        private String getUserGroup(String userId) {
            // 根据业务逻辑确定用户分组
            int hash = userId.hashCode();
            return hash % 2 == 0 ? "group-a" : "group-b";
        }
    }
}

// 自定义请求上下文
@Data
public class RequestContext {
    private String requestId;
    private String userId;
    private String clientIp;
    private String requestPath;
    private Map<String, String> customAttributes = new HashMap<>();
}
2.6 监控与指标收集
2.6.1 Micrometer指标集成

java

复制代码
@Configuration
public class LoadBalancerMetricsConfig {
    
    @Bean
    public LoadBalancerMetricsRecorder loadBalancerMetricsRecorder(
            MeterRegistry meterRegistry) {
        
        return new LoadBalancerMetricsRecorder(meterRegistry);
    }
    
    @Bean
    public LoadBalancerPropertiesCustomizer metricsPropertiesCustomizer(
            LoadBalancerMetricsRecorder recorder) {
        
        return properties -> {
            properties.getMetrics().setEnabled(true);
            properties.getMetrics().setRecorder(recorder);
        };
    }
}

@Component
public class LoadBalancerMetricsRecorder {
    
    private final MeterRegistry meterRegistry;
    private final Map<String, Timer> requestTimers = new ConcurrentHashMap<>();
    private final Map<String, Counter> errorCounters = new ConcurrentHashMap<>();
    
    public LoadBalancerMetricsRecorder(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    public void recordRequest(String serviceId, String instanceId, 
                             long duration, boolean success) {
        
        String timerName = "loadbalancer.requests.duration";
        Timer timer = requestTimers.computeIfAbsent(serviceId + "." + instanceId, 
            key -> Timer.builder(timerName)
                .tag("service", serviceId)
                .tag("instance", instanceId)
                .description("Load balancer request duration")
                .register(meterRegistry));
        
        timer.record(duration, TimeUnit.MILLISECONDS);
        
        // 记录成功/失败计数
        String counterName = success ? 
            "loadbalancer.requests.success" : "loadbalancer.requests.failed";
        
        Counter counter = errorCounters.computeIfAbsent(counterName,
            key -> Counter.builder(counterName)
                .tag("service", serviceId)
                .tag("instance", instanceId)
                .description("Load balancer request outcomes")
                .register(meterRegistry));
        
        counter.increment();
    }
    
    public void recordInstanceStatus(String serviceId, String instanceId, 
                                    boolean healthy) {
        
        Gauge.builder("loadbalancer.instance.health", 
                () -> healthy ? 1 : 0)
            .tag("service", serviceId)
            .tag("instance", instanceId)
            .description("Service instance health status")
            .register(meterRegistry);
    }
}
2.6.2 Grafana监控仪表板配置

json

复制代码
{
  "dashboard": {
    "title": "Spring Cloud LoadBalancer Metrics",
    "panels": [
      {
        "title": "Request Duration by Service",
        "type": "graph",
        "targets": [{
          "expr": "rate(loadbalancer_requests_duration_seconds_sum[5m]) / rate(loadbalancer_requests_duration_seconds_count[5m])",
          "legendFormat": "{{service}}"
        }]
      },
      {
        "title": "Instance Health Status",
        "type": "heatmap",
        "targets": [{
          "expr": "loadbalancer_instance_health",
          "legendFormat": "{{instance}}"
        }]
      },
      {
        "title": "Error Rate",
        "type": "singlestat",
        "targets": [{
          "expr": "rate(loadbalancer_requests_failed_total[5m]) / rate(loadbalancer_requests_total[5m]) * 100",
          "format": "percent"
        }]
      }
    ]
  }
}

3. 使用 Feign 进行文件上传

3.1 Feign 文件上传基础

3.1.1 依赖配置

xml

复制代码
<!-- pom.xml 依赖 -->
<dependencies>
    <!-- Spring Cloud OpenFeign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    <!-- 文件上传支持 -->
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form</artifactId>
        <version>3.8.0</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form-spring</artifactId>
        <version>3.8.0</version>
    </dependency>
    
    <!-- Spring Web(提供Multipart支持) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 可选:Apache HttpClient -->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
    </dependency>
</dependencies>
3.1.2 基础配置类

java

复制代码
@Configuration
@EnableFeignClients
public class FeignFileUploadConfig {
    
    @Bean
    public Encoder feignFormEncoder() {
        return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<>() {
            @Override
            public Object getObject() {
                return new HttpMessageConverters(
                    new ByteArrayHttpMessageConverter(),
                    new StringHttpMessageConverter(),
                    new ResourceHttpMessageConverter(),
                    new SourceHttpMessageConverter<>(),
                    new AllEncompassingFormHttpMessageConverter()
                );
            }
        }));
    }
    
    @Bean
    public feign.Logger.Level feignLoggerLevel() {
        return feign.Logger.Level.FULL;
    }
    
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, 1000, 3);
    }
    
    // 配置HTTP客户端(可选)
    @Bean
    public Client feignClient() {
        return new ApacheHttpClient();
    }
}

3.2 单文件上传实现

3.2.1 服务端接口

java

复制代码
@RestController
@RequestMapping("/api/files")
@Slf4j
public class FileUploadController {
    
    @PostMapping("/upload")
    public ResponseEntity<UploadResult> uploadFile(
            @RequestPart("file") MultipartFile file,
            @RequestParam(value = "description", required = false) String description,
            @RequestHeader(value = "X-User-ID", required = false) String userId) {
        
        try {
            // 验证文件
            validateFile(file);
            
            // 保存文件
            String fileId = saveFile(file, userId);
            
            // 记录元数据
            FileMetadata metadata = FileMetadata.builder()
                .fileId(fileId)
                .originalName(file.getOriginalFilename())
                .size(file.getSize())
                .contentType(file.getContentType())
                .description(description)
                .uploadedBy(userId)
                .uploadedAt(Instant.now())
                .build();
            
            saveMetadata(metadata);
            
            return ResponseEntity.ok(UploadResult.success(fileId, metadata));
            
        } catch (FileValidationException e) {
            log.error("文件验证失败", e);
            return ResponseEntity.badRequest()
                .body(UploadResult.error("VALIDATION_ERROR", e.getMessage()));
            
        } catch (StorageException e) {
            log.error("文件存储失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(UploadResult.error("STORAGE_ERROR", "文件存储失败"));
        }
    }
    
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) {
            throw new FileValidationException("文件不能为空");
        }
        
        // 文件大小限制(10MB)
        if (file.getSize() > 10 * 1024 * 1024) {
            throw new FileValidationException("文件大小不能超过10MB");
        }
        
        // 文件类型验证
        String contentType = file.getContentType();
        List<String> allowedTypes = Arrays.asList(
            "image/jpeg", "image/png", "image/gif",
            "application/pdf", "text/plain"
        );
        
        if (!allowedTypes.contains(contentType)) {
            throw new FileValidationException("不支持的文件类型");
        }
    }
    
    private String saveFile(MultipartFile file, String userId) {
        // 生成唯一文件名
        String originalFilename = file.getOriginalFilename();
        String extension = getFileExtension(originalFilename);
        String fileId = UUID.randomUUID().toString();
        String filename = fileId + "." + extension;
        
        // 存储路径(可按用户ID分目录)
        Path storagePath = Paths.get("uploads", 
            userId != null ? userId : "anonymous", 
            filename);
        
        try {
            Files.createDirectories(storagePath.getParent());
            Files.copy(file.getInputStream(), storagePath, 
                StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new StorageException("文件保存失败", e);
        }
        
        return fileId;
    }
    
    private String getFileExtension(String filename) {
        if (filename == null || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf(".") + 1);
    }
    
    // 数据类
    @Data
    @Builder
    public static class UploadResult {
        private boolean success;
        private String fileId;
        private FileMetadata metadata;
        private String errorCode;
        private String errorMessage;
        
        public static UploadResult success(String fileId, FileMetadata metadata) {
            return UploadResult.builder()
                .success(true)
                .fileId(fileId)
                .metadata(metadata)
                .build();
        }
        
        public static UploadResult error(String errorCode, String errorMessage) {
            return UploadResult.builder()
                .success(false)
                .errorCode(errorCode)
                .errorMessage(errorMessage)
                .build();
        }
    }
    
    @Data
    @Builder
    public static class FileMetadata {
        private String fileId;
        private String originalName;
        private Long size;
        private String contentType;
        private String description;
        private String uploadedBy;
        private Instant uploadedAt;
    }
}
3.2.2 客户端Feign接口

java

复制代码
@FeignClient(
    name = "file-service",
    url = "${feign.client.file-service.url}",
    configuration = FileServiceClient.FileServiceConfig.class
)
public interface FileServiceClient {
    
    @PostMapping(value = "/api/files/upload", 
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    UploadResult uploadFile(
        @RequestPart("file") MultipartFile file,
        @RequestParam(value = "description", required = false) String description,
        @RequestHeader(value = "X-User-ID", required = false) String userId);
    
    // 支持直接上传字节数组
    @PostMapping(value = "/api/files/upload-bytes",
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    UploadResult uploadFileBytes(
        @RequestPart("file") byte[] fileBytes,
        @RequestParam("filename") String filename,
        @RequestParam(value = "contentType", required = false) String contentType,
        @RequestParam(value = "description", required = false) String description,
        @RequestHeader(value = "X-User-ID", required = false) String userId);
    
    // 支持上传Resource
    @PostMapping(value = "/api/files/upload-resource",
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    UploadResult uploadFileResource(
        @RequestPart("file") Resource fileResource,
        @RequestParam("filename") String filename,
        @RequestParam(value = "contentType", required = false) String contentType,
        @RequestParam(value = "description", required = false) String description,
        @RequestHeader(value = "X-User-ID", required = false) String userId);
    
    // 配置类
    @Configuration
    class FileServiceConfig {
        
        @Bean
        @Primary
        @Scope("prototype")
        public Encoder feignFormEncoder() {
            List<HttpMessageConverter<?>> converters = 
                new ArrayList<>(new SpringEncoder(getMessageConverters()).getConverters());
            
            converters.add(new ByteArrayHttpMessageConverter());
            converters.add(new StringHttpMessageConverter());
            converters.add(new ResourceHttpMessageConverter());
            converters.add(new SourceHttpMessageConverter<>());
            converters.add(new AllEncompassingFormHttpMessageConverter());
            
            FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
            formConverter.setMultipartCharset(StandardCharsets.UTF_8);
            
            MultipartHttpMessageConverter multipartConverter = 
                new MultipartHttpMessageConverter();
            multipartConverter.setMultipartCharset(StandardCharsets.UTF_8);
            
            converters.add(formConverter);
            converters.add(multipartConverter);
            
            return new SpringFormEncoder(new SpringEncoder(
                () -> new HttpMessageConverters(converters)
            ));
        }
        
        @Bean
        public Decoder feignDecoder() {
            ObjectFactory<HttpMessageConverters> messageConverters = 
                () -> new HttpMessageConverters(
                    new MappingJackson2HttpMessageConverter()
                );
            return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
        }
        
        @Bean
        public Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
        
        @Bean
        public RequestInterceptor requestIdInterceptor() {
            return template -> {
                String requestId = UUID.randomUUID().toString();
                template.header("X-Request-ID", requestId);
                template.header("X-Caller-Service", "your-service-name");
            };
        }
        
        @Bean
        public ErrorDecoder fileUploadErrorDecoder() {
            return new FileUploadErrorDecoder();
        }
    }
    
    // 自定义错误解码器
    class FileUploadErrorDecoder implements ErrorDecoder {
        
        private final ErrorDecoder defaultDecoder = new Default();
        private final ObjectMapper objectMapper = new ObjectMapper();
        
        @Override
        public Exception decode(String methodKey, Response response) {
            
            if (response.status() == 400 || response.status() == 413) {
                try {
                    // 尝试解析错误响应体
                    String body = Util.toString(response.body().asReader(StandardCharsets.UTF_8));
                    UploadResult errorResult = objectMapper.readValue(body, UploadResult.class);
                    
                    return new FileUploadException(
                        "文件上传失败: " + errorResult.getErrorMessage(),
                        errorResult.getErrorCode(),
                        response.status()
                    );
                    
                } catch (IOException e) {
                    // 解析失败,返回默认异常
                }
            }
            
            return defaultDecoder.decode(methodKey, response);
        }
    }
    
    // 自定义异常
    class FileUploadException extends RuntimeException {
        private final String errorCode;
        private final int statusCode;
        
        public FileUploadException(String message, String errorCode, int statusCode) {
            super(message);
            this.errorCode = errorCode;
            this.statusCode = statusCode;
        }
    }
}
3.2.3 客户端使用示例

java

复制代码
@Service
@Slf4j
public class FileUploadService {
    
    private final FileServiceClient fileServiceClient;
    private final ObjectMapper objectMapper;
    
    public FileUploadService(FileServiceClient fileServiceClient, 
                            ObjectMapper objectMapper) {
        this.fileServiceClient = fileServiceClient;
        this.objectMapper = objectMapper;
    }
    
    public String uploadSingleFile(MultipartFile file, String userId, 
                                  String description) {
        try {
            log.info("开始上传文件: {}, 大小: {} bytes", 
                    file.getOriginalFilename(), file.getSize());
            
            // 调用Feign客户端
            UploadResult result = fileServiceClient.uploadFile(
                file, description, userId
            );
            
            if (result.isSuccess()) {
                log.info("文件上传成功, fileId: {}", result.getFileId());
                
                // 记录上传日志
                logUploadSuccess(result.getFileId(), file, userId);
                
                return result.getFileId();
            } else {
                log.error("文件上传失败: {} - {}", 
                         result.getErrorCode(), result.getErrorMessage());
                throw new BusinessException("文件上传失败: " + result.getErrorMessage());
            }
            
        } catch (FeignException e) {
            log.error("Feign调用失败", e);
            handleFeignException(e);
            throw new BusinessException("文件服务调用失败");
        }
    }
    
    public String uploadFileFromPath(Path filePath, String userId, 
                                    String description) {
        try {
            // 读取文件内容
            byte[] fileBytes = Files.readAllBytes(filePath);
            String filename = filePath.getFileName().toString();
            
            // 获取文件类型
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }
            
            // 使用字节数组上传
            UploadResult result = fileServiceClient.uploadFileBytes(
                fileBytes, filename, contentType, description, userId
            );
            
            if (result.isSuccess()) {
                return result.getFileId();
            } else {
                throw new BusinessException(result.getErrorMessage());
            }
            
        } catch (IOException e) {
            throw new BusinessException("读取文件失败", e);
        }
    }
    
    public String uploadFileFromUrl(String fileUrl, String userId, 
                                   String description) {
        try {
            // 从URL下载文件
            URL url = new URL(fileUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            
            if (connection.getResponseCode() != 200) {
                throw new BusinessException("无法下载文件: " + fileUrl);
            }
            
            // 获取文件名和类型
            String filename = getFileNameFromUrl(fileUrl);
            String contentType = connection.getContentType();
            
            // 读取文件内容
            byte[] fileBytes;
            try (InputStream inputStream = connection.getInputStream();
                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                fileBytes = outputStream.toByteArray();
            }
            
            // 上传文件
            return uploadFileBytes(fileBytes, filename, contentType, 
                                  description, userId);
            
        } catch (IOException e) {
            throw new BusinessException("从URL下载文件失败", e);
        }
    }
    
    private String getFileNameFromUrl(String url) {
        try {
            URL parsedUrl = new URL(url);
            String path = parsedUrl.getPath();
            return path.substring(path.lastIndexOf('/') + 1);
        } catch (Exception e) {
            return "downloaded-file";
        }
    }
    
    private void logUploadSuccess(String fileId, MultipartFile file, String userId) {
        // 记录上传日志到数据库或消息队列
        Map<String, Object> logData = new HashMap<>();
        logData.put("fileId", fileId);
        logData.put("originalName", file.getOriginalFilename());
        logData.put("size", file.getSize());
        logData.put("contentType", file.getContentType());
        logData.put("userId", userId);
        logData.put("timestamp", Instant.now());
        
        // 异步记录日志
        CompletableFuture.runAsync(() -> {
            try {
                // 保存到数据库或发送到Kafka
                log.info("Upload log: {}", objectMapper.writeValueAsString(logData));
            } catch (JsonProcessingException e) {
                log.error("Failed to serialize upload log", e);
            }
        });
    }
    
    private void handleFeignException(FeignException e) {
        if (e.status() == 400) {
            log.warn("文件验证失败: {}", e.getMessage());
        } else if (e.status() == 413) {
            log.warn("文件过大: {}", e.getMessage());
        } else if (e.status() == 504) {
            log.warn("文件上传超时: {}", e.getMessage());
        } else {
            log.error("文件上传服务异常, status: {}", e.status(), e);
        }
    }
}

3.3 多文件上传实现

3.3.1 服务端多文件接口

java

复制代码
@RestController
@RequestMapping("/api/files")
public class MultiFileUploadController {
    
    @PostMapping("/upload-multiple")
    public ResponseEntity<BatchUploadResult> uploadMultipleFiles(
            @RequestPart("files") MultipartFile[] files,
            @RequestParam(value = "category", required = false) String category,
            @RequestParam(value = "tags", required = false) List<String> tags,
            @RequestHeader(value = "X-User-ID") String userId) {
        
        List<FileUploadResult> results = new ArrayList<>();
        List<String> errors = new ArrayList<>();
        
        for (int i = 0; i < files.length; i++) {
            MultipartFile file = files[i];
            
            try {
                // 验证并保存单个文件
                String fileId = processSingleFile(file, userId, category, tags);
                
                results.add(FileUploadResult.success(
                    fileId, 
                    file.getOriginalFilename(),
                    file.getSize(),
                    file.getContentType()
                ));
                
            } catch (Exception e) {
                errors.add(String.format("文件 %s 上传失败: %s", 
                    file.getOriginalFilename(), e.getMessage()));
                
                results.add(FileUploadResult.error(
                    file.getOriginalFilename(),
                    e.getMessage()
                ));
            }
        }
        
        BatchUploadResult batchResult = BatchUploadResult.builder()
            .totalFiles(files.length)
            .successfulUploads((int) results.stream()
                .filter(FileUploadResult::isSuccess).count())
            .failedUploads((int) results.stream()
                .filter(r -> !r.isSuccess()).count())
            .results(results)
            .errors(errors)
            .build();
        
        if (errors.isEmpty()) {
            return ResponseEntity.ok(batchResult);
        } else {
            return ResponseEntity.status(HttpStatus.MULTI_STATUS)
                .body(batchResult);
        }
    }
    
    @PostMapping("/upload-multipart")
    public ResponseEntity<BatchUploadResult> uploadWithMetadata(
            @RequestPart("files") MultipartFile[] files,
            @RequestPart("metadata") FileUploadMetadata metadata) {
        
        // 处理带元数据的批量上传
        return uploadMultipleFiles(
            files, 
            metadata.getCategory(),
            metadata.getTags(),
            metadata.getUserId()
        );
    }
    
    // 数据类
    @Data
    @Builder
    public static class FileUploadMetadata {
        private String userId;
        private String category;
        private List<String> tags;
        private Map<String, String> customAttributes;
        private Instant uploadTime;
        private Integer expiryDays;
    }
    
    @Data
    @Builder
    public static class BatchUploadResult {
        private int totalFiles;
        private int successfulUploads;
        private int failedUploads;
        private List<FileUploadResult> results;
        private List<String> errors;
        private String batchId;
        private Instant completedAt;
    }
    
    @Data
    @Builder
    public static class FileUploadResult {
        private boolean success;
        private String fileId;
        private String filename;
        private Long size;
        private String contentType;
        private String errorMessage;
        
        public static FileUploadResult success(String fileId, String filename, 
                                              Long size, String contentType) {
            return FileUploadResult.builder()
                .success(true)
                .fileId(fileId)
                .filename(filename)
                .size(size)
                .contentType(contentType)
                .build();
        }
        
        public static FileUploadResult error(String filename, String errorMessage) {
            return FileUploadResult.builder()
                .success(false)
                .filename(filename)
                .errorMessage(errorMessage)
                .build();
        }
    }
}
3.3.2 客户端多文件Feign接口

java

复制代码
@FeignClient(name = "file-service", 
            configuration = MultiFileServiceClient.MultiFileConfig.class)
public interface MultiFileServiceClient {
    
    @PostMapping(value = "/api/files/upload-multiple",
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    BatchUploadResult uploadMultipleFiles(
        @RequestPart("files") MultipartFile[] files,
        @RequestParam(value = "category", required = false) String category,
        @RequestParam(value = "tags", required = false) List<String> tags,
        @RequestHeader("X-User-ID") String userId);
    
    @PostMapping(value = "/api/files/upload-multipart",
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    BatchUploadResult uploadWithMetadata(
        @RequestPart("files") MultipartFile[] files,
        @RequestPart("metadata") FileUploadMetadata metadata);
    
    // 流式上传接口
    @PostMapping(value = "/api/files/upload-stream",
                consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResponseEntity<Void> uploadStream(
        @RequestPart("metadata") FileUploadMetadata metadata,
        @RequestPart("file") InputStream fileStream,
        @RequestParam("filename") String filename,
        @RequestHeader("Content-Length") long contentLength,
        @RequestHeader("X-User-ID") String userId);
    
    // 配置类
    @Configuration
    class MultiFileConfig {
        
        @Bean
        public Encoder feignEncoder(ObjectFactory<HttpMessageConverters> converters) {
            return new SpringFormEncoder(new SpringEncoder(converters) {
                @Override
                public void encode(Object object, Type bodyType, 
                                 RequestTemplate template) {
                    if (object instanceof MultipartFile[]) {
                        // 处理多文件数组
                        encodeMultipartFiles((MultipartFile[]) object, template);
                    } else if (object instanceof MultiFileRequest) {
                        // 处理自定义多文件请求
                        encodeMultiFileRequest((MultiFileRequest) object, template);
                    } else {
                        super.encode(object, bodyType, template);
                    }
                }
                
                private void encodeMultipartFiles(MultipartFile[] files, 
                                                RequestTemplate template) {
                    MultipartFormData data = new MultipartFormData();
                    
                    for (int i = 0; i < files.length; i++) {
                        MultipartFile file = files[i];
                        data.addFile("files", 
                            new MultipartFormData.File(
                                file.getOriginalFilename(),
                                file.getName(),
                                getContentType(file),
                                file
                            )
                        );
                    }
                    
                    template.body(data, MultipartFormData.class);
                }
                
                private String getContentType(MultipartFile file) {
                    String contentType = file.getContentType();
                    return contentType != null ? contentType : 
                        "application/octet-stream";
                }
            });
        }
        
        @Bean
        public Retryer multipartRetryer() {
            return new Retryer.Default(1000, 5000, 5) {
                @Override
                public void continueOrPropagate(RetryableException e) {
                    // 对于大文件上传,增加重试间隔
                    if (e.getMessage().contains("timeout")) {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException ex) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    super.continueOrPropagate(e);
                }
            };
        }
        
        @Bean
        public RequestInterceptor multipartRequestInterceptor() {
            return template -> {
                // 为大文件上传添加特殊头信息
                if (template.requestBody().asString().contains("multipart/form-data")) {
                    template.header("Expect", "100-continue");
                    template.header("X-Upload-Type", "multipart");
                }
            };
        }
    }
    
    // 自定义多文件请求封装
    @Data
    @Builder
    public static class MultiFileRequest {
        private List<FileItem> files;
        private Map<String, String> metadata;
        private String userId;
        private String category;
        private List<String> tags;
        
        @Data
        @Builder
        public static class FileItem {
            private String name;
            private byte[] content;
            private String contentType;
            private String originalFilename;
        }
    }
}
3.3.3 客户端多文件上传服务

java

复制代码
@Service
@Slf4j
public class MultiFileUploadService {
    
    private final MultiFileServiceClient fileServiceClient;
    private final TaskExecutor taskExecutor;
    private final ObjectMapper objectMapper;
    
    public MultiFileUploadService(MultiFileServiceClient fileServiceClient,
                                 @Qualifier("fileUploadTaskExecutor") TaskExecutor taskExecutor,
                                 ObjectMapper objectMapper) {
        this.fileServiceClient = fileServiceClient;
        this.taskExecutor = taskExecutor;
        this.objectMapper = objectMapper;
    }
    
    public BatchUploadResult uploadMultipleFiles(List<MultipartFile> files, 
                                                String userId, String category,
                                                List<String> tags) {
        log.info("开始批量上传 {} 个文件", files.size());
        
        long startTime = System.currentTimeMillis();
        
        try {
            MultipartFile[] fileArray = files.toArray(new MultipartFile[0]);
            
            BatchUploadResult result = fileServiceClient.uploadMultipleFiles(
                fileArray, category, tags, userId
            );
            
            long duration = System.currentTimeMillis() - startTime;
            log.info("批量上传完成, 成功: {}, 失败: {}, 耗时: {}ms",
                    result.getSuccessfulUploads(), 
                    result.getFailedUploads(),
                    duration);
            
            // 记录批量上传日志
            recordBatchUpload(result, userId, duration);
            
            return result;
            
        } catch (FeignException e) {
            log.error("批量上传失败", e);
            throw new BusinessException("文件批量上传失败: " + e.getMessage());
        }
    }
    
    public CompletableFuture<BatchUploadResult> uploadMultipleFilesAsync(
            List<MultipartFile> files, String userId, String category,
            List<String> tags) {
        
        return CompletableFuture.supplyAsync(() -> 
            uploadMultipleFiles(files, userId, category, tags),
            taskExecutor
        );
    }
    
    public BatchUploadResult uploadLargeFiles(List<Path> filePaths, String userId,
                                             String category, List<String> tags,
                                             ProgressListener progressListener) {
        
        List<CompletableFuture<FileUploadResult>> futures = new ArrayList<>();
        List<FileUploadResult> results = new ArrayList<>();
        
        for (Path filePath : filePaths) {
            CompletableFuture<FileUploadResult> future = 
                CompletableFuture.supplyAsync(() -> 
                    uploadLargeFile(filePath, userId, category, tags, progressListener),
                    taskExecutor
                );
            
            futures.add(future);
        }
        
        // 等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .join();
        
        // 收集结果
        for (CompletableFuture<FileUploadResult> future : futures) {
            try {
                results.add(future.get());
            } catch (Exception e) {
                log.error("获取文件上传结果失败", e);
            }
        }
        
        return buildBatchResult(results);
    }
    
    private FileUploadResult uploadLargeFile(Path filePath, String userId,
                                            String category, List<String> tags,
                                            ProgressListener progressListener) {
        
        try {
            String filename = filePath.getFileName().toString();
            long fileSize = Files.size(filePath);
            
            log.info("开始上传大文件: {}, 大小: {} bytes", filename, fileSize);
            
            // 分片上传(如果文件很大)
            if (fileSize > 10 * 1024 * 1024) { // 大于10MB
                return uploadInChunks(filePath, userId, category, tags, 
                                     progressListener);
            }
            
            // 直接上传小文件
            byte[] fileBytes = Files.readAllBytes(filePath);
            String contentType = Files.probeContentType(filePath);
            
            // 创建MultipartFile
            MultipartFile multipartFile = new MockMultipartFile(
                "file", filename, contentType, fileBytes
            );
            
            MultipartFile[] files = new MultipartFile[]{multipartFile};
            BatchUploadResult batchResult = fileServiceClient.uploadMultipleFiles(
                files, category, tags, userId
            );
            
            if (!batchResult.getResults().isEmpty()) {
                return batchResult.getResults().get(0);
            }
            
            return FileUploadResult.error(filename, "上传结果为空");
            
        } catch (IOException e) {
            log.error("上传大文件失败: {}", filePath, e);
            return FileUploadResult.error(
                filePath.getFileName().toString(),
                "文件读取失败: " + e.getMessage()
            );
        }
    }
    
    private FileUploadResult uploadInChunks(Path filePath, String userId,
                                           String category, List<String> tags,
                                           ProgressListener progressListener) {
        
        try {
            String filename = filePath.getFileName().toString();
            String contentType = Files.probeContentType(filePath);
            long fileSize = Files.size(filePath);
            
            // 初始化分片上传
            String uploadId = initChunkedUpload(filename, fileSize, 
                                               contentType, userId);
            
            // 分片上传
            int chunkSize = 5 * 1024 * 1024; // 5MB每片
            long uploadedBytes = 0;
            
            try (InputStream inputStream = Files.newInputStream(filePath)) {
                byte[] buffer = new byte[chunkSize];
                int chunkIndex = 0;
                
                while (true) {
                    int bytesRead = inputStream.read(buffer);
                    if (bytesRead == -1) break;
                    
                    // 上传分片
                    uploadChunk(uploadId, chunkIndex, 
                               Arrays.copyOf(buffer, bytesRead),
                               bytesRead == chunkSize);
                    
                    uploadedBytes += bytesRead;
                    chunkIndex++;
                    
                    // 更新进度
                    if (progressListener != null) {
                        double progress = (double) uploadedBytes / fileSize * 100;
                        progressListener.onProgress(filename, progress);
                    }
                    
                    log.debug("已上传分片 {}: {}/{} bytes", 
                             chunkIndex, uploadedBytes, fileSize);
                }
            }
            
            // 完成上传
            return completeChunkedUpload(uploadId);
            
        } catch (IOException e) {
            log.error("分片上传失败", e);
            return FileUploadResult.error(
                filePath.getFileName().toString(),
                "分片上传失败: " + e.getMessage()
            );
        }
    }
    
    private String initChunkedUpload(String filename, long fileSize,
                                    String contentType, String userId) {
        // 调用服务端初始化分片上传接口
        Map<String, Object> request = new HashMap<>();
        request.put("filename", filename);
        request.put("fileSize", fileSize);
        request.put("contentType", contentType);
        request.put("userId", userId);
        request.put("chunkSize", 5 * 1024 * 1024);
        
        // 这里需要实现具体的分片上传初始化逻辑
        return UUID.randomUUID().toString();
    }
    
    private void uploadChunk(String uploadId, int chunkIndex, 
                            byte[] chunkData, boolean isFullChunk) {
        // 上传单个分片
        // 实现分片上传逻辑
    }
    
    private FileUploadResult completeChunkedUpload(String uploadId) {
        // 完成分片上传
        // 实现完成逻辑
        return FileUploadResult.success(uploadId, "chunked-file", 0L, 
                                       "application/octet-stream");
    }
    
    private void recordBatchUpload(BatchUploadResult result, 
                                  String userId, long duration) {
        try {
            Map<String, Object> logData = new HashMap<>();
            logData.put("batchId", result.getBatchId());
            logData.put("userId", userId);
            logData.put("totalFiles", result.getTotalFiles());
            logData.put("successful", result.getSuccessfulUploads());
            logData.put("failed", result.getFailedUploads());
            logData.put("duration", duration);
            logData.put("timestamp", Instant.now());
            
            if (result.getErrors() != null && !result.getErrors().isEmpty()) {
                logData.put("errors", result.getErrors());
            }
            
            // 异步保存日志
            CompletableFuture.runAsync(() -> {
                log.info("Batch upload completed: {}", 
                        objectMapper.writeValueAsString(logData));
            });
            
        } catch (JsonProcessingException e) {
            log.error("Failed to serialize batch upload log", e);
        }
    }
    
    public interface ProgressListener {
        void onProgress(String filename, double progress);
    }
}

3.4 高级文件上传特性

3.4.1 断点续传实现

java

复制代码
@Service
@Slf4j
public class ResumableUploadService {
    
    private final FileServiceClient fileServiceClient;
    private final UploadStateRepository uploadStateRepository;
    
    public ResumableUploadService(FileServiceClient fileServiceClient,
                                 UploadStateRepository uploadStateRepository) {
        this.fileServiceClient = fileServiceClient;
        this.uploadStateRepository = uploadStateRepository;
    }
    
    public ResumableUploadSession initResumableUpload(String filename, 
                                                     long fileSize,
                                                     String userId) {
        
        String sessionId = UUID.randomUUID().toString();
        
        ResumableUploadSession session = ResumableUploadSession.builder()
            .sessionId(sessionId)
            .filename(filename)
            .fileSize(fileSize)
            .userId(userId)
            .chunkSize(5 * 1024 * 1024) // 5MB每片
            .createdAt(Instant.now())
            .expiresAt(Instant.now().plusHours(24))
            .build();
        
        // 保存会话状态
        uploadStateRepository.save(session);
        
        return session;
    }
    
    public UploadChunkResult uploadChunk(String sessionId, int chunkIndex,
                                        byte[] chunkData, boolean isLastChunk) {
        
        // 获取上传会话
        ResumableUploadSession session = uploadStateRepository.findById(sessionId)
            .orElseThrow(() -> new BusinessException("上传会话不存在或已过期"));
        
        // 验证分片索引
        if (chunkIndex < 0 || chunkIndex > session.getTotalChunks()) {
            throw new BusinessException("无效的分片索引");
        }
        
        // 上传分片
        try {
            // 调用Feign接口上传分片
            ChunkUploadRequest request = ChunkUploadRequest.builder()
                .sessionId(sessionId)
                .chunkIndex(chunkIndex)
                .chunkData(chunkData)
                .isLastChunk(isLastChunk)
                .build();
            
            ChunkUploadResponse response = fileServiceClient.uploadChunk(request);
            
            // 更新会话状态
            session.getUploadedChunks().add(chunkIndex);
            session.setUploadedBytes(session.getUploadedBytes() + chunkData.length);
            session.setLastActivity(Instant.now());
            
            if (isLastChunk) {
                session.setStatus(UploadStatus.COMPLETED);
                session.setCompletedAt(Instant.now());
                
                // 完成上传
                completeUpload(sessionId);
            }
            
            uploadStateRepository.save(session);
            
            return UploadChunkResult.success(chunkIndex, response.getFileId());
            
        } catch (FeignException e) {
            log.error("分片上传失败: session={}, chunk={}", sessionId, chunkIndex, e);
            return UploadChunkResult.error(chunkIndex, e.getMessage());
        }
    }
    
    public ResumableUploadStatus getUploadStatus(String sessionId) {
        ResumableUploadSession session = uploadStateRepository.findById(sessionId)
            .orElseThrow(() -> new BusinessException("上传会话不存在"));
        
        return ResumableUploadStatus.builder()
            .sessionId(sessionId)
            .filename(session.getFilename())
            .fileSize(session.getFileSize())
            .uploadedBytes(session.getUploadedBytes())
            .chunkSize(session.getChunkSize())
            .uploadedChunks(session.getUploadedChunks())
            .status(session.getStatus())
            .progress((double) session.getUploadedBytes() / session.getFileSize() * 100)
            .createdAt(session.getCreatedAt())
            .lastActivity(session.getLastActivity())
            .build();
    }
    
    public boolean resumeUpload(String sessionId, Path filePath, 
                               ProgressListener progressListener) {
        
        ResumableUploadSession session = uploadStateRepository.findById(sessionId)
            .orElseThrow(() -> new BusinessException("上传会话不存在"));
        
        try {
            long fileSize = Files.size(filePath);
            if (fileSize != session.getFileSize()) {
                throw new BusinessException("文件大小不匹配");
            }
            
            int chunkSize = session.getChunkSize();
            Set<Integer> uploadedChunks = session.getUploadedChunks();
            
            try (InputStream inputStream = Files.newInputStream(filePath)) {
                for (int chunkIndex = 0; chunkIndex < session.getTotalChunks(); chunkIndex++) {
                    
                    // 跳过已上传的分片
                    if (uploadedChunks.contains(chunkIndex)) {
                        inputStream.skip(chunkSize);
                        continue;
                    }
                    
                    // 读取分片数据
                    byte[] buffer = new byte[chunkSize];
                    int bytesRead = inputStream.read(buffer);
                    if (bytesRead == -1) break;
                    
                    // 上传分片
                    boolean isLastChunk = (chunkIndex == session.getTotalChunks() - 1);
                    byte[] chunkData = bytesRead == chunkSize ? 
                        buffer : Arrays.copyOf(buffer, bytesRead);
                    
                    UploadChunkResult result = uploadChunk(sessionId, chunkIndex, 
                                                         chunkData, isLastChunk);
                    
                    if (!result.isSuccess()) {
                        log.error("续传分片失败: chunk={}, error={}", 
                                 chunkIndex, result.getErrorMessage());
                        return false;
                    }
                    
                    // 更新进度
                    if (progressListener != null) {
                        double progress = (double) session.getUploadedBytes() / 
                                        session.getFileSize() * 100;
                        progressListener.onProgress(session.getFilename(), progress);
                    }
                }
            }
            
            return true;
            
        } catch (IOException e) {
            log.error("续传失败", e);
            return false;
        }
    }
    
    // 数据类
    @Data
    @Builder
    public static class ResumableUploadSession {
        private String sessionId;
        private String filename;
        private long fileSize;
        private String userId;
        private int chunkSize;
        private Set<Integer> uploadedChunks = new HashSet<>();
        private long uploadedBytes;
        private UploadStatus status;
        private Instant createdAt;
        private Instant lastActivity;
        private Instant expiresAt;
        private Instant completedAt;
        
        public int getTotalChunks() {
            return (int) Math.ceil((double) fileSize / chunkSize);
        }
    }
    
    public enum UploadStatus {
        INITIALIZED,
        UPLOADING,
        COMPLETED,
        FAILED,
        CANCELLED
    }
}
3.4.2 安全特性实现

java

复制代码
@Component
public class FileUploadSecurityService {
    
    private final AntivirusScanner antivirusScanner;
    private final FileTypeValidator fileTypeValidator;
    private final RateLimiter rateLimiter;
    
    public FileUploadSecurityService(AntivirusScanner antivirusScanner,
                                    FileTypeValidator fileTypeValidator,
                                    RateLimiter rateLimiter) {
        this.antivirusScanner = antivirusScanner;
        this.fileTypeValidator = fileTypeValidator;
        this.rateLimiter = rateLimiter;
    }
    
    public SecurityCheckResult validateFile(MultipartFile file, String userId) {
        SecurityCheckResult result = new SecurityCheckResult();
        
        try {
            // 1. 检查文件大小
            if (!checkFileSize(file)) {
                result.addViolation("FILE_SIZE_LIMIT_EXCEEDED", 
                    "文件大小超过限制");
            }
            
            // 2. 检查文件类型
            if (!fileTypeValidator.isSafeType(file)) {
                result.addViolation("UNSAFE_FILE_TYPE", 
                    "不支持的文件类型或潜在危险文件");
            }
            
            // 3. 检查文件名
            if (!validateFilename(file.getOriginalFilename())) {
                result.addViolation("INVALID_FILENAME", 
                    "文件名包含非法字符");
            }
            
            // 4. 检查上传频率
            if (!rateLimiter.tryAcquire(userId)) {
                result.addViolation("RATE_LIMIT_EXCEEDED", 
                    "上传频率过高,请稍后再试");
            }
            
            // 5. 病毒扫描
            ScanResult scanResult = antivirusScanner.scan(file.getBytes());
            if (!scanResult.isClean()) {
                result.addViolation("VIRUS_DETECTED", 
                    "文件包含病毒或恶意代码: " + scanResult.getThreatName());
            }
            
            // 6. 内容检查(如敏感信息检测)
            if (containsSensitiveInfo(file)) {
                result.addViolation("SENSITIVE_CONTENT_DETECTED", 
                    "文件包含敏感信息");
            }
            
            result.setPassed(result.getViolations().isEmpty());
            
        } catch (IOException e) {
            result.addViolation("FILE_PROCESSING_ERROR", 
                "文件处理失败: " + e.getMessage());
            result.setPassed(false);
        }
        
        return result;
    }
    
    public SecurityCheckResult validateBatch(List<MultipartFile> files, String userId) {
        SecurityCheckResult batchResult = new SecurityCheckResult();
        batchResult.setPassed(true);
        
        for (MultipartFile file : files) {
            SecurityCheckResult fileResult = validateFile(file, userId);
            
            if (!fileResult.isPassed()) {
                batchResult.getFileResults().put(
                    file.getOriginalFilename(), fileResult);
                batchResult.setPassed(false);
            }
        }
        
        return batchResult;
    }
    
    public String generateSecureFilename(String originalFilename) {
        // 移除路径信息
        String safeName = Paths.get(originalFilename).getFileName().toString();
        
        // 移除特殊字符
        safeName = safeName.replaceAll("[^a-zA-Z0-9._-]", "_");
        
        // 添加时间戳和随机数防止重名
        String timestamp = Instant.now().toString()
            .replaceAll("[^0-9]", "").substring(0, 14);
        String random = UUID.randomUUID().toString().substring(0, 8);
        
        String extension = "";
        int dotIndex = safeName.lastIndexOf('.');
        if (dotIndex > 0 && dotIndex < safeName.length() - 1) {
            extension = safeName.substring(dotIndex);
            safeName = safeName.substring(0, dotIndex);
        }
        
        return String.format("%s_%s_%s%s", 
            safeName, timestamp, random, extension);
    }
    
    public byte[] encryptFile(byte[] fileData, String keyId) {
        // 实现文件加密
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            // ... 加密逻辑
            return fileData;
        } catch (Exception e) {
            throw new SecurityException("文件加密失败", e);
        }
    }
    
    public String calculateFileHash(byte[] fileData) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(fileData);
            return Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException("计算文件哈希失败", e);
        }
    }
    
    @Data
    public static class SecurityCheckResult {
        private boolean passed;
        private List<Violation> violations = new ArrayList<>();
        private Map<String, SecurityCheckResult> fileResults = new HashMap<>();
        private Instant checkedAt = Instant.now();
        
        public void addViolation(String code, String message) {
            violations.add(new Violation(code, message));
        }
        
        @Data
        @AllArgsConstructor
        public static class Violation {
            private String code;
            private String message;
        }
    }
    
    @Component
    public static class FileTypeValidator {
        
        private static final Set<String> SAFE_EXTENSIONS = Set.of(
            "jpg", "jpeg", "png", "gif", "pdf", "txt", "doc", "docx",
            "xls", "xlsx", "ppt", "pptx"
        );
        
        private static final Map<String, String> MIME_TYPE_MAP = Map.of(
            "image/jpeg", "jpg",
            "image/png", "png",
            "image/gif", "gif",
            "application/pdf", "pdf",
            "text/plain", "txt"
        );
        
        public boolean isSafeType(MultipartFile file) {
            // 1. 检查MIME类型
            String mimeType = file.getContentType();
            if (mimeType == null) {
                return false;
            }
            
            // 2. 检查文件扩展名
            String originalFilename = file.getOriginalFilename();
            if (originalFilename == null) {
                return false;
            }
            
            String extension = getExtension(originalFilename).toLowerCase();
            if (!SAFE_EXTENSIONS.contains(extension)) {
                return false;
            }
            
            // 3. 验证MIME类型与扩展名是否匹配
            String expectedExtension = MIME_TYPE_MAP.get(mimeType.toLowerCase());
            if (expectedExtension != null && 
                !expectedExtension.equalsIgnoreCase(extension)) {
                return false;
            }
            
            // 4. 魔数验证(实际文件类型)
            try {
                byte[] header = new byte[512];
                InputStream inputStream = file.getInputStream();
                int bytesRead = inputStream.read(header);
                
                if (!validateMagicNumber(header, bytesRead, extension)) {
                    return false;
                }
                
            } catch (IOException e) {
                return false;
            }
            
            return true;
        }
        
        private String getExtension(String filename) {
            int dotIndex = filename.lastIndexOf('.');
            return (dotIndex == -1) ? "" : filename.substring(dotIndex + 1);
        }
        
        private boolean validateMagicNumber(byte[] header, int length, 
                                          String extension) {
            // 实现魔数验证逻辑
            // 例如:PNG文件头应该是 89 50 4E 47
            // PDF文件头应该是 25 50 44 46
            
            if (length < 4) return false;
            
            switch (extension.toLowerCase()) {
                case "png":
                    return header[0] == (byte) 0x89 && 
                           header[1] == 0x50 && 
                           header[2] == 0x4E && 
                           header[3] == 0x47;
                case "jpg":
                case "jpeg":
                    return header[0] == (byte) 0xFF && 
                           header[1] == (byte) 0xD8;
                case "gif":
                    return header[0] == 0x47 && 
                           header[1] == 0x49 && 
                           header[2] == 0x46;
                case "pdf":
                    return header[0] == 0x25 && 
                           header[1] == 0x50 && 
                           header[2] == 0x44 && 
                           header[3] == 0x46;
                default:
                    return true; // 对于其他类型,暂时信任扩展名
            }
        }
    }
}

4. 在非Spring环境中使用原生Feign API的完整指南

4.1 原生Feign基础架构

4.1.1 Feign核心组件

java

复制代码
// Feign的核心构建器
public class NativeFeignClient {
    
    // 1. 基础构建
    public static <T> T createBasicClient(Class<T> apiType, String baseUrl) {
        return Feign.builder()
            .encoder(new GsonEncoder())  // JSON编码器
            .decoder(new GsonDecoder())  // JSON解码器
            .logger(new Slf4jLogger())   // 日志
            .logLevel(Logger.Level.BASIC)
            .target(apiType, baseUrl);   // 目标接口和URL
    }
    
    // 2. 带HTTP客户端的构建
    public static <T> T createWithHttpClient(Class<T> apiType, String baseUrl) {
        // 使用Apache HttpClient
        HttpClient httpClient = HttpClientBuilder.create()
            .setMaxConnTotal(200)
            .setMaxConnPerRoute(20)
            .setDefaultRequestConfig(RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(10000)
                .build())
            .build();
        
        return Feign.builder()
            .client(new ApacheHttpClient(httpClient))
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
            .target(apiType, baseUrl);
    }
    
    // 3. 使用OkHttp
    public static <T> T createWithOkHttp(Class<T> apiType, String baseUrl) {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
            .addInterceptor(new LoggingInterceptor())
            .build();
        
        return Feign.builder()
            .client(new OkHttpClient(okHttpClient))
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .retryer(new Retryer.Default(100, 1000, 3))
            .target(apiType, baseUrl);
    }
    
    // 4. 带断路器
    public static <T> T createWithCircuitBreaker(Class<T> apiType, String baseUrl) {
        CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("api-client");
        
        return Feign.builder()
            .client(new Resilience4JFeignClient(
                circuitBreaker,
                new DefaultFeignClient()
            ))
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .target(apiType, baseUrl);
    }
}
4.1.2 Maven依赖配置

xml

复制代码
<!-- 原生Feign核心依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>11.10</version>
</dependency>

<!-- HTTP客户端(选择其中一个) -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>11.10</version>
</dependency>
<!-- 或 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>11.10</version>
</dependency>

<!-- 编码器/解码器 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>11.10</version>
</dependency>
<!-- 或 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>11.10</version>
</dependency>

<!-- 其他功能 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>11.10</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hystrix</artifactId>
    <version>11.10</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-micrometer</artifactId>
    <version>11.10</version>
</dependency>

4.2 完整原生Feign客户端实现

4.2.1 API接口定义

java

复制代码
// 基础API接口定义
public interface UserServiceApi {
    
    @RequestLine("GET /api/users/{id}")
    @Headers({
        "Accept: application/json",
        "X-Request-ID: {requestId}"
    })
    User getUserById(@Param("id") Long id, @Param("requestId") String requestId);
    
    @RequestLine("POST /api/users")
    @Headers("Content-Type: application/json")
    @Body("{user}")
    User createUser(@Param("user") User user);
    
    @RequestLine("GET /api/users")
    @Headers("Accept: application/json")
    List<User> getUsers(
        @Param("page") Integer page,
        @Param("size") Integer size,
        @Param("sort") String sort
    );
    
    @RequestLine("PUT /api/users/{id}")
    @Headers("Content-Type: application/json")
    @Body("{user}")
    User updateUser(@Param("id") Long id, @Param("user") User user);
    
    @RequestLine("DELETE /api/users/{id}")
    @Headers("Accept: application/json")
    void deleteUser(@Param("id") Long id);
    
    // 文件上传接口
    @RequestLine("POST /api/users/{id}/avatar")
    @Headers({
        "Content-Type: multipart/form-data",
        "X-User-ID: {userId}"
    })
    UploadResult uploadAvatar(
        @Param("id") Long userId,
        @Param("avatar") File avatar,
        @Param("description") String description
    );
    
    // 流式响应
    @RequestLine("GET /api/users/export")
    @Headers("Accept: text/csv")
    Response exportUsers(@Param("format") String format);
    
    // 自定义请求头
    @RequestLine("GET /api/users/profile")
    @Headers({
        "Authorization: Bearer {token}",
        "X-Client-Version: {version}",
        "X-Client-Platform: {platform}"
    })
    UserProfile getProfile(
        @Param("token") String token,
        @Param("version") String version,
        @Param("platform") String platform
    );
}

// 数据模型
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String username;
    private String email;
    private String phone;
    private Instant createdAt;
    private Instant updatedAt;
    private UserStatus status;
    
    public enum UserStatus {
        ACTIVE, INACTIVE, SUSPENDED
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UploadResult {
    private boolean success;
    private String fileId;
    private String url;
    private Long size;
    private Instant uploadedAt;
}
4.2.2 高级配置实现

java

复制代码
public class NativeFeignClientFactory {
    
    private final ObjectMapper objectMapper;
    private final MetricRegistry metricRegistry;
    private final Cache<String, Object> responseCache;
    
    public NativeFeignClientFactory(ObjectMapper objectMapper,
                                   MetricRegistry metricRegistry) {
        this.objectMapper = objectMapper;
        this.metricRegistry = metricRegistry;
        this.responseCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .recordStats()
            .build();
    }
    
    public <T> T createClient(Class<T> apiType, String baseUrl, 
                             ClientConfig config) {
        
        Feign.Builder builder = Feign.builder();
        
        // 1. 配置HTTP客户端
        builder.client(createHttpClient(config));
        
        // 2. 配置编码器/解码器
        builder.encoder(createEncoder(config));
        builder.decoder(createDecoder(config));
        
        // 3. 配置重试器
        builder.retryer(createRetryer(config));
        
        // 4. 配置超时
        builder.options(createOptions(config));
        
        // 5. 配置拦截器
        config.getInterceptors().forEach(builder::requestInterceptor);
        
        // 6. 配置错误解码器
        builder.errorDecoder(createErrorDecoder(config));
        
        // 7. 配置日志
        builder.logger(createLogger(config));
        builder.logLevel(config.getLogLevel());
        
        // 8. 配置契约(自定义注解处理)
        builder.contract(createContract(config));
        
        // 9. 配置指标收集
        if (config.isMetricsEnabled()) {
            builder.client(new MeteredClient(
                builder.client(),
                metricRegistry,
                apiType.getSimpleName()
            ));
        }
        
        // 10. 配置缓存
        if (config.isCacheEnabled()) {
            builder.client(new CachingClient(
                builder.client(),
                responseCache,
                config.getCacheConfig()
            ));
        }
        
        // 11. 配置断路器
        if (config.isCircuitBreakerEnabled()) {
            builder.client(new CircuitBreakerClient(
                builder.client(),
                config.getCircuitBreakerConfig()
            ));
        }
        
        return builder.target(apiType, baseUrl);
    }
    
    private Client createHttpClient(ClientConfig config) {
        switch (config.getHttpClientType()) {
            case APACHE:
                return createApacheHttpClient(config);
            case OKHTTP:
                return createOkHttpClient(config);
            case JDK:
                return new Client.Default(null, null);
            default:
                throw new IllegalArgumentException("Unsupported HTTP client type");
        }
    }
    
    private Client createApacheHttpClient(ClientConfig config) {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
            
            // 连接池配置
            PoolingHttpClientConnectionManager connectionManager = 
                new PoolingHttpClientConnectionManager();
            connectionManager.setMaxTotal(config.getMaxConnections());
            connectionManager.setDefaultMaxPerRoute(config.getMaxConnectionsPerRoute());
            
            httpClientBuilder.setConnectionManager(connectionManager);
            
            // 请求配置
            RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(config.getConnectTimeout())
                .setSocketTimeout(config.getSocketTimeout())
                .setConnectionRequestTimeout(config.getConnectionRequestTimeout())
                .build();
            
            httpClientBuilder.setDefaultRequestConfig(requestConfig);
            
            // 重试策略
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(
                config.getRetryCount(), true
            ));
            
            // 禁用SSL验证(仅测试环境)
            if (config.isDisableSslValidation()) {
                SSLContext sslContext = SSLContexts.custom()
                    .loadTrustMaterial((chain, authType) -> true)
                    .build();
                
                httpClientBuilder.setSSLContext(sslContext);
                httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
            }
            
            return new ApacheHttpClient(httpClientBuilder.build());
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to create Apache HTTP client", e);
        }
    }
    
    private Encoder createEncoder(ClientConfig config) {
        switch (config.getEncoderType()) {
            case JACKSON:
                return new JacksonEncoder(objectMapper);
            case GSON:
                return new GsonEncoder();
            case JSON:
                return new JsonEncoder();
            case STRING:
                return new Encoder.Default();
            default:
                throw new IllegalArgumentException("Unsupported encoder type");
        }
    }
    
    private Decoder createDecoder(ClientConfig config) {
        switch (config.getDecoderType()) {
            case JACKSON:
                return new JacksonDecoder(objectMapper);
            case GSON:
                return new GsonDecoder();
            case JSON:
                return new JsonDecoder();
            case STRING:
                return new Decoder.Default();
            default:
                throw new IllegalArgumentException("Unsupported decoder type");
        }
    }
    
    // 配置类
    @Data
    @Builder
    public static class ClientConfig {
        private HttpClientType httpClientType;
        private EncoderType encoderType;
        private DecoderType decoderType;
        private LogLevel logLevel;
        
        // HTTP配置
        private int connectTimeout;
        private int socketTimeout;
        private int connectionRequestTimeout;
        private int maxConnections;
        private int maxConnectionsPerRoute;
        
        // 重试配置
        private int retryCount;
        private long retryInterval;
        private long maxRetryInterval;
        private double backoffMultiplier;
        
        // 高级功能
        private boolean metricsEnabled;
        private boolean cacheEnabled;
        private boolean circuitBreakerEnabled;
        private boolean disableSslValidation;
        
        private List<RequestInterceptor> interceptors;
        private CacheConfig cacheConfig;
        private CircuitBreakerConfig circuitBreakerConfig;
        
        public enum HttpClientType {
            APACHE, OKHTTP, JDK
        }
        
        public enum EncoderType {
            JACKSON, GSON, JSON, STRING
        }
        
        public enum DecoderType {
            JACKSON, GSON, JSON, STRING
        }
    }
}

// 自定义拦截器示例
public class CustomRequestInterceptor implements RequestInterceptor {
    
    private final String apiKey;
    private final String clientId;
    
    public CustomRequestInterceptor(String apiKey, String clientId) {
        this.apiKey = apiKey;
        this.clientId = clientId;
    }
    
    @Override
    public void apply(RequestTemplate template) {
        // 添加认证头
        template.header("X-API-Key", apiKey);
        template.header("X-Client-ID", clientId);
        
        // 添加请求ID
        template.header("X-Request-ID", UUID.randomUUID().toString());
        
        // 添加时间戳
        template.header("X-Timestamp", Instant.now().toString());
        
        // 记录请求日志
        logRequest(template);
        
        // 修改请求体(如添加签名)
        if (template.body() != null) {
            String signature = calculateSignature(template.body());
            template.header("X-Signature", signature);
        }
    }
    
    private void logRequest(RequestTemplate template) {
        System.out.printf("[Feign Request] %s %s%n", 
                         template.method(), template.url());
    }
    
    private String calculateSignature(byte[] body) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(body);
            return Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to calculate signature", e);
        }
    }
}

// 自定义错误解码器
public class CustomErrorDecoder implements ErrorDecoder {
    
    private final ObjectMapper objectMapper;
    private final ErrorDecoder defaultDecoder = new Default();
    
    public CustomErrorDecoder(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
    
    @Override
    public Exception decode(String methodKey, Response response) {
        try {
            // 尝试解析错误响应体
            if (response.body() != null) {
                String body = Util.toString(response.body().asReader(StandardCharsets.UTF_8));
                
                // 尝试解析为已知错误结构
                try {
                    ApiError apiError = objectMapper.readValue(body, ApiError.class);
                    return new ApiException(
                        apiError.getMessage(),
                        apiError.getCode(),
                        response.status(),
                        apiError.getDetails()
                    );
                } catch (JsonProcessingException e) {
                    // 如果不是JSON格式,返回原始错误
                }
            }
            
            // 根据状态码返回特定异常
            switch (response.status()) {
                case 400:
                    return new BadRequestException("Bad request");
                case 401:
                    return new UnauthorizedException("Unauthorized");
                case 403:
                    return new ForbiddenException("Forbidden");
                case 404:
                    return new NotFoundException("Not found");
                case 429:
                    return new RateLimitException("Rate limit exceeded");
                case 500:
                    return new ServerException("Server error");
                case 502:
                case 503:
                case 504:
                    return new ServiceUnavailableException("Service unavailable");
                default:
                    return defaultDecoder.decode(methodKey, response);
            }
            
        } catch (IOException e) {
            return new FeignException("Error decoding response", e);
        }
    }
}

// 自定义异常类
public class ApiException extends RuntimeException {
    private final String errorCode;
    private final int statusCode;
    private final Map<String, Object> details;
    
    public ApiException(String message, String errorCode, 
                       int statusCode, Map<String, Object> details) {
        super(message);
        this.errorCode = errorCode;
        this.statusCode = statusCode;
        this.details = details != null ? details : new HashMap<>();
    }
}
4.2.3 完整使用示例

java

复制代码
public class NativeFeignExample {
    
    public static void main(String[] args) {
        // 1. 创建对象映射器
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        
        // 2. 创建度量注册表
        MetricRegistry metricRegistry = new MetricRegistry();
        
        // 3. 创建工厂
        NativeFeignClientFactory factory = new NativeFeignClientFactory(
            objectMapper, metricRegistry
        );
        
        // 4. 配置客户端
        ClientConfig config = ClientConfig.builder()
            .httpClientType(ClientConfig.HttpClientType.APACHE)
            .encoderType(ClientConfig.EncoderType.JACKSON)
            .decoderType(ClientConfig.DecoderType.JACKSON)
            .logLevel(Logger.Level.FULL)
            .connectTimeout(5000)
            .socketTimeout(10000)
            .maxConnections(100)
            .maxConnectionsPerRoute(20)
            .retryCount(3)
            .metricsEnabled(true)
            .cacheEnabled(true)
            .circuitBreakerEnabled(true)
            .interceptors(Arrays.asList(
                new CustomRequestInterceptor("your-api-key", "your-client-id"),
                new LoggingInterceptor()
            ))
            .build();
        
        // 5. 创建客户端
        UserServiceApi userService = factory.createClient(
            UserServiceApi.class,
            "https://api.example.com",
            config
        );
        
        // 6. 使用客户端
        try {
            // 获取用户
            User user = userService.getUserById(123L, "req-123");
            System.out.println("User: " + user.getUsername());
            
            // 创建用户
            User newUser = new User();
            newUser.setUsername("newuser");
            newUser.setEmail("newuser@example.com");
            
            User createdUser = userService.createUser(newUser);
            System.out.println("Created user ID: " + createdUser.getId());
            
            // 批量获取用户
            List<User> users = userService.getUsers(0, 20, "createdAt,desc");
            System.out.println("Total users: " + users.size());
            
            // 文件上传
            File avatar = new File("avatar.jpg");
            UploadResult uploadResult = userService.uploadAvatar(
                createdUser.getId(), avatar, "Profile avatar"
            );
            System.out.println("File uploaded: " + uploadResult.getUrl());
            
            // 导出用户
            Response exportResponse = userService.exportUsers("csv");
            try (InputStream is = exportResponse.body().asInputStream()) {
                // 处理流式响应
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    // 处理数据
                }
            }
            
        } catch (ApiException e) {
            System.err.println("API Error: " + e.getErrorCode() + " - " + e.getMessage());
            e.getDetails().forEach((key, value) -> 
                System.err.println(key + ": " + value));
            
        } catch (Exception e) {
            System.err.println("Unexpected error: " + e.getMessage());
            e.printStackTrace();
        }
        
        // 7. 监控指标
        reportMetrics(metricRegistry);
    }
    
    private static void reportMetrics(MetricRegistry metricRegistry) {
        // 输出请求计数
        Counter requestCounter = metricRegistry.counter("feign.requests.total");
        System.out.println("Total requests: " + requestCounter.getCount());
        
        // 输出响应时间直方图
        Histogram responseTimeHistogram = metricRegistry.histogram("feign.response.time");
        Snapshot snapshot = responseTimeHistogram.getSnapshot();
        System.out.printf("Response time - Avg: %.2f, 95th: %.2f%n",
                         snapshot.getMean(),
                         snapshot.get95thPercentile());
    }
}

// 异步客户端实现
public class AsyncFeignClient<T> {
    
    private final T syncClient;
    private final ExecutorService executorService;
    
    public AsyncFeignClient(Class<T> apiType, String baseUrl, ClientConfig config) {
        NativeFeignClientFactory factory = new NativeFeignClientFactory(
            new ObjectMapper(), new MetricRegistry()
        );
        this.syncClient = factory.createClient(apiType, baseUrl, config);
        this.executorService = Executors.newFixedThreadPool(10);
    }
    
    public CompletableFuture<User> getUserAsync(Long id, String requestId) {
        return CompletableFuture.supplyAsync(() -> {
            UserServiceApi api = (UserServiceApi) syncClient;
            return api.getUserById(id, requestId);
        }, executorService);
    }
    
    public CompletableFuture<List<User>> getUsersAsync(Integer page, Integer size, String sort) {
        return CompletableFuture.supplyAsync(() -> {
            UserServiceApi api = (UserServiceApi) syncClient;
            return api.getUsers(page, size, sort);
        }, executorService);
    }
    
    public CompletableFuture<User> createUserAsync(User user) {
        return CompletableFuture.supplyAsync(() -> {
            UserServiceApi api = (UserServiceApi) syncClient;
            return api.createUser(user);
        }, executorService);
    }
    
    // 批量异步操作
    public CompletableFuture<List<User>> batchCreateUsersAsync(List<User> users) {
        List<CompletableFuture<User>> futures = users.stream()
            .map(this::createUserAsync)
            .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));
    }
    
    // 带超时的异步操作
    public CompletableFuture<User> getUserWithTimeout(Long id, String requestId, 
                                                     long timeout, TimeUnit unit) {
        CompletableFuture<User> future = getUserAsync(id, requestId);
        return future.orTimeout(timeout, unit)
            .exceptionally(ex -> {
                if (ex instanceof TimeoutException) {
                    throw new RuntimeException("Request timeout", ex);
                }
                throw new RuntimeException("Request failed", ex);
            });
    }
    
    public void shutdown() {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

4.3 原生Feign高级特性

4.3.1 自定义编解码器

java

复制代码
// 自定义Protobuf编码器
public class ProtobufEncoder implements Encoder {
    
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
        if (object instanceof Message) {
            Message message = (Message) object;
            template.body(message.toByteArray(), StandardCharsets.UTF_8);
            template.header("Content-Type", "application/x-protobuf");
        } else {
            throw new EncodeException("Object must be a Protocol Buffers Message");
        }
    }
}

// 自定义Protobuf解码器
public class ProtobufDecoder implements Decoder {
    
    private final ExtensionRegistry extensionRegistry;
    
    public ProtobufDecoder() {
        this.extensionRegistry = ExtensionRegistry.newInstance();
    }
    
    @Override
    public Object decode(Response response, Type type) throws IOException {
        if (response.body() == null) {
            return null;
        }
        
        if (type instanceof Class && Message.class.isAssignableFrom((Class<?>) type)) {
            @SuppressWarnings("unchecked")
            Class<Message> messageClass = (Class<Message>) type;
            
            Message.Builder builder = getBuilder(messageClass);
            builder.mergeFrom(response.body().asInputStream(), extensionRegistry);
            return builder.build();
        }
        
        throw new DecodeException(response.status(), 
            "Type is not a Protocol Buffers Message", response.request());
    }
    
    private Message.Builder getBuilder(Class<Message> messageClass) {
        try {
            Method method = messageClass.getMethod("newBuilder");
            return (Message.Builder) method.invoke(null);
        } catch (Exception e) {
            throw new RuntimeException("Failed to create builder", e);
        }
    }
}

// 自定义XML编码器
public class JacksonXmlEncoder implements Encoder {
    
    private final XmlMapper xmlMapper;
    
    public JacksonXmlEncoder() {
        this.xmlMapper = new XmlMapper();
        this.xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
    
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
        try {
            String xml = xmlMapper.writeValueAsString(object);
            template.body(xml, StandardCharsets.UTF_8);
            template.header("Content-Type", "application/xml");
        } catch (JsonProcessingException e) {
            throw new EncodeException("Failed to encode object as XML", e);
        }
    }
}

// 多部分表单编码器
public class MultipartFormEncoder implements Encoder {
    
    private final Encoder delegate;
    private final String boundary;
    
    public MultipartFormEncoder() {
        this.delegate = new feign.form.FormEncoder();
        this.boundary = UUID.randomUUID().toString();
    }
    
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
        if (object instanceof MultipartFormData) {
            MultipartFormData formData = (MultipartFormData) object;
            encodeMultipart(formData, template);
        } else {
            delegate.encode(object, bodyType, template);
        }
    }
    
    private void encodeMultipart(MultipartFormData formData, RequestTemplate template) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        
        for (MultipartFormData.Part part : formData.getParts()) {
            writer.append("--").append(boundary).append("\r\n");
            
            if (part instanceof MultipartFormData.FilePart) {
                MultipartFormData.FilePart filePart = (MultipartFormData.FilePart) part;
                writer.append("Content-Disposition: form-data; name=\"")
                      .append(filePart.getName())
                      .append("\"; filename=\"")
                      .append(filePart.getFilename())
                      .append("\"\r\n");
                
                if (filePart.getContentType() != null) {
                    writer.append("Content-Type: ")
                          .append(filePart.getContentType())
                          .append("\r\n");
                }
                
                writer.append("\r\n");
                writer.flush();
                
                try {
                    if (filePart.getData() instanceof byte[]) {
                        outputStream.write((byte[]) filePart.getData());
                    } else if (filePart.getData() instanceof InputStream) {
                        InputStream is = (InputStream) filePart.getData();
                        byte[] buffer = new byte[4096];
                        int bytesRead;
                        while ((bytesRead = is.read(buffer)) != -1) {
                            outputStream.write(buffer, 0, bytesRead);
                        }
                    }
                } catch (IOException e) {
                    throw new EncodeException("Failed to write file data", e);
                }
                
                writer.append("\r\n");
            } else {
                writer.append("Content-Disposition: form-data; name=\"")
                      .append(part.getName())
                      .append("\"\r\n\r\n")
                      .append(part.getValue())
                      .append("\r\n");
            }
        }
        
        writer.append("--").append(boundary).append("--\r\n");
        writer.flush();
        
        template.body(outputStream.toByteArray(), StandardCharsets.UTF_8);
        template.header("Content-Type", "multipart/form-data; boundary=" + boundary);
    }
}

// 自定义压缩解码器
public class CompressedResponseDecoder implements Decoder {
    
    private final Decoder delegate;
    
    public CompressedResponseDecoder(Decoder delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Object decode(Response response, Type type) throws IOException {
        Response decompressedResponse = decompressResponse(response);
        return delegate.decode(decompressedResponse, type);
    }
    
    private Response decompressResponse(Response response) {
        String contentEncoding = response.headers().get("Content-Encoding");
        
        if (contentEncoding != null && contentEncoding.contains("gzip")) {
            try {
                byte[] decompressed = decompressGzip(response.body().asInputStream());
                return response.toBuilder()
                    .body(decompressed)
                    .removeHeader("Content-Encoding")
                    .build();
            } catch (IOException e) {
                throw new DecodeException(response.status(), 
                    "Failed to decompress gzip response", response.request(), e);
            }
        }
        
        return response;
    }
    
    private byte[] decompressGzip(InputStream inputStream) throws IOException {
        try (GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = gzipInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        }
    }
}
4.3.2 高级拦截器和过滤器

java

复制代码
// 认证拦截器
public class OAuth2Interceptor implements RequestInterceptor {
    
    private final TokenProvider tokenProvider;
    private final TokenCache tokenCache;
    
    public OAuth2Interceptor(TokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
        this.tokenCache = new TokenCache();
    }
    
    @Override
    public void apply(RequestTemplate template) {
        String accessToken = getAccessToken();
        template.header("Authorization", "Bearer " + accessToken);
    }
    
    private String getAccessToken() {
        // 从缓存获取
        String cachedToken = tokenCache.getToken();
        if (cachedToken != null && !isTokenExpired(cachedToken)) {
            return cachedToken;
        }
        
        // 获取新token
        TokenResponse response = tokenProvider.getToken();
        tokenCache.cacheToken(response.getAccessToken(), 
                             response.getExpiresIn());
        
        return response.getAccessToken();
    }
    
    private boolean isTokenExpired(String token) {
        // 解析token判断是否过期
        return false; // 简化实现
    }
}

// 日志拦截器
public class FullLoggingInterceptor implements RequestInterceptor {
    
    private final Logger logger;
    private final ObjectMapper objectMapper;
    
    public FullLoggingInterceptor(Logger logger) {
        this.logger = logger;
        this.objectMapper = new ObjectMapper();
        this.objectMapper.setVisibility(
            PropertyAccessor.FIELD, 
            JsonAutoDetect.Visibility.ANY
        );
    }
    
    @Override
    public void apply(RequestTemplate template) {
        logRequest(template);
    }
    
    private void logRequest(RequestTemplate template) {
        try {
            Map<String, Object> logData = new HashMap<>();
            logData.put("timestamp", Instant.now());
            logData.put("method", template.method());
            logData.put("url", template.url());
            logData.put("headers", template.headers());
            
            if (template.body() != null) {
                // 尝试解析请求体(可能不是JSON)
                try {
                    String body = new String(template.body(), StandardCharsets.UTF_8);
                    if (isJson(body)) {
                        logData.put("body", objectMapper.readValue(body, Object.class));
                    } else {
                        logData.put("body", "[binary or non-JSON data]");
                    }
                } catch (Exception e) {
                    logData.put("body", "[unparseable data]");
                }
            }
            
            logger.info("Feign Request: {}", objectMapper.writeValueAsString(logData));
            
        } catch (JsonProcessingException e) {
            logger.error("Failed to log request", e);
        }
    }
    
    private boolean isJson(String str) {
        try {
            objectMapper.readTree(str);
            return true;
        } catch (JsonProcessingException e) {
            return false;
        }
    }
}

// 重试拦截器
public class SmartRetryInterceptor implements RequestInterceptor, Retryer {
    
    private final Map<String, Integer> retryCounts = new ConcurrentHashMap<>();
    private final long maxRetryInterval = 30000; // 30秒
    private final double backoffMultiplier = 1.5;
    
    @Override
    public void apply(RequestTemplate template) {
        // 为每个请求生成唯一ID用于重试跟踪
        String requestId = UUID.randomUUID().toString();
        template.header("X-Retry-ID", requestId);
        retryCounts.put(requestId, 0);
    }
    
    @Override
    public void continueOrPropagate(RetryableException e) {
        String requestId = extractRequestId(e);
        int retryCount = retryCounts.getOrDefault(requestId, 0);
        
        if (retryCount >= 3) {
            retryCounts.remove(requestId);
            throw e;
        }
        
        // 指数退避
        long waitTime = (long) (100 * Math.pow(backoffMultiplier, retryCount));
        waitTime = Math.min(waitTime, maxRetryInterval);
        
        try {
            Thread.sleep(waitTime);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw e;
        }
        
        retryCounts.put(requestId, retryCount + 1);
    }
    
    @Override
    public Retryer clone() {
        return new SmartRetryInterceptor();
    }
    
    private String extractRequestId(RetryableException e) {
        // 从异常中提取请求ID
        return e.getMessage(); // 简化实现
    }
}

// 指标收集拦截器
public class MetricsInterceptor implements RequestInterceptor {
    
    private final MeterRegistry meterRegistry;
    private final Map<String, Timer.Sample> activeRequests = new ConcurrentHashMap<>();
    
    public MetricsInterceptor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    @Override
    public void apply(RequestTemplate template) {
        String requestKey = template.method() + " " + template.url();
        
        // 开始计时
        Timer.Sample sample = Timer.start(meterRegistry);
        activeRequests.put(requestKey, sample);
        
        // 记录请求计数
        Counter.builder("feign.requests")
            .tag("method", template.method())
            .tag("endpoint", extractEndpoint(template.url()))
            .register(meterRegistry)
            .increment();
    }
    
    public void recordResponse(String requestKey, int statusCode, boolean success) {
        Timer.Sample sample = activeRequests.remove(requestKey);
        if (sample != null) {
            sample.stop(Timer.builder("feign.response.time")
                .tag("status", String.valueOf(statusCode))
                .tag("success", String.valueOf(success))
                .register(meterRegistry));
        }
        
        // 记录响应状态
        Counter.builder("feign.responses")
            .tag("status", String.valueOf(statusCode))
            .register(meterRegistry)
            .increment();
    }
    
    private String extractEndpoint(String url) {
        // 从URL提取端点路径
        try {
            URI uri = new URI(url);
            return uri.getPath();
        } catch (URISyntaxException e) {
            return "unknown";
        }
    }
}
4.3.3 服务发现集成

java

复制代码
// 基于DNS的服务发现
public class DnsServiceDiscovery implements ServiceDiscovery {
    
    private final DnsResolver dnsResolver;
    private final Map<String, List<ServiceInstance>> cache = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler;
    
    public DnsServiceDiscovery() {
        this.dnsResolver = new DnsResolver();
        this.scheduler = Executors.newScheduledThreadPool(1);
        
        // 定期刷新缓存
        scheduler.scheduleAtFixedRate(this::refreshAll, 30, 30, TimeUnit.SECONDS);
    }
    
    @Override
    public List<ServiceInstance> getInstances(String serviceName) {
        return cache.computeIfAbsent(serviceName, this::resolveInstances);
    }
    
    private List<ServiceInstance> resolveInstances(String serviceName) {
        try {
            InetAddress[] addresses = InetAddress.getAllByName(serviceName);
            
            return Arrays.stream(addresses)
                .map(address -> new SimpleServiceInstance(
                    serviceName,
                    address.getHostAddress(),
                    80, // 默认端口
                    Map.of("hostname", address.getHostName())
                ))
                .collect(Collectors.toList());
            
        } catch (UnknownHostException e) {
            throw new ServiceDiscoveryException(
                "Failed to resolve service: " + serviceName, e);
        }
    }
    
    private void refreshAll() {
        for (String serviceName : cache.keySet()) {
            try {
                List<ServiceInstance> instances = resolveInstances(serviceName);
                cache.put(serviceName, instances);
            } catch (Exception e) {
                // 记录错误但继续运行
                System.err.println("Failed to refresh service: " + serviceName);
            }
        }
    }
    
    public void shutdown() {
        scheduler.shutdown();
    }
}

// 基于Consul的服务发现
public class ConsulServiceDiscovery implements ServiceDiscovery {
    
    private final ConsulClient consulClient;
    private final Map<String, ServiceCache<ConsulService>> caches = new ConcurrentHashMap<>();
    
    public ConsulServiceDiscovery(String consulHost, int consulPort) {
        this.consulClient = Consul.newClient(consulHost, consulPort);
    }
    
    @Override
    public List<ServiceInstance> getInstances(String serviceName) {
        ServiceCache<ConsulService> cache = caches.computeIfAbsent(
            serviceName, this::createCache);
        
        return cache.getInstances().stream()
            .map(this::toServiceInstance)
            .collect(Collectors.toList());
    }
    
    private ServiceCache<ConsulService> createCache(String serviceName) {
        ServiceCache<ConsulService> cache = ServiceCache.newCache(
            consulClient.agentClient(),
            serviceName
        );
        
        cache.addListener(new ServiceCacheListener<ConsulService>() {
            @Override
            public void cacheChanged() {
                System.out.println("Service cache updated: " + serviceName);
            }
        });
        
        cache.start();
        return cache;
    }
    
    private ServiceInstance toServiceInstance(ConsulService consulService) {
        return new SimpleServiceInstance(
            consulService.getServiceName(),
            consulService.getAddress(),
            consulService.getPort(),
            consulService.getMetadata()
        );
    }
    
    public void shutdown() {
        caches.values().forEach(ServiceCache::stop);
    }
}

// 负载均衡客户端
public class LoadBalancedClient implements Client {
    
    private final Client delegate;
    private final ServiceDiscovery serviceDiscovery;
    private final LoadBalancer loadBalancer;
    private final Map<String, List<ServiceInstance>> instanceCache = new ConcurrentHashMap<>();
    
    public LoadBalancedClient(Client delegate, ServiceDiscovery serviceDiscovery) {
        this.delegate = delegate;
        this.serviceDiscovery = serviceDiscovery;
        this.loadBalancer = new RoundRobinLoadBalancer();
    }
    
    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        // 解析服务名称
        String serviceName = extractServiceName(request.url());
        
        // 获取服务实例
        List<ServiceInstance> instances = getServiceInstances(serviceName);
        if (instances.isEmpty()) {
            throw new IOException("No instances available for service: " + serviceName);
        }
        
        // 选择实例
        ServiceInstance instance = loadBalancer.choose(instances);
        
        // 重写URL
        Request balancedRequest = rewriteRequest(request, instance);
        
        // 执行请求
        try {
            return delegate.execute(balancedRequest, options);
        } catch (IOException e) {
            // 标记实例为不健康
            loadBalancer.markFailure(instance);
            throw e;
        }
    }
    
    private List<ServiceInstance> getServiceInstances(String serviceName) {
        return instanceCache.computeIfAbsent(serviceName, 
            key -> serviceDiscovery.getInstances(key));
    }
    
    private String extractServiceName(String url) {
        // 从URL中提取服务名称
        // 例如:http://user-service/api/users -> user-service
        try {
            URI uri = new URI(url);
            String host = uri.getHost();
            return host != null ? host : "unknown";
        } catch (URISyntaxException e) {
            return "unknown";
        }
    }
    
    private Request rewriteRequest(Request original, ServiceInstance instance) {
        try {
            URI originalUri = new URI(original.url());
            
            URI newUri = new URI(
                originalUri.getScheme(),
                null, // 用户信息
                instance.getHost(),
                instance.getPort(),
                originalUri.getPath(),
                originalUri.getQuery(),
                originalUri.getFragment()
            );
            
            return Request.create(
                original.method(),
                newUri.toString(),
                original.headers(),
                original.body(),
                original.charset(),
                original.requestTemplate()
            );
            
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Invalid URL: " + original.url(), e);
        }
    }
}

// 服务发现接口
public interface ServiceDiscovery {
    List<ServiceInstance> getInstances(String serviceName);
}

// 服务实例接口
public interface ServiceInstance {
    String getServiceName();
    String getHost();
    int getPort();
    Map<String, String> getMetadata();
}

// 负载均衡器接口
public interface LoadBalancer {
    ServiceInstance choose(List<ServiceInstance> instances);
    void markSuccess(ServiceInstance instance);
    void markFailure(ServiceInstance instance);
}

// 轮询负载均衡器实现
public class RoundRobinLoadBalancer implements LoadBalancer {
    
    private final ConcurrentMap<String, AtomicInteger> counters = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Set<ServiceInstance>> healthyInstances = new ConcurrentHashMap<>();
    
    @Override
    public ServiceInstance choose(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            throw new IllegalStateException("No instances available");
        }
        
        String serviceName = instances.get(0).getServiceName();
        
        // 获取健康实例
        Set<ServiceInstance> healthy = healthyInstances.computeIfAbsent(
            serviceName, k -> new ConcurrentHashSet<>());
        
        // 初始化健康实例集合
        if (healthy.isEmpty()) {
            healthy.addAll(instances);
        }
        
        // 从健康实例中选择
        List<ServiceInstance> available = new ArrayList<>(healthy);
        if (available.isEmpty()) {
            available = instances; // 如果没有健康实例,使用所有实例
        }
        
        AtomicInteger counter = counters.computeIfAbsent(
            serviceName, k -> new AtomicInteger(0));
        
        int index = counter.getAndIncrement() % available.size();
        if (index < 0) {
            index = 0;
            counter.set(0);
        }
        
        return available.get(index);
    }
    
    @Override
    public void markSuccess(ServiceInstance instance) {
        String serviceName = instance.getServiceName();
        Set<ServiceInstance> healthy = healthyInstances.get(serviceName);
        if (healthy != null) {
            healthy.add(instance);
        }
    }
    
    @Override
    public void markFailure(ServiceInstance instance) {
        String serviceName = instance.getServiceName();
        Set<ServiceInstance> healthy = healthyInstances.get(serviceName);
        if (healthy != null) {
            healthy.remove(instance);
        }
    }
}

4.4 测试和监控

4.4.1 单元测试

java

复制代码
public class NativeFeignClientTest {
    
    @Test
    public void testUserServiceClient() {
        // 创建模拟服务器
        try (MockWebServer server = new MockWebServer()) {
            // 设置模拟响应
            server.enqueue(new MockResponse()
                .setResponseCode(200)
                .setHeader("Content-Type", "application/json")
                .setBody("{\"id\":123,\"username\":\"testuser\",\"email\":\"test@example.com\"}"));
            
            server.start();
            
            // 创建Feign客户端
            UserServiceApi client = Feign.builder()
                .encoder(new GsonEncoder())
                .decoder(new GsonDecoder())
                .target(UserServiceApi.class, server.url("/").toString());
            
            // 执行测试
            User user = client.getUserById(123L, "test-request-id");
            
            // 验证结果
            assertNotNull(user);
            assertEquals(123L, user.getId());
            assertEquals("testuser", user.getUsername());
            assertEquals("test@example.com", user.getEmail());
            
            // 验证请求
            RecordedRequest request = server.takeRequest();
            assertEquals("GET", request.getMethod());
            assertEquals("/api/users/123", request.getPath());
            assertEquals("test-request-id", request.getHeader("X-Request-ID"));
        }
    }
    
    @Test
    public void testErrorHandling() {
        try (MockWebServer server = new MockWebServer()) {
            // 模拟错误响应
            server.enqueue(new MockResponse()
                .setResponseCode(404)
                .setBody("{\"error\":\"User not found\"}"));
            
            server.start();
            
            UserServiceApi client = Feign.builder()
                .encoder(new GsonEncoder())
                .decoder(new GsonDecoder())
                .errorDecoder(new CustomErrorDecoder(new ObjectMapper()))
                .target(UserServiceApi.class, server.url("/").toString());
            
            try {
                client.getUserById(999L, "test");
                fail("Expected exception");
            } catch (ApiException e) {
                assertEquals(404, e.getStatusCode());
                assertEquals("User not found", e.getMessage());
            }
        }
    }
    
    @Test
    public void testRetryMechanism() {
        try (MockWebServer server = new MockWebServer()) {
            // 第一次失败,第二次成功
            server.enqueue(new MockResponse().setResponseCode(503));
            server.enqueue(new MockResponse()
                .setResponseCode(200)
                .setBody("{\"id\":123,\"username\":\"retryuser\"}"));
            
            server.start();
            
            UserServiceApi client = Feign.builder()
                .encoder(new GsonEncoder())
                .decoder(new GsonDecoder())
                .retryer(new Retryer.Default(100, 1000, 3))
                .target(UserServiceApi.class, server.url("/").toString());
            
            User user = client.getUserById(123L, "retry-test");
            
            assertNotNull(user);
            assertEquals(123L, user.getId());
            
            // 验证重试次数
            assertEquals(2, server.getRequestCount());
        }
    }
}

// 性能测试
public class NativeFeignPerformanceTest {
    
    @Test
    public void testConcurrentRequests() throws InterruptedException {
        int threadCount = 10;
        int requestsPerThread = 100;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        try (MockWebServer server = new MockWebServer()) {
            // 准备响应
            for (int i = 0; i < threadCount * requestsPerThread; i++) {
                server.enqueue(new MockResponse()
                    .setResponseCode(200)
                    .setBody("{\"id\":" + i + ",\"username\":\"user" + i + "\"}"));
            }
            
            server.start();
            
            UserServiceApi client = Feign.builder()
                .encoder(new GsonEncoder())
                .decoder(new GsonDecoder())
                .target(UserServiceApi.class, server.url("/").toString());
            
            CountDownLatch latch = new CountDownLatch(threadCount);
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            
            long startTime = System.currentTimeMillis();
            
            for (int i = 0; i < threadCount; i++) {
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    try {
                        for (int j = 0; j < requestsPerThread; j++) {
                            client.getUserById((long) j, "perf-test");
                        }
                    } finally {
                        latch.countDown();
                    }
                }, executor);
                
                futures.add(future);
            }
            
            // 等待所有任务完成
            latch.await(30, TimeUnit.SECONDS);
            
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;
            
            System.out.printf("Completed %d requests in %d ms (%d req/sec)%n",
                threadCount * requestsPerThread,
                duration,
                (threadCount * requestsPerThread * 1000L) / duration);
            
            // 验证所有请求都已完成
            for (CompletableFuture<Void> future : futures) {
                assertTrue(future.isDone());
            }
            
            assertEquals(threadCount * requestsPerThread, server.getRequestCount());
            
        } finally {
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}
4.4.2 集成测试

java

复制代码
public class NativeFeignIntegrationTest {
    
    private static MockWebServer mockServer;
    private static UserServiceApi client;
    
    @BeforeAll
    public static void setup() {
        mockServer = new MockWebServer();
        
        client = Feign.builder()
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
            .retryer(new Retryer.Default(100, 1000, 3))
            .target(UserServiceApi.class, mockServer.url("/").toString());
    }
    
    @AfterAll
    public static void teardown() throws IOException {
        mockServer.shutdown();
    }
    
    @BeforeEach
    public void reset() {
        mockServer.dispatcher = new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) {
                return handleRequest(request);
            }
        };
    }
    
    private MockResponse handleRequest(RecordedRequest request) {
        String path = request.getPath();
        String method = request.getMethod();
        
        if ("GET".equals(method) && path.startsWith("/api/users/")) {
            String id = path.substring("/api/users/".length());
            
            try {
                Long userId = Long.parseLong(id);
                
                User user = new User();
                user.setId(userId);
                user.setUsername("testuser" + userId);
                user.setEmail("user" + userId + "@example.com");
                user.setCreatedAt(Instant.now());
                
                ObjectMapper mapper = new ObjectMapper();
                mapper.registerModule(new JavaTimeModule());
                
                return new MockResponse()
                    .setResponseCode(200)
                    .setHeader("Content-Type", "application/json")
                    .setBody(mapper.writeValueAsString(user));
                    
            } catch (Exception e) {
                return new MockResponse().setResponseCode(400);
            }
        }
        
        return new MockResponse().setResponseCode(404);
    }
    
    @Test
    public void testGetUser() {
        User user = client.getUserById(123L, "test-request");
        
        assertNotNull(user);
        assertEquals(123L, user.getId());
        assertEquals("testuser123", user.getUsername());
    }
    
    @Test
    public void testCreateUser() {
        // 设置创建用户的响应
        mockServer.setDispatcher(new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) {
                if ("POST".equals(request.getMethod()) && "/api/users".equals(request.getPath())) {
                    try {
                        ObjectMapper mapper = new ObjectMapper();
                        mapper.registerModule(new JavaTimeModule());
                        
                        User createdUser = mapper.readValue(request.getBody().readUtf8(), User.class);
                        createdUser.setId(456L);
                        createdUser.setCreatedAt(Instant.now());
                        
                        return new MockResponse()
                            .setResponseCode(201)
                            .setHeader("Content-Type", "application/json")
                            .setBody(mapper.writeValueAsString(createdUser));
                            
                    } catch (Exception e) {
                        return new MockResponse().setResponseCode(400);
                    }
                }
                return new MockResponse().setResponseCode(404);
            }
        });
        
        User newUser = new User();
        newUser.setUsername("newuser");
        newUser.setEmail("new@example.com");
        
        User createdUser = client.createUser(newUser);
        
        assertNotNull(createdUser);
        assertEquals(456L, createdUser.getId());
        assertEquals("newuser", createdUser.getUsername());
    }
}
4.4.3 监控和诊断

java

复制代码
// 健康检查
public class FeignClientHealthCheck extends HealthCheck {
    
    private final UserServiceApi userService;
    
    public FeignClientHealthCheck(UserServiceApi userService) {
        this.userService = userService;
    }
    
    @Override
    protected Result check() throws Exception {
        try {
            // 执行一个简单的健康检查请求
            long startTime = System.nanoTime();
            userService.getUserById(1L, "health-check");
            long duration = System.nanoTime() - startTime;
            
            // 转换为毫秒
            double responseTimeMs = duration / 1_000_000.0;
            
            Map<String, Object> details = new HashMap<>();
            details.put("response_time_ms", responseTimeMs);
            details.put("timestamp", Instant.now());
            
            if (responseTimeMs > 1000) {
                return Result.unhealthy("Response time too high: " + responseTimeMs + "ms")
                    .withDetails(details);
            }
            
            return Result.healthy()
                .withDetails(details);
            
        } catch (Exception e) {
            return Result.unhealthy("Service unavailable: " + e.getMessage());
        }
    }
}

// 指标报告
public class FeignMetricsReporter {
    
    private final MetricRegistry metricRegistry;
    private final ScheduledExecutorService scheduler;
    
    public FeignMetricsReporter(MetricRegistry metricRegistry) {
        this.metricRegistry = metricRegistry;
        this.scheduler = Executors.newScheduledThreadPool(1);
    }
    
    public void start() {
        // 定期报告指标
        scheduler.scheduleAtFixedRate(this::reportMetrics, 1, 1, TimeUnit.MINUTES);
    }
    
    private void reportMetrics() {
        try {
            // 收集并报告各种指标
            reportRequestMetrics();
            reportResponseTimeMetrics();
            reportErrorMetrics();
            reportConnectionPoolMetrics();
            
        } catch (Exception e) {
            System.err.println("Failed to report metrics: " + e.getMessage());
        }
    }
    
    private void reportRequestMetrics() {
        Counter totalRequests = metricRegistry.counter("feign.requests.total");
        Counter successfulRequests = metricRegistry.counter("feign.requests.success");
        Counter failedRequests = metricRegistry.counter("feign.requests.failed");
        
        System.out.printf("Request Stats - Total: %d, Success: %d, Failed: %d%n",
                         totalRequests.getCount(),
                         successfulRequests.getCount(),
                         failedRequests.getCount());
    }
    
    private void reportResponseTimeMetrics() {
        Histogram responseTimeHistogram = metricRegistry.histogram("feign.response.time");
        Snapshot snapshot = responseTimeHistogram.getSnapshot();
        
        System.out.printf("Response Time - Min: %.2f, Max: %.2f, Mean: %.2f, 95th: %.2f%n",
                         snapshot.getMin(),
                         snapshot.getMax(),
                         snapshot.getMean(),
                         snapshot.get95thPercentile());
    }
    
    public void stop() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

// 分布式追踪
public class TracingInterceptor implements RequestInterceptor {
    
    private final Tracer tracer;
    
    public TracingInterceptor(Tracer tracer) {
        this.tracer = tracer;
    }
    
    @Override
    public void apply(RequestTemplate template) {
        Span currentSpan = tracer.currentSpan();
        if (currentSpan != null) {
            // 添加追踪头
            String traceId = currentSpan.context().traceIdString();
            String spanId = currentSpan.context().spanIdString();
            
            template.header("X-B3-TraceId", traceId);
            template.header("X-B3-SpanId", spanId);
            template.header("X-B3-ParentSpanId", 
                currentSpan.context().parentIdString());
            template.header("X-B3-Sampled", 
                String.valueOf(currentSpan.context().sampled()));
        }
        
        // 开始新的子span
        Tracer.SpanInScope spanInScope = tracer.nextSpan()
            .name("feign-" + template.method() + "-" + template.url())
            .kind(Span.Kind.CLIENT)
            .start()
            .annotate("cs") // client send
            .putTag("http.method", template.method())
            .putTag("http.url", template.url())
            .makeCurrent();
        
        // 将span作用域存储在请求上下文中
        template.header("X-Span-Scope", "active");
    }
}

// 连接池监控
public class ConnectionPoolMonitor {
    
    private final PoolingHttpClientConnectionManager connectionManager;
    private final ScheduledExecutorService monitorService;
    
    public ConnectionPoolMonitor(PoolingHttpClientConnectionManager connectionManager) {
        this.connectionManager = connectionManager;
        this.monitorService = Executors.newScheduledThreadPool(1);
    }
    
    public void startMonitoring() {
        monitorService.scheduleAtFixedRate(this::monitor, 30, 30, TimeUnit.SECONDS);
    }
    
    private void monitor() {
        PoolStats totalStats = connectionManager.getTotalStats();
        Map<String, PoolStats> routeStats = connectionManager.getRoutes();
        
        System.out.println("=== Connection Pool Stats ===");
        System.out.printf("Total Connections: %d%n", totalStats.getAvailable());
        System.out.printf("Total Leased: %d%n", totalStats.getLeased());
        System.out.printf("Total Pending: %d%n", totalStats.getPending());
        System.out.printf("Max Total: %d%n", totalStats.getMax());
        
        routeStats.forEach((route, stats) -> {
            System.out.printf("Route %s: Available=%d, Leased=%d, Pending=%d%n",
                route, stats.getAvailable(), stats.getLeased(), stats.getPending());
        });
        
        // 检查是否有泄漏的连接
        connectionManager.getRoutes().forEach((route, stats) -> {
            if (stats.getLeased() > stats.getMax() * 0.8) {
                System.err.printf("WARNING: High connection usage on route %s: %d/%d%n",
                    route, stats.getLeased(), stats.getMax());
            }
        });
    }
    
    public void shutdown() {
        monitorService.shutdown();
    }
}

总结

本文详细介绍了Spring Cloud Feign在四个方面的深入应用:

  1. 与Spring Cloud Contract集成:展示了如何通过契约测试确保微服务之间的API兼容性,包括生产者端和消费者端的完整配置、契约定义、测试生成和CI/CD集成。

  2. 负载均衡器高级配置:深入探讨了Spring Cloud LoadBalancer的各种配置选项,包括自定义负载均衡策略、区域感知、健康检查、重试机制以及与熔断器的集成。

  3. Feign文件上传:提供了完整的文件上传解决方案,包括单文件上传、多文件上传、大文件分片上传、断点续传、安全验证等高级特性。

  4. 原生Feign API使用:展示了如何在非Spring环境中使用原生Feign API,包括客户端构建、自定义编解码器、拦截器、服务发现、负载均衡、监控和测试等完整实现。

相关推荐
比奇堡鱼贩2 小时前
python第五次作业
开发语言·前端·python
半兽先生2 小时前
使用 retire.js 自动检测前端 JavaScript 库漏洞
开发语言·前端·javascript
前路不黑暗@2 小时前
Java项目:Java脚手架项目的地图服务(十)
java·数据库·spring boot·笔记·学习·spring cloud·maven
014-code3 小时前
Redisson 常用技巧
java·redis
java干货3 小时前
明明删了数据,磁盘却满了?
java
之歆3 小时前
HA 高可用集群指南
java·开发语言
lsx2024063 小时前
电子商务网站主机:选择与维护指南
开发语言
wangluoqi4 小时前
c++ 逆元 小总结
开发语言·c++
BackCatK Chen4 小时前
第十五章 吃透C语言结构与数据形式:struct/union/typedef全解析
c语言·开发语言·数据结构·typedef·结构体·函数指针·联合体