在现代微服务架构中,Spring Boot 已成为构建 RESTful API 的主流框架。然而,仅仅实现接口功能是不够的,生产环境中的接口需要考虑性能、稳定性、安全性和可维护性。本文将从性能优化、异常处理、安全防护和监控运维四个维度,详细阐述 Spring 接口调用的最佳实践。

一、性能优化:让接口飞起来

1.1 异步处理:提升吞吐量

同步阻塞是性能杀手。当接口需要调用多个下游服务时,使用异步处理可以显著提升吞吐量。

最佳实践:使用 CompletableFuture 进行并行调用

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
    
    @Autowired
    private RestTemplate restTemplate;
    
    /**
     * 同步方式 - 性能差
     * 依次调用用户服务、商品服务、库存服务,总耗时 = 三个服务耗时之和
     */
    @GetMapping("/sync/{orderId}")
    public OrderDTO getOrderSync(@PathVariable String orderId) {
        // 调用用户服务
        UserDTO user = restTemplate.getForObject("http://user-service/users/" + orderId, UserDTO.class);
        
        // 调用商品服务
        ProductDTO product = restTemplate.getForObject("http://product-service/products/" + orderId, ProductDTO.class);
        
        // 调用库存服务
        StockDTO stock = restTemplate.getForObject("http://stock-service/stocks/" + orderId, StockDTO.class);
        
        return OrderDTO.builder()
                .user(user)
                .product(product)
                .stock(stock)
                .build();
    }
    
    /**
     * 异步方式 - 性能优
     * 并行调用三个服务,总耗时 = 最慢的那个服务耗时
     */
    @GetMapping("/async/{orderId}")
    public CompletableFuture<OrderDTO> getOrderAsync(@PathVariable String orderId) {
        // 创建线程池(实际项目中应该配置为Bean)
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        // 并行调用三个服务
        CompletableFuture<UserDTO> userFuture = CompletableFuture.supplyAsync(() -> 
            restTemplate.getForObject("http://user-service/users/" + orderId, UserDTO.class), executor);
            
        CompletableFuture<ProductDTO> productFuture = CompletableFuture.supplyAsync(() -> 
            restTemplate.getForObject("http://product-service/products/" + orderId, ProductDTO.class), executor);
            
        CompletableFuture<StockDTO> stockFuture = CompletableFuture.supplyAsync(() -> 
            restTemplate.getForObject("http://stock-service/stocks/" + orderId, StockDTO.class), executor);
        
        // 组合结果
        return userFuture.thenCombineAsync(productFuture, (user, product) -> {
            OrderDTO order = new OrderDTO();
            order.setUser(user);
            order.setProduct(product);
            return order;
        }, executor).thenCombineAsync(stockFuture, (order, stock) -> {
            order.setStock(stock);
            return order;
        }, executor);
    }
    
    /**
     * 使用 @Async 注解(推荐)
     * 需要配置 @EnableAsync
     */
    @Async("customExecutor")
    public CompletableFuture<UserDTO> getUserAsync(String userId) {
        return CompletableFuture.completedFuture(
            restTemplate.getForObject("http://user-service/users/" + userId, UserDTO.class)
        );
    }
}

配置异步线程池:

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "customExecutor")
    public Executor customExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:CPU核心数
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
        // 最大线程数:核心线程数的2倍
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
        // 队列容量
        executor.setQueueCapacity(100);
        // 线程名前缀
        executor.setThreadNamePrefix("async-executor-");
        // 拒绝策略:调用者运行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

1.2 缓存策略:减少重复计算

缓存是提升性能最有效的手段之一。Spring 提供了强大的缓存抽象。

最佳实践:多级缓存策略

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    /**
     * 本地缓存(Caffeine)- 最快
     * 最大容量1000,过期时间5分钟
     */
    @Cacheable(value = "localProducts", 
               key = "#productId",
               cacheManager = "localCacheManager")
    public ProductDTO getProductLocal(String productId) {
        log.info("从数据库查询产品: {}", productId);
        return productRepository.findById(productId)
                .map(this::convertToDTO)
                .orElse(null);
    }
    
    /**
     * 分布式缓存(Redis)- 中等
     * 过期时间1小时
     */
    @Cacheable(value = "redisProducts",
               key = "#productId",
               cacheManager = "redisCacheManager")
    public ProductDTO getProductRedis(String productId) {
        log.info("从数据库查询产品: {}", productId);
        return productRepository.findById(productId)
                .map(this::convertToDTO)
                .orElse(null);
    }
    
    /**
     * 缓存穿透保护:返回空值也缓存
     */
    @Cacheable(value = "redisProducts",
               key = "#productId",
               unless = "#result == null",
               cacheManager = "redisCacheManager")
    public ProductDTO getProductWithNullCache(String productId) {
        return productRepository.findById(productId)
                .map(this::convertToDTO)
                .orElse(null);
    }
    
    /**
     * 缓存更新:更新数据库后删除缓存
     */
    @CacheEvict(value = "redisProducts", key = "#product.id")
    public void updateProduct(ProductDTO product) {
        Product entity = convertToEntity(product);
        productRepository.save(entity);
    }
    
    /**
     * 批量查询:避免缓存击穿
     */
    @Cacheable(value = "redisProducts",
               key = "#productIds",
               cacheManager = "redisCacheManager")
    public List<ProductDTO> getProductsBatch(List<String> productIds) {
        List<Product> products = productRepository.findAllById(productIds);
        return products.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }
}

缓存配置类:

@Configuration
@EnableCaching
public class CacheConfig {
    
    /**
     * 本地缓存配置(Caffeine)
     */
    @Bean
    public CacheManager localCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(1000)
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .recordStats()); // 记录统计信息
        return cacheManager;
    }
    
    /**
     * Redis缓存配置
     */
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues(); // 不缓存null值
        
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

1.3 数据库查询优化

N+1 查询问题是性能杀手,需要特别注意。

最佳实践:使用 @EntityGraph 解决 N+1 问题

@Entity
public class Order {
    @Id
    private String id;
    
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> items;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
}

public interface OrderRepository extends JpaRepository<Order, String> {
    
    /**
     * N+1问题:查询订单后,每个订单的items和user都需要额外查询
     * 会导致:1 + N + N 次查询
     */
    @Query("SELECT o FROM Order o WHERE o.id = :id")
    Optional<Order> findByIdNPlus1(@Param("id") String id);
    
    /**
     * 解决方案1:使用 @EntityGraph
     * 一次性加载所有关联数据
     */
    @EntityGraph(attributePaths = {"items", "user"})
    Optional<Order> findById(String id);
    
    /**
     * 解决方案2:使用 JOIN FETCH
     */
    @Query("SELECT o FROM Order o JOIN FETCH o.items JOIN FETCH o.user WHERE o.id = :id")
    Optional<Order> findByIdWithJoin(@Param("id") String id);
    
    /**
     * 解决方案3:批量查询
     * 先查询订单列表,再批量查询关联数据
     */
    @Query("SELECT o FROM Order o WHERE o.id IN :ids")
    List<Order> findByIds(@Param("ids") List<String> ids);
    
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id IN :ids")
    List<Order> findByIdsWithItems(@Param("ids") List<String> ids);
}

1.4 连接池优化

数据库连接池配置直接影响性能。

最佳实践:HikariCP 优化配置

# application.yml
spring:
  datasource:
    hikari:
      # 连接池名称
      pool-name: MyHikariCP
      # 最小空闲连接数
      minimum-idle: 10
      # 最大连接数(根据业务调整,一般为CPU核心数*2)
      maximum-pool-size: 50
      # 连接超时时间(毫秒)
      connection-timeout: 30000
      # 连接最大生命周期(毫秒)
      max-lifetime: 1800000
      # 空闲连接存活时间(毫秒)
      idle-timeout: 600000
      # 连接测试查询
      connection-test-query: SELECT 1
      # 是否启用JMX
      register-mbeans: true
      # 连接泄漏检测
      leak-detection-threshold: 60000

二、异常处理:优雅地处理错误

2.1 统一异常处理

避免在每个接口中重复写 try-catch,使用 @ControllerAdvice 统一处理。

最佳实践:全局异常处理器

/**
 * 统一异常处理
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .code(e.getCode())
                .message(e.getMessage())
                .path(getCurrentPath())
                .build();
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
    }
    
    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
        log.warn("参数校验失败: {}", e.getMessage());
        
        // 提取具体的字段错误信息
        List<String> errors = e.getBindingResult().getFieldErrors().stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.toList());
        
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .code("VALIDATION_ERROR")
                .message("参数校验失败")
                .details(errors)
                .path(getCurrentPath())
                .build();
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
    }
    
    /**
     * 处理HTTP客户端异常(如404、500等)
     */
    @ExceptionHandler(HttpClientErrorException.class)
    public ResponseEntity<ErrorResponse> handleHttpClientError(HttpClientErrorException e) {
        log.error("HTTP客户端异常: {} - {}", e.getStatusCode(), e.getMessage());
        
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .code("HTTP_CLIENT_ERROR")
                .message("下游服务调用失败")
                .details(Map.of(
                    "status", e.getStatusCode().toString(),
                    "body", e.getResponseBodyAsString()
                ))
                .path(getCurrentPath())
                .build();
        
        return ResponseEntity.status(e.getStatusCode()).body(response);
    }
    
    /**
     * 处理超时异常
     */
    @ExceptionHandler(TimeoutException.class)
    public ResponseEntity<ErrorResponse> handleTimeoutException(TimeoutException e) {
        log.error("请求超时: {}", e.getMessage());
        
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .code("TIMEOUT_ERROR")
                .message("请求超时,请稍后重试")
                .path(getCurrentPath())
                .build();
        
        return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(response);
    }
    
    /**
     * 处理所有未预期的异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("系统异常: ", e);
        
        ErrorResponse response = ErrorResponse.builder()
                .timestamp(LocalDateTime.now())
                .code("SYSTEM_ERROR")
                .message("系统繁忙,请稍后重试")
                .details(e.getMessage())
                .path(getCurrentPath())
                .build();
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
    
    private String getCurrentPath() {
        // 获取当前请求路径
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            return attributes.getRequest().getRequestURI();
        }
        return "unknown";
    }
}

/**
 * 业务异常类
 */
@Getter
public class BusinessException extends RuntimeException {
    private final String code;
    
    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }
    
    public static BusinessException of(String code, String message) {
        return new BusinessException(code, message);
    }
}

/**
 * 错误响应DTO
 */
@Data
@Builder
public class ErrorResponse {
    private LocalDateTime timestamp;
    private String code;
    private String message;
    private Object details;
    private String path;
}

2.2 重试机制

对于网络抖动等临时性故障,使用重试机制可以提高成功率。

最佳实践:使用 Spring Retry

@Service
public class PaymentService {
    
    /**
     * 重试策略:
     * - 最多重试3次
     * - 间隔时间:1秒、2秒、4秒(指数退避)
     * - 只对特定异常重试
     */
    @Retryable(
        value = {HttpClientErrorException.class, TimeoutException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 5000),
        listeners = "retryListener"
    )
    public PaymentResult processPayment(PaymentRequest request) {
        log.info("尝试处理支付: {}", request.getOrderId());
        
        // 调用支付网关
        return restTemplate.postForObject(
            "http://payment-gateway/pay",
            request,
            PaymentResult.class
        );
    }
    
    /**
     * 重试失败后的降级处理
     */
    @Recover
    public PaymentResult recoverPayment(HttpClientErrorException e, PaymentRequest request) {
        log.error("支付重试失败,进入降级处理: {}", request.getOrderId(), e);
        
        // 记录失败订单
        saveFailedOrder(request);
        
        // 返回降级结果
        return PaymentResult.builder()
                .success(false)
                .orderId(request.getOrderId())
                .message("支付处理失败,请稍后重试")
                .build();
    }
    
    /**
     * 重试失败后的降级处理(TimeoutException)
     */
    @Recover
    public PaymentResult recoverPayment(TimeoutException e, PaymentRequest request) {
        log.error("支付超时,进入降级处理: {}", request.getOrderId(), e);
        
        // 发送延迟消息,异步重试
        messageQueueService.sendDelayedMessage("payment-retry", request, 300);
        
        return PaymentResult.builder()
                .success(false)
                .orderId(request.getOrderId())
                .message("支付处理中,请稍后查询结果")
                .build();
    }
    
    private void saveFailedOrder(PaymentRequest request) {
        // 保存失败订单到数据库
    }
}

/**
 * 重试监听器
 */
@Component
public class RetryListener implements RetryListener {
    
    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        return true;
    }
    
    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        // 重试结束
    }
    
    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        log.warn("重试失败: 第{}次,异常: {}", context.getRetryCount(), throwable.getClass().getSimpleName());
    }
}

/**
 * 启用重试
 */
@SpringBootApplication
@EnableRetry
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2.3 熔断器:防止级联故障

当下游服务不可用时,熔断器可以快速失败,防止故障扩散。

最佳实践:使用 Resilience4j

@Service
public class OrderService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    /**
     * 熔断器配置
     * - 失败率超过50%时熔断
     * - 最小调用次数10次
     * - 熔断持续时间30秒
     * - 半开状态允许1次试探调用
     */
    @CircuitBreaker(name = "orderService", fallbackMethod = "createOrderFallback")
    @RateLimiter(name = "orderService", fallbackMethod = "rateLimitFallback")
    @Retry(name = "orderService", fallbackMethod = "retryFallback")
    public OrderDTO createOrder(OrderRequest request) {
        // 调用库存服务检查库存
        StockDTO stock = restTemplate.getForObject(
            "http://stock-service/check/" + request.getProductId(),
            StockDTO.class
        );
        
        if (stock == null || stock.getQuantity() < request.getQuantity()) {
            throw new BusinessException("INSUFFICIENT_STOCK", "库存不足");
        }
        
        // 创建订单
        OrderDTO order = OrderDTO.builder()
                .id(UUID.randomUUID().toString())
                .productId(request.getProductId())
                .quantity(request.getQuantity())
                .status("CREATED")
                .build();
        
        // 异步扣减库存
        CompletableFuture.runAsync(() -> {
            restTemplate.postForObject(
                "http://stock-service/deduct",
                DeductRequest.builder()
                        .productId(request.getProductId())
                        .quantity(request.getQuantity())
                        .build(),
                Void.class
            );
        });
        
        return order;
    }
    
    /**
     * 熔断降级
     */
    public OrderDTO createOrderFallback(OrderRequest request, Exception e) {
        log.error("订单创建熔断降级: {}", request.getOrderId(), e);
        
        // 返回缓存的默认订单或排队处理
        return OrderDTO.builder()
                .id(request.getOrderId())
                .status("PENDING")
                .message("系统繁忙,订单已排队处理")
                .build();
    }
    
    /**
     * 限流降级
     */
    public OrderDTO rateLimitFallback(OrderRequest request, Exception e) {
        log.warn("请求被限流: {}", request.getOrderId());
        
        throw new BusinessException("RATE_LIMIT", "请求过于频繁,请稍后重试");
    }
    
    /**
     * 重试降级
     */
    public OrderDTO retryFallback(OrderRequest request, Exception e) {
        log.error("重试失败: {}", request.getOrderId(), e);
        
        // 发送到消息队列异步处理
        messageQueueService.send("order-queue", request);
        
        return OrderDTO.builder()
                .id(request.getOrderId())
                .status("QUEUED")
                .message("订单已加入队列异步处理")
                .build();
    }
}

/**
 * 熔断器配置类
 */
@Configuration
public class Resilience4jConfig {
    
    @Bean
    public Customizer<Resilience4JCircuitBreakerFactory> circuitBreakerCustomizer() {
        return factory -> factory.configure(builder -> builder
                .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()
                        .toBuilder()
                        .failureRateThreshold(50)
                        .waitDurationInOpenState(Duration.ofSeconds(30))
                        .permittedNumberOfCallsInHalfOpenState(1)
                        .slidingWindowSize(10)
                        .build()),
                "orderService");
    }
    
    @Bean
    public Customizer<Resilience4JRateLimiterFactory> rateLimiterCustomizer() {
        return factory -> factory.configure(builder -> builder
                .rateLimiterConfig(RateLimiterConfig.ofDefaults()
                        .toBuilder()
                        .limitForPeriod(10)  // 每秒10个请求
                        .limitRefreshPeriod(Duration.ofSeconds(1))
                        .timeoutDuration(Duration.ofMillis(100))
                        .build()),
                "orderService");
    }
}

三、安全防护:保护你的接口

3.1 请求签名验证

防止请求被篡改,确保请求的完整性。

最佳实践:HMAC 签名验证

/**
 * 请求签名验证拦截器
 */
@Component
public class SignatureInterceptor implements HandlerInterceptor {
    
    private static final String SIGNATURE_HEADER = "X-Signature";
    private static final String TIMESTAMP_HEADER = "X-Timestamp";
    private static final String NONCE_HEADER = "X-Nonce";
    
    @Value("${api.secret-key}")
    private String secretKey;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求参数
        String signature = request.getHeader(SIGNATURE_HEADER);
        String timestamp = request.getHeader(TIMESTAMP_HEADER);
        String nonce = request.getHeader(NONCE_HEADER);
        
        // 1. 验证时间戳(防止重放攻击)
        if (!validateTimestamp(timestamp)) {
            throw new SecurityException("INVALID_TIMESTAMP", "时间戳无效");
        }
        
        // 2. 验证nonce(防止重放攻击)
        if (!validateNonce(nonce)) {
            throw new SecurityException("INVALID_NONCE", "nonce无效");
        }
        
        // 3. 验证签名
        String calculatedSignature = calculateSignature(request, timestamp, nonce);
        if (!calculatedSignature.equals(signature)) {
            throw new SecurityException("INVALID_SIGNATURE", "签名验证失败");
        }
        
        return true;
    }
    
    private boolean validateTimestamp(String timestamp) {
        try {
            long requestTime = Long.parseLong(timestamp);
            long currentTime = System.currentTimeMillis() / 1000;
            // 时间戳误差不超过5分钟
            return Math.abs(currentTime - requestTime) <= 300;
        } catch (Exception e) {
            return false;
        }
    }
    
    private boolean validateNonce(String nonce) {
        // 使用Redis检查nonce是否已使用(5分钟内)
        String key = "nonce:" + nonce;
        Boolean exists = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(exists)) {
            return false;
        }
        // 设置5分钟过期
        redisTemplate.opsForValue().set(key, "1", 5, TimeUnit.MINUTES);
        return true;
    }
    
    private String calculateSignature(HttpServletRequest request, String timestamp, String nonce) throws Exception {
        // 获取请求体
        String body = getRequestBody(request);
        
        // 构建签名字符串:secretKey + timestamp + nonce + body
        String data = secretKey + timestamp + nonce + body;
        
        // 使用HMAC-SHA256计算签名
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        
        return Base64.getEncoder().encodeToString(hash);
    }
    
    private String getRequestBody(HttpServletRequest request) throws IOException {
        if (request.getMethod().equalsIgnoreCase("GET")) {
            // GET请求使用查询参数
            Map<String, String[]> parameterMap = request.getParameterMap();
            return parameterMap.entrySet().stream()
                    .sorted(Map.Entry.comparingByKey())
                    .map(e -> e.getKey() + "=" + String.join(",", e.getValue()))
                    .collect(Collectors.joining("&"));
        } else {
            // POST/PUT请求使用请求体
            try (BufferedReader reader = request.getReader()) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                return sb.toString();
            }
        }
    }
}

/**
 * 注册拦截器
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private SignatureInterceptor signatureInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signatureInterceptor)
                .addPathPatterns("/api/secure/**")
                .excludePathPatterns("/api/public/**");
    }
}

3.2 接口限流

防止接口被滥用,保护系统资源。

最佳实践:使用 Redis + Lua 脚本实现分布式限流

/**
 * 分布式限流器
 */
@Component
public class DistributedRateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 使用Lua脚本保证原子性
     * 限流算法:令牌桶
     */
    private static final String LUA_SCRIPT = 
            "local key = KEYS[1] " +
            "local limit = tonumber(ARGV[1]) " +
            "local expire = tonumber(ARGV[2]) " +
            "local current = tonumber(redis.call('GET', key) or '0') " +
            "if current + 1 > limit then " +
            "    return 0 " +
            "else " +
            "    redis.call('INCR', key) " +
            "    if current == 0 then " +
            "        redis.call('EXPIRE', key, expire) " +
            "    end " +
            "    return 1 " +
            "end";
    
    /**
     * 尝试获取令牌
     * @param key 限流键(如:userId或IP)
     * @param limit 限制数量
     * @param expire 过期时间(秒)
     * @return true-获取成功,false-获取失败
     */
    public boolean tryAcquire(String key, int limit, int expire) {
        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(LUA_SCRIPT, Long.class),
                Collections.singletonList("rate_limit:" + key),
                String.valueOf(limit),
                String.valueOf(expire)
        );
        return result != null && result == 1;
    }
    
    /**
     * 获取剩余令牌数
     */
    public int getRemaining(String key) {
        String value = redisTemplate.opsForValue().get("rate_limit:" + key);
        return value != null ? Integer.parseInt(value) : 0;
    }
    
    /**
     * 重置限流
     */
    public void reset(String key) {
        redisTemplate.delete("rate_limit:" + key);
    }
}

/**
 * 接口限流注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    /**
     * 限流标识(支持SpEL表达式)
     */
    String key() default "";
    
    /**
     * 限制数量
     */
    int limit() default 60;
    
    /**
     * 时间窗口(秒)
     */
    int timeWindow() default 60;
    
    /**
     * 限流类型:IP、USER、CUSTOM
     */
    LimitType type() default LimitType.IP;
}

public enum LimitType {
    IP,      // 基于IP限流
    USER,    // 基于用户ID限流
    CUSTOM   // 自定义key
}

/**
 * AOP实现限流
 */
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
    
    @Autowired
    private DistributedRateLimiter rateLimiter;
    
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        // 构建限流key
        String key = buildKey(rateLimit, joinPoint);
        
        // 尝试获取令牌
        boolean allowed = rateLimiter.tryAcquire(key, rateLimit.limit(), rateLimit.timeWindow());
        
        if (!allowed) {
            log.warn("限流触发: {}", key);
            throw new BusinessException("RATE_LIMIT_EXCEEDED", "请求过于频繁,请稍后重试");
        }
        
        log.debug("限流通过: {}", key);
        return joinPoint.proceed();
    }
    
    private String buildKey(RateLimit rateLimit, ProceedingJoinPoint joinPoint) {
        StringBuilder key = new StringBuilder();
        
        // 根据类型构建key
        switch (rateLimit.type()) {
            case IP:
                key.append("IP:").append(getIpAddress());
                break;
            case USER:
                key.append("USER:").append(getCurrentUserId());
                break;
            case CUSTOM:
                // 解析SpEL表达式
                key.append(rateLimit.key());
                break;
        }
        
        return key.toString();
    }
    
    private String getIpAddress() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String ip = request.getHeader("X-Forwarded-For");
            if (ip == null || ip.isEmpty()) {
                ip = request.getRemoteAddr();
            }
            return ip;
        }
        return "unknown";
    }
    
    private String getCurrentUserId() {
        // 从SecurityContext或Token中获取用户ID
        return "mock_user_id";
    }
}

/**
 * 使用示例
 */
@RestController
@RequestMapping("/api")
public class ApiController {
    
    @Autowired
    private DistributedRateLimiter rateLimiter;
    
    /**
     * 限制每个IP每分钟最多60次请求
     */
    @RateLimit(type = LimitType.IP, limit = 60, timeWindow = 60)
    @GetMapping("/public/data")
    public ResponseEntity<String> getData() {
        return ResponseEntity.ok("success");
    }
    
    /**
     * 限制每个用户每秒最多10次请求
     */
    @RateLimit(type = LimitType.USER, limit = 10, timeWindow = 1)
    @PostMapping("/user/action")
    public ResponseEntity<String> userAction() {
        return ResponseEntity.ok("success");
    }
    
    /**
     * 自定义限流key(基于用户ID+接口)
     */
    @RateLimit(key = "#userId + ':' + 'order_create'", limit = 5, timeWindow = 60)
    @PostMapping("/order/create")
    public ResponseEntity<String> createOrder(@RequestParam String userId) {
        return ResponseEntity.ok("success");
    }
}

3.3 输入参数校验

严格的输入校验是安全的第一道防线。

最佳实践:使用 Bean Validation + 自定义校验器

/**
 * 创建订单请求
 */
@Data
public class OrderCreateRequest {
    
    @NotBlank(message = "用户ID不能为空")
    @Size(min = 1, max = 50, message = "用户ID长度必须在1-50之间")
    private String userId;
    
    @NotBlank(message = "产品ID不能为空")
    private String productId;
    
    @Min(value = 1, message = "数量必须大于0")
    @Max(value = 1000, message = "单次最多购买1000件")
    private Integer quantity;
    
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
    
    @DecimalMin(value = "0.01", message = "金额必须大于0")
    @DecimalMax(value = "999999.99", message = "金额不能超过999999.99")
    private BigDecimal amount;
    
    @Future(message = "期望送达时间必须是未来时间")
    private LocalDateTime expectedDeliveryTime;
    
    @Valid
    private Address address;
    
    @Size(min = 1, max = 10, message = "商品规格最多选择10项")
    private List<@Valid ProductSpec> specs;
}

/**
 * 地址对象
 */
@Data
public class Address {
    @NotBlank(message = "省不能为空")
    private String province;
    
    @NotBlank(message = "市不能为空")
    private String city;
    
    @NotBlank(message = "详细地址不能为空")
    @Size(max = 200, message = "详细地址不能超过200字")
    private String detail;
    
    @Pattern(regexp = "\\d{6}", message = "邮政编码格式不正确")
    private String zipCode;
}

/**
 * 商品规格
 */
@Data
public class ProductSpec {
    @NotBlank(message = "规格名称不能为空")
    private String name;
    
    @NotBlank(message = "规格值不能为空")
    private String value;
}

/**
 * 自定义校验注解:检查库存
 */
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StockValidator.class)
public @interface ValidStock {
    String message() default "库存不足";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

/**
 * 自定义校验器
 */
public class StockValidator implements ConstraintValidator<ValidStock, OrderCreateRequest> {
    
    @Autowired
    private StockService stockService;
    
    @Override
    public boolean isValid(OrderCreateRequest request, ConstraintValidatorContext context) {
        if (request == null || request.getProductId() == null) {
            return false;
        }
        
        // 调用库存服务检查
        StockDTO stock = stockService.checkStock(request.getProductId());
        if (stock == null || stock.getQuantity() < request.getQuantity()) {
            // 自定义错误消息
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                "产品" + request.getProductId() + "库存不足,当前库存:" + 
                (stock != null ? stock.getQuantity() : 0)
            ).addConstraintViolation();
            return false;
        }
        
        return true;
    }
}

/**
 * 控制器中使用校验
 */
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    /**
     * 使用 @Valid 触发校验
     */
    @PostMapping
    public ResponseEntity<OrderDTO> createOrder(@Valid @RequestBody OrderCreateRequest request) {
        // 校验通过,执行业务逻辑
        OrderDTO order = orderService.create(request);
        return ResponseEntity.ok(order);
    }
    
    /**
     * 分组校验:不同场景使用不同校验规则
     */
    @PutMapping("/{id}")
    public ResponseEntity<OrderDTO> updateOrder(
            @PathVariable String id,
            @Validated(UpdateGroup.class) @RequestBody OrderCreateRequest request) {
        // 更新逻辑
        return ResponseEntity.ok(orderService.update(id, request));
    }
}

/**
 * 校验分组接口
 */
public interface UpdateGroup {
}

3.4 防止 SQL 注入和 XSS 攻击

最佳实践:使用预编译语句和参数化查询

/**
 * 正确的数据库操作方式
 */
@Repository
public class UserRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    /**
     * 错误示例:字符串拼接,存在SQL注入风险
     */
    public List<User> findUsersByKeywordWrong(String keyword) {
        // 危险!存在SQL注入风险
        String sql = "SELECT * FROM user WHERE name LIKE '%" + keyword + "%'";
        return entityManager.createNativeQuery(sql, User.class).getResultList();
    }
    
    /**
     * 正确示例:使用参数化查询
     */
    public List<User> findUsersByKeywordCorrect(String keyword) {
        // 安全!使用参数化查询
        String jpql = "SELECT u FROM User u WHERE u.name LIKE :keyword";
        return entityManager.createQuery(jpql, User.class)
                .setParameter("keyword", "%" + keyword + "%")
                .getResultList();
    }
    
    /**
     * 使用原生SQL的正确方式
     */
    public List<User> findUsersByKeywordNative(String keyword) {
        Query query = entityManager.createNativeQuery(
                "SELECT * FROM user WHERE name LIKE :keyword", 
                User.class
        );
        query.setParameter("keyword", "%" + keyword + "%");
        return query.getResultList();
    }
}

/**
 * 防止XSS攻击:输入过滤和输出转义
 */
@Component
public class XssFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
    }
}

/**
 * XSS包装器:过滤请求参数
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return xssClean(value);
    }
    
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) return null;
        
        String[] cleanValues = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            cleanValues[i] = xssClean(values[i]);
        }
        return cleanValues;
    }
    
    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> map = super.getParameterMap();
        Map<String, String[]> cleanMap = new HashMap<>();
        
        for (Map.Entry<String, String[]> entry : map.entrySet()) {
            String[] values = entry.getValue();
            String[] cleanValues = new String[values.length];
            for (int i = 0; i < values.length; i++) {
                cleanValues[i] = xssClean(values[i]);
            }
            cleanMap.put(entry.getKey(), cleanValues);
        }
        return cleanMap;
    }
    
    /**
     * XSS过滤:使用正则表达式移除危险标签
     */
    private String xssClean(String value) {
        if (value == null) {
            return null;
        }
        
        // 移除脚本标签
        value = value.replaceAll("<script[^>]*>([\\s\\S]*?)</script>", "");
        value = value.replaceAll("<style[^>]*>([\\s\\S]*?)</style>", "");
        
        // 移除事件处理器
        value = value.replaceAll("on\\w+\\s*=", "");
        
        // 转义特殊字符
        value = value.replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace("\"", "&quot;")
                    .replace("'", "&#x27;");
        
        return value;
    }
}

/**
 * 输出时的XSS防护(JSON响应)
 */
@RestControllerAdvice
public class XssResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, 
                                  MediaType selectedContentType, 
                                  Class<? extends HttpMessageConverter<?>> converterType,
                                  HttpServletRequest request, HttpServletResponse response) {
        if (body instanceof String) {
            return xssClean((String) body);
        }
        // 对于复杂对象,可以使用Jackson的Serializer
        return body;
    }
    
    private String xssClean(String value) {
        if (value == null) return null;
        return value.replace("<", "&lt;").replace(">", "&gt;");
    }
}

四、监控与运维:让系统可观测

4.1 请求日志记录

记录详细的请求日志,便于问题排查。

最佳实践:使用 MDC 和切面记录日志

/**
 * 请求日志切面
 */
@Aspect
@Component
@Slf4j
public class RequestLogAspect {
    
    private static final String REQUEST_ID = "requestId";
    private static final String START_TIME = "startTime";
    
    /**
     * 切点:所有Controller方法
     */
    @Around("execution(* com.example.controller..*.*(..))")
    public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        
        // 生成请求ID
        String requestId = generateRequestId();
        
        // 设置MDC(用于日志追踪)
        MDC.put(REQUEST_ID, requestId);
        MDC.put("method", request.getMethod());
        MDC.put("path", request.getRequestURI());
        MDC.put("clientIp", getClientIp(request));
        
        long startTime = System.currentTimeMillis();
        MDC.put(START_TIME, String.valueOf(startTime));
        
        // 记录请求参数
        log.info("请求开始: {} {} {}", 
                request.getMethod(), 
                request.getRequestURI(),
                getParameters(request));
        
        try {
            // 执行原方法
            Object result = joinPoint.proceed();
            
            long duration = System.currentTimeMillis() - startTime;
            log.info("请求成功: {} {} - 耗时: {}ms", 
                    request.getMethod(), 
                    request.getRequestURI(), 
                    duration);
            
            return result;
            
        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            log.error("请求失败: {} {} - 耗时: {}ms - 错误: {}", 
                    request.getMethod(), 
                    request.getRequestURI(), 
                    duration, 
                    e.getMessage(), 
                    e);
            throw e;
            
        } finally {
            // 清理MDC
            MDC.clear();
        }
    }
    
    private String generateRequestId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
    
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
    
    private String getParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap.isEmpty()) {
            return "";
        }
        
        return parameterMap.entrySet().stream()
                .map(e -> e.getKey() + "=" + String.join(",", e.getValue()))
                .collect(Collectors.joining("&"));
    }
}

/**
 * 异步日志配置(logback-spring.xml)
 */
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 异步日志 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <queueSize>10000</queueSize>
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="FILE"/>
    </appender>
    
    <!-- 文件日志 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="ASYNC"/>
    </root>
</configuration>

4.2 指标监控

使用 Micrometer 收集性能指标。

最佳实践:自定义指标和健康检查

/**
 * 自定义指标收集
 */
@Component
public class CustomMetrics {
    
    private final MeterRegistry meterRegistry;
    
    // 计数器:订单创建数量
    private Counter orderCreatedCounter;
    
    // 计时器:订单处理耗时
    private Timer orderProcessTimer;
    
    // 仪表:当前活跃连接数
    private AtomicInteger activeConnections;
    
    public CustomMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        initMetrics();
    }
    
    private void initMetrics() {
        // 初始化计数器
        orderCreatedCounter = Counter.builder("order.created")
                .description("订单创建数量")
                .tag("service", "order-service")
                .register(meterRegistry);
        
        // 初始化计时器
        orderProcessTimer = Timer.builder("order.process.time")
                .description("订单处理耗时")
                .tag("service", "order-service")
                .publishPercentiles(0.5, 0.95, 0.99) // 百分位
                .register(meterRegistry);
        
        // 初始化仪表
        activeConnections = new AtomicInteger(0);
        Gauge.builder("connection.active", activeConnections, AtomicInteger::get)
                .description("当前活跃连接数")
                .register(meterRegistry);
    }
    
    public void recordOrderCreated() {
        orderCreatedCounter.increment();
    }
    
    public <T> T recordOrderProcess(Supplier<T> supplier) {
        return orderProcessTimer.record(supplier);
    }
    
    public void incrementActiveConnection() {
        activeConnections.incrementAndGet();
    }
    
    public void decrementActiveConnection() {
        activeConnections.decrementAndGet();
    }
}

/**
 * 自定义健康检查
 */
@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Override
    public Health health() {
        Health.Builder builder = Health.up();
        
        // 检查数据库
        try (Connection connection = dataSource.getConnection()) {
            connection.createStatement().execute("SELECT 1");
            builder.withDetail("database", "UP");
        } catch (Exception e) {
            builder.down().withDetail("database", "DOWN").withException(e);
        }
        
        // 检查Redis
        try {
            redisTemplate.getConnectionFactory().getConnection().ping();
            builder.withDetail("redis", "UP");
        } catch (Exception e) {
            builder.down().withDetail("redis", "DOWN").withException(e);
        }
        
        // 检查磁盘空间
        File root = new File("/");
        long freeSpace = root.getFreeSpace();
        long totalSpace = root.getTotalSpace();
        double usedPercent = 100.0 * (totalSpace - freeSpace) / totalSpace;
        
        if (usedPercent > 90) {
            builder.status("WARNING").withDetail("disk", "磁盘空间不足90%");
        } else {
            builder.withDetail("disk", "OK");
        }
        
        return builder.build();
    }
}

/**
 * 应用配置
 */
@Configuration
public class ActuatorConfig {
    
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
        return registry -> registry.config()
                .commonTags("application", "order-service")
                .commonTags("environment", System.getProperty("spring.profiles.active", "dev"));
    }
}

4.3 接口文档

使用 Swagger/OpenAPI 自动生成接口文档。

最佳实践:详细的文档注解

/**
 * 订单接口文档
 */
@RestController
@RequestMapping("/api/v1/orders")
@Tag(name = "订单管理", description = "订单相关接口")
public class OrderApiDocController {
    
    @Autowired
    private OrderService orderService;
    
    @Operation(
            summary = "创建订单",
            description = "根据用户ID和产品信息创建订单,支持批量购买",
            responses = {
                    @ApiResponse(responseCode = "200", description = "订单创建成功",
                            content = @Content(schema = @Schema(implementation = OrderDTO.class))),
                    @ApiResponse(responseCode = "400", description = "参数错误或库存不足",
                            content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
                    @ApiResponse(responseCode = "429", description = "请求过于频繁",
                            content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
            }
    )
    @PostMapping
    @RateLimit(type = LimitType.USER, limit = 10, timeWindow = 60)
    public ResponseEntity<OrderDTO> createOrder(
            @RequestBody @Valid OrderCreateRequest request,
            @RequestHeader(value = "X-Signature", required = false) String signature,
            @RequestHeader(value = "X-Timestamp") String timestamp) {
        
        OrderDTO order = orderService.create(request);
        return ResponseEntity.ok(order);
    }
    
    @Operation(
            summary = "查询订单详情",
            description = "根据订单ID查询订单详细信息,包含商品和用户信息",
            parameters = {
                    @Parameter(name = "id", description = "订单ID", required = true, example = "ORD-20240101-001")
            }
    )
    @GetMapping("/{id}")
    public ResponseEntity<OrderDTO> getOrder(
            @PathVariable @Pattern(regexp = "^ORD-\\d{8}-\\d{3}$", message = "订单ID格式不正确") String id) {
        
        OrderDTO order = orderService.findById(id)
                .orElseThrow(() -> new BusinessException("ORDER_NOT_FOUND", "订单不存在"));
        
        return ResponseEntity.ok(order);
    }
    
    @Operation(
            summary = "分页查询订单列表",
            description = "支持按状态、时间范围分页查询订单"
    )
    @GetMapping
    public ResponseEntity<Page<OrderDTO>> listOrders(
            @RequestParam(defaultValue = "1") @Min(1) Integer page,
            @RequestParam(defaultValue = "10") @Min(1) @Max(100) Integer size,
            @RequestParam(required = false) String status,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        
        Pageable pageable = PageRequest.of(page - 1, size, Sort.by("createTime").descending());
        Page<OrderDTO> result = orderService.list(status, startDate, endDate, pageable);
        
        return ResponseEntity.ok(result);
    }
}

Swagger 配置:

@Configuration
public class SwaggerConfig {
    
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("订单服务 API")
                        .version("1.0")
                        .description("订单管理服务接口文档")
                        .contact(new Contact()
                                .name("技术团队")
                                .email("tech@example.com")
                                .url("https://example.com"))
                        .license(new License()
                                .name("Apache 2.0")
                                .url("http://springdoc.org")));
    }
}

五、配置最佳实践

5.1 配置管理

最佳实践:使用 @ConfigurationProperties 进行类型安全的配置

/**
 * API配置类
 */
@Configuration
@ConfigurationProperties(prefix = "app.api")
@Data
public class ApiConfigProperties {
    
    /**
     * 超时配置
     */
    private Timeout timeout = new Timeout();
    
    /**
     * 重试配置
     */
    private Retry retry = new Retry();
    
    /**
     * 限流配置
     */
    private RateLimit rateLimit = new RateLimit();
    
    /**
     * 缓存配置
     */
    private Cache cache = new Cache();
    
    @Data
    public static class Timeout {
        private int connect = 5000;
        private int read = 10000;
        private int write = 5000;
    }
    
    @Data
    public static class Retry {
        private int maxAttempts = 3;
        private long backoff = 1000;
        private double multiplier = 2.0;
    }
    
    @Data
    public static class RateLimit {
        private int defaultLimit = 60;
        private int defaultWindow = 60;
        private Map<String, Integer> customLimits = new HashMap<>();
    }
    
    @Data
    public static class Cache {
        private long localTtl = 300;
        private long redisTtl = 3600;
        private int localMaxSize = 1000;
    }
}

/**
 * 使用配置
 */
@Service
public class ConfigDrivenService {
    
    @Autowired
    private ApiConfigProperties config;
    
    public void doSomething() {
        // 使用配置
        int connectTimeout = config.getTimeout().getConnect();
        int retryAttempts = config.getRetry().getMaxAttempts();
        
        // 自定义限流规则
        String customKey = "order_create";
        Integer customLimit = config.getRateLimit().getCustomLimits().get(customKey);
        if (customLimit == null) {
            customLimit = config.getRateLimit().getDefaultLimit();
        }
    }
}

application.yml 配置示例:

app:
  api:
    timeout:
      connect: 5000
      read: 10000
      write: 5000
    retry:
      maxAttempts: 3
      backoff: 1000
      multiplier: 2.0
    rate-limit:
      defaultLimit: 60
      defaultWindow: 60
      customLimits:
        order_create: 10
        payment_submit: 5
    cache:
      localTtl: 300
      redisTtl: 3600
      localMaxSize: 1000

5.2 多环境配置

最佳实践:使用 Spring Profile 和配置文件分离

# application.yml(公共配置)
spring:
  application:
    name: order-service
  profiles:
    active: dev
    
---
# 开发环境
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:mysql://localhost:3306/order_dev?useSSL=false
    username: dev_user
    password: dev_pass
  redis:
    host: localhost
    port: 6379
    
app:
  api:
    timeout:
      connect: 3000
    rate-limit:
      defaultLimit: 1000
      
logging:
  level:
    com.example: DEBUG
    
---
# 生产环境
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://mysql-cluster:3306/order_prod?useSSL=true&serverTimezone=UTC
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 50
      connection-timeout: 30000
  redis:
    host: redis-cluster
    port: 6379
    password: ${REDIS_PASSWORD}
    ssl: true
    
app:
  api:
    timeout:
      connect: 5000
      read: 15000
    rate-limit:
      defaultLimit: 60
      
logging:
  level:
    com.example: INFO
  file:
    name: /var/log/order-service/app.log

六、测试最佳实践

6.1 单元测试

最佳实践:使用 JUnit 5 + Mockito

@SpringBootTest
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    
    @Mock
    private OrderRepository orderRepository;
    
    @Mock
    private RestTemplate restTemplate;
    
    @Mock
    private DistributedRateLimiter rateLimiter;
    
    @InjectMocks
    private OrderService orderService;
    
    @Test
    @DisplayName("创建订单成功")
    void createOrderSuccess() {
        // Given
        OrderCreateRequest request = new OrderCreateRequest();
        request.setUserId("U001");
        request.setProductId("P001");
        request.setQuantity(2);
        
        // Mock库存检查
        StockDTO stock = new StockDTO();
        stock.setQuantity(100);
        when(restTemplate.getForObject(
            "http://stock-service/check/P001", 
            StockDTO.class
        )).thenReturn(stock);
        
        // Mock限流检查
        when(rateLimiter.tryAcquire(anyString(), anyInt(), anyInt())).thenReturn(true);
        
        // Mock保存订单
        Order savedOrder = new Order();
        savedOrder.setId("ORD-001");
        when(orderRepository.save(any(Order.class))).thenReturn(savedOrder);
        
        // When
        OrderDTO result = orderService.create(request);
        
        // Then
        assertNotNull(result);
        assertEquals("ORD-001", result.getId());
        verify(orderRepository, times(1)).save(any(Order.class));
        verify(restTemplate, times(1)).getForObject(anyString(), any());
    }
    
    @Test
    @DisplayName("库存不足时抛出异常")
    void createOrderInsufficientStock() {
        // Given
        OrderCreateRequest request = new OrderCreateRequest();
        request.setProductId("P001");
        request.setQuantity(1000);
        
        StockDTO stock = new StockDTO();
        stock.setQuantity(10);
        when(restTemplate.getForObject(anyString(), any())).thenReturn(stock);
        
        // When & Then
        assertThrows(BusinessException.class, () -> {
            orderService.create(request);
        });
    }
    
    @Test
    @DisplayName("异步创建订单")
    void createOrderAsync() throws Exception {
        // Given
        OrderCreateRequest request = new OrderCreateRequest();
        
        // Mock异步操作
        CompletableFuture<OrderDTO> future = CompletableFuture.supplyAsync(() -> {
            OrderDTO dto = new OrderDTO();
            dto.setId("ASYNC-001");
            return dto;
        });
        
        when(orderRepository.saveAsync(any())).thenReturn(future);
        
        // When
        CompletableFuture<OrderDTO> result = orderService.createAsync(request);
        
        // Then
        OrderDTO order = result.get(5, TimeUnit.SECONDS);
        assertEquals("ASYNC-001", order.getId());
    }
}

6.2 集成测试

最佳实践:使用 Testcontainers 进行真实环境测试

@SpringBootTest
@Testcontainers
class OrderIntegrationTest {
    
    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");
    
    @Container
    static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
            .withExposedPorts(6379);
    
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysql::getJdbcUrl);
        registry.add("spring.datasource.username", mysql::getUsername);
        registry.add("spring.datasource.password", mysql::getPassword);
        registry.add("spring.redis.host", redis::getHost);
        registry.add("spring.redis.port", redis::getFirstMappedPort()::toString);
    }
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Test
    @DisplayName("完整流程集成测试")
    void fullIntegrationTest() {
        // 1. 创建订单
        OrderCreateRequest request = new OrderCreateRequest();
        request.setUserId("U001");
        request.setProductId("P001");
        request.setQuantity(1);
        
        ResponseEntity<OrderDTO> createResponse = restTemplate.postForEntity(
            "/api/orders",
            request,
            OrderDTO.class
        );
        
        assertEquals(HttpStatus.OK, createResponse.getStatusCode());
        assertNotNull(createResponse.getBody());
        
        String orderId = createResponse.getBody().getId();
        
        // 2. 查询订单
        ResponseEntity<OrderDTO> getResponse = restTemplate.getForEntity(
            "/api/orders/" + orderId,
            OrderDTO.class
        );
        
        assertEquals(HttpStatus.OK, getResponse.getStatusCode());
        assertEquals(orderId, getResponse.getBody().getId());
        
        // 3. 验证缓存
        // 再次查询应该走缓存
        long start = System.currentTimeMillis();
        restTemplate.getForEntity("/api/orders/" + orderId, OrderDTO.class);
        long duration = System.currentTimeMillis() - start;
        
        // 缓存命中应该非常快(< 10ms)
        assertTrue(duration < 10);
    }
}

七、总结

Spring 接口调用的最佳实践是一个系统工程,需要从多个维度综合考虑:

性能优化要点

  1. 异步处理:使用 CompletableFuture 或 @Async 提升吞吐量
  2. 缓存策略:多级缓存(本地 + 分布式),防止缓存穿透、击穿、雪崩
  3. 数据库优化:解决 N+1 问题,合理使用连接池
  4. 批量处理:减少网络调用次数

异常处理要点

  1. 统一异常处理:使用 @ControllerAdvice 集中处理
  2. 重试机制:对临时性故障进行重试(指数退避)
  3. 熔断降级:防止级联故障,快速失败
  4. 优雅降级:提供备选方案,保证核心功能可用

安全防护要点

  1. 请求签名:防止请求被篡改
  2. 接口限流:保护系统资源,防止滥用
  3. 参数校验:严格的输入验证
  4. 安全防护:防止 SQL 注入、XSS 攻击

监控运维要点

  1. 日志追踪:使用 MDC 实现请求链路追踪
  2. 指标监控:收集关键指标,设置告警
  3. 健康检查:实时监控系统状态
  4. 文档完善:自动生成接口文档

通过遵循这些最佳实践,可以构建出高性能、高可用、安全可靠的 Spring 接口服务。记住,最佳实践不是一成不变的,需要根据具体业务场景和系统规模进行调整和优化。