3.1 服务调用概述
微服务间通信方式
在微服务架构中,服务间通信是核心问题之一。常见的通信方式包括:
同步通信
- HTTP/REST API
- RPC调用
- GraphQL
异步通信
- 消息队列
- 事件驱动
- 发布订阅
Spring Cloud 服务调用组件
┌─────────────────────────────────────────────────────────────────┐
│ Spring Cloud 服务调用架构 │
├─────────────────────────────────────────────────────────────────┤
│ 服务消费者 (Order Service) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ OpenFeign │ │ RestTemplate │ │
│ │ (声明式调用) │ │ (编程式调用) │ │
│ └─────────┬───────┘ └─────────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LoadBalancer / Ribbon │ │
│ │ (客户端负载均衡) │ │
│ └─────────────────────┬───────────────────────────────────┘ │
├────────────────────────┼───────────────────────────────────────┤
│ │ HTTP请求 │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ User Service │ │ User Service │ │ User Service │ │
│ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ │
│ │ 192.168.1.10 │ │ 192.168.1.11 │ │ 192.168.1.12 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
3.2 OpenFeign 声明式服务调用
OpenFeign 简介
OpenFeign是一个声明式的Web服务客户端,它让编写Web服务客户端变得更加容易。只需要创建一个接口并在接口上添加注解即可。
核心特性
- 声明式API:通过注解定义服务调用接口
- 集成负载均衡:自动集成Ribbon负载均衡
- 支持熔断器:集成Hystrix或Resilience4j
- 请求/响应拦截器:支持自定义拦截器
- 多种编码器/解码器:支持JSON、XML等格式
添加依赖
pom.xml
<dependencies>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- LoadBalancer (替代Ribbon) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 熔断器支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
</dependencies>
启用 OpenFeign
OrderServiceApplication.java
package com.example.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 订单服务启动类
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients // 启用Feign客户端
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
创建 Feign 客户端
1. 用户服务客户端
UserServiceClient.java
package com.example.orderservice.client;
import com.example.orderservice.dto.User;
import com.example.orderservice.fallback.UserServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 用户服务Feign客户端
*
* @FeignClient 注解参数说明:
* - name/value: 服务名称,对应Eureka中注册的服务名
* - url: 直接指定服务地址(可选)
* - fallback: 降级处理类
* - fallbackFactory: 降级处理工厂类
* - configuration: 自定义配置类
*/
@FeignClient(
name = "user-service",
fallback = UserServiceFallback.class,
configuration = FeignConfig.class
)
public interface UserServiceClient {
/**
* 根据ID获取用户信息
*/
@GetMapping("/api/users/{id}")
User getUserById(@PathVariable("id") Long id);
/**
* 根据用户名获取用户信息
*/
@GetMapping("/api/users/username/{username}")
User getUserByUsername(@PathVariable("username") String username);
/**
* 创建用户
*/
@PostMapping("/api/users")
User createUser(@RequestBody User user);
/**
* 更新用户信息
*/
@PutMapping("/api/users/{id}")
User updateUser(@PathVariable("id") Long id, @RequestBody User user);
/**
* 检查用户名是否存在
*/
@GetMapping("/api/users/check/username/{username}")
Map<String, Boolean> checkUsername(@PathVariable("username") String username);
/**
* 获取用户统计信息
*/
@GetMapping("/api/users/stats")
Map<String, Object> getUserStats();
/**
* 健康检查
*/
@GetMapping("/api/users/health")
Map<String, Object> health();
}
2. 商品服务客户端
ProductServiceClient.java
package com.example.orderservice.client;
import com.example.orderservice.dto.Product;
import com.example.orderservice.fallback.ProductServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 商品服务Feign客户端
*/
@FeignClient(
name = "product-service",
fallback = ProductServiceFallback.class
)
public interface ProductServiceClient {
/**
* 根据ID获取商品信息
*/
@GetMapping("/api/products/{id}")
Product getProductById(@PathVariable("id") Long id);
/**
* 批量获取商品信息
*/
@PostMapping("/api/products/batch")
List<Product> getProductsByIds(@RequestBody List<Long> ids);
/**
* 检查商品库存
*/
@GetMapping("/api/products/{id}/stock")
Map<String, Object> checkStock(@PathVariable("id") Long id);
/**
* 扣减库存
*/
@PostMapping("/api/products/{id}/stock/reduce")
boolean reduceStock(@PathVariable("id") Long id, @RequestParam("quantity") Integer quantity);
/**
* 恢复库存
*/
@PostMapping("/api/products/{id}/stock/restore")
boolean restoreStock(@PathVariable("id") Long id, @RequestParam("quantity") Integer quantity);
}
Feign 配置
1. 全局配置
FeignConfig.java
package com.example.orderservice.config;
import feign.Logger;
import feign.Request;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import java.util.concurrent.TimeUnit;
/**
* Feign全局配置
*/
@Configuration
public class FeignConfig {
/**
* 日志级别配置
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* 请求超时配置
*/
@Bean
public Request.Options options() {
return new Request.Options(
5000, // 连接超时时间(毫秒)
10000 // 读取超时时间(毫秒)
);
}
/**
* 重试配置
*/
@Bean
public Retryer retryer() {
// 最大重试次数为3,初始重试间隔为100ms,最大重试间隔为1s
return new Retryer.Default(100, 1000, 3);
}
/**
* 编码器配置
*/
@Bean
public Encoder encoder() {
return new SpringEncoder(() -> new HttpMessageConvertersConfiguration().messageConverters());
}
/**
* 解码器配置
*/
@Bean
public Decoder decoder() {
return new ResponseEntityDecoder(new SpringDecoder(() -> new HttpMessageConvertersConfiguration().messageConverters()));
}
/**
* 错误解码器
*/
@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}
2. 自定义错误解码器
CustomErrorDecoder.java
package com.example.orderservice.config;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
/**
* 自定义Feign错误解码器
*/
@Component
@Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
log.error("Feign client error: method={}, status={}, reason={}",
methodKey, response.status(), response.reason());
HttpStatus httpStatus = HttpStatus.valueOf(response.status());
switch (httpStatus) {
case NOT_FOUND:
return new ResourceNotFoundException("Resource not found: " + methodKey);
case BAD_REQUEST:
return new IllegalArgumentException("Bad request: " + methodKey);
case UNAUTHORIZED:
return new UnauthorizedException("Unauthorized: " + methodKey);
case FORBIDDEN:
return new ForbiddenException("Forbidden: " + methodKey);
case INTERNAL_SERVER_ERROR:
return new ServiceUnavailableException("Service unavailable: " + methodKey);
case SERVICE_UNAVAILABLE:
return new ServiceUnavailableException("Service unavailable: " + methodKey);
default:
return defaultErrorDecoder.decode(methodKey, response);
}
}
// 自定义异常类
public static class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
public static class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
public static class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}
public static class ServiceUnavailableException extends RuntimeException {
public ServiceUnavailableException(String message) {
super(message);
}
}
}
3. 请求拦截器
FeignRequestInterceptor.java
package com.example.orderservice.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
/**
* Feign请求拦截器
*/
@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 添加请求ID用于链路追踪
String requestId = UUID.randomUUID().toString();
template.header("X-Request-ID", requestId);
// 传递认证信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 传递Authorization头
String authorization = request.getHeader("Authorization");
if (authorization != null) {
template.header("Authorization", authorization);
}
// 传递用户信息
String userId = request.getHeader("X-User-ID");
if (userId != null) {
template.header("X-User-ID", userId);
}
}
// 添加服务标识
template.header("X-Service-Name", "order-service");
log.debug("Feign request: {} {}, headers: {}",
template.method(), template.url(), template.headers());
}
}
降级处理
1. 用户服务降级
UserServiceFallback.java
package com.example.orderservice.fallback;
import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 用户服务降级处理
*/
@Component
@Slf4j
public class UserServiceFallback implements UserServiceClient {
@Override
public User getUserById(Long id) {
log.warn("User service is unavailable, using fallback for getUserById: {}", id);
return User.builder()
.id(id)
.username("unknown-user-" + id)
.email("unknown@example.com")
.fullName("Unknown User")
.status(User.UserStatus.INACTIVE)
.build();
}
@Override
public User getUserByUsername(String username) {
log.warn("User service is unavailable, using fallback for getUserByUsername: {}", username);
return User.builder()
.id(-1L)
.username(username)
.email("unknown@example.com")
.fullName("Unknown User")
.status(User.UserStatus.INACTIVE)
.build();
}
@Override
public User createUser(User user) {
log.error("User service is unavailable, cannot create user: {}", user.getUsername());
throw new ServiceUnavailableException("用户服务暂时不可用,无法创建用户");
}
@Override
public User updateUser(Long id, User user) {
log.error("User service is unavailable, cannot update user: {}", id);
throw new ServiceUnavailableException("用户服务暂时不可用,无法更新用户");
}
@Override
public Map<String, Boolean> checkUsername(String username) {
log.warn("User service is unavailable, using fallback for checkUsername: {}", username);
Map<String, Boolean> result = new HashMap<>();
result.put("exists", false);
return result;
}
@Override
public Map<String, Object> getUserStats() {
log.warn("User service is unavailable, using fallback for getUserStats");
Map<String, Object> stats = new HashMap<>();
stats.put("activeUsers", 0);
stats.put("status", "unavailable");
return stats;
}
@Override
public Map<String, Object> health() {
log.warn("User service is unavailable, using fallback for health check");
Map<String, Object> health = new HashMap<>();
health.put("status", "DOWN");
health.put("service", "user-service");
health.put("message", "Service is currently unavailable");
return health;
}
public static class ServiceUnavailableException extends RuntimeException {
public ServiceUnavailableException(String message) {
super(message);
}
}
}
2. 降级工厂类
UserServiceFallbackFactory.java
package com.example.orderservice.fallback;
import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.dto.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 用户服务降级工厂类
* 可以获取到具体的异常信息
*/
@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceClient> {
@Override
public UserServiceClient create(Throwable cause) {
return new UserServiceClient() {
@Override
public User getUserById(Long id) {
log.error("Failed to get user by id: {}, cause: {}", id, cause.getMessage());
return User.builder()
.id(id)
.username("fallback-user-" + id)
.email("fallback@example.com")
.fullName("Fallback User")
.status(User.UserStatus.INACTIVE)
.build();
}
@Override
public User getUserByUsername(String username) {
log.error("Failed to get user by username: {}, cause: {}", username, cause.getMessage());
return User.builder()
.id(-1L)
.username(username)
.email("fallback@example.com")
.fullName("Fallback User")
.status(User.UserStatus.INACTIVE)
.build();
}
@Override
public User createUser(User user) {
log.error("Failed to create user: {}, cause: {}", user.getUsername(), cause.getMessage());
throw new RuntimeException("用户服务不可用: " + cause.getMessage());
}
@Override
public User updateUser(Long id, User user) {
log.error("Failed to update user: {}, cause: {}", id, cause.getMessage());
throw new RuntimeException("用户服务不可用: " + cause.getMessage());
}
@Override
public Map<String, Boolean> checkUsername(String username) {
log.error("Failed to check username: {}, cause: {}", username, cause.getMessage());
Map<String, Boolean> result = new HashMap<>();
result.put("exists", false);
return result;
}
@Override
public Map<String, Object> getUserStats() {
log.error("Failed to get user stats, cause: {}", cause.getMessage());
Map<String, Object> stats = new HashMap<>();
stats.put("activeUsers", 0);
stats.put("status", "error");
stats.put("error", cause.getMessage());
return stats;
}
@Override
public Map<String, Object> health() {
log.error("Failed to check health, cause: {}", cause.getMessage());
Map<String, Object> health = new HashMap<>();
health.put("status", "DOWN");
health.put("service", "user-service");
health.put("error", cause.getMessage());
return health;
}
};
}
}
3.3 负载均衡
Spring Cloud LoadBalancer
Spring Cloud LoadBalancer是Spring Cloud自研的负载均衡器,用于替代Netflix Ribbon。
负载均衡算法
- 轮询(Round Robin):默认算法
- 随机(Random):随机选择实例
- 加权轮询(Weighted Round Robin):根据权重分配
- 最少连接(Least Connections):选择连接数最少的实例
自定义负载均衡配置
LoadBalancerConfig.java
package com.example.orderservice.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* 负载均衡配置
*/
@Configuration
public class LoadBalancerConfig {
/**
* 自定义负载均衡算法
* 这里使用随机算法
*/
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}
自定义负载均衡算法
WeightedLoadBalancer.java
package com.example.orderservice.loadbalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
* 基于权重的负载均衡算法
*/
@Slf4j
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final String serviceId;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public WeightedLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request)
.next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(
ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
log.warn("No servers available for service: {}", serviceId);
return new EmptyResponse();
}
// 基于权重选择实例
ServiceInstance selectedInstance = selectByWeight(instances);
return new DefaultResponse(selectedInstance);
}
/**
* 基于权重选择服务实例
*/
private ServiceInstance selectByWeight(List<ServiceInstance> instances) {
// 计算总权重
int totalWeight = 0;
for (ServiceInstance instance : instances) {
int weight = getWeight(instance);
totalWeight += weight;
}
if (totalWeight <= 0) {
// 如果没有权重配置,使用随机算法
return instances.get(ThreadLocalRandom.current().nextInt(instances.size()));
}
// 根据权重随机选择
int randomWeight = ThreadLocalRandom.current().nextInt(totalWeight);
int currentWeight = 0;
for (ServiceInstance instance : instances) {
currentWeight += getWeight(instance);
if (randomWeight < currentWeight) {
log.debug("Selected instance: {} with weight: {}",
instance.getInstanceId(), getWeight(instance));
return instance;
}
}
// 默认返回第一个实例
return instances.get(0);
}
/**
* 获取实例权重
*/
private int getWeight(ServiceInstance instance) {
Map<String, String> metadata = instance.getMetadata();
String weightStr = metadata.get("weight");
if (weightStr != null) {
try {
return Integer.parseInt(weightStr);
} catch (NumberFormatException e) {
log.warn("Invalid weight value: {} for instance: {}",
weightStr, instance.getInstanceId());
}
}
// 默认权重为1
return 1;
}
}
配置负载均衡策略
application.yml
spring:
cloud:
loadbalancer:
# 负载均衡缓存配置
cache:
enabled: true
ttl: 35s
capacity: 256
# 健康检查配置
health-check:
initial-delay: 0
interval: 25s
path:
user-service: /actuator/health
product-service: /actuator/health
# 重试配置
retry:
enabled: true
max-retries-on-same-service-instance: 1
max-retries-on-next-service-instance: 1
retry-on-all-operations: false
# Feign配置
feign:
client:
config:
default:
# 连接超时
connect-timeout: 5000
# 读取超时
read-timeout: 10000
# 日志级别
logger-level: full
# 特定服务配置
user-service:
connect-timeout: 3000
read-timeout: 8000
logger-level: basic
# 启用熔断器
circuitbreaker:
enabled: true
# 压缩配置
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
# 日志配置
logging:
level:
com.example.orderservice.client: DEBUG
org.springframework.cloud.openfeign: DEBUG
org.springframework.cloud.loadbalancer: DEBUG
3.4 服务调用实战
订单服务实现
1. 订单实体
Order.java
package com.example.orderservice.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 订单实体
*/
@Entity
@Table(name = "orders")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_no", unique = true, nullable = false)
private String orderNo;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "user_name")
private String userName;
@Column(name = "user_email")
private String userEmail;
@Column(name = "total_amount", nullable = false)
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Builder.Default
private OrderStatus status = OrderStatus.PENDING;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderItem> items;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
if (orderNo == null) {
orderNo = generateOrderNo();
}
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis();
}
/**
* 订单状态枚举
*/
public enum OrderStatus {
PENDING, // 待处理
CONFIRMED, // 已确认
PAID, // 已支付
SHIPPED, // 已发货
DELIVERED, // 已送达
CANCELLED, // 已取消
REFUNDED // 已退款
}
}
2. 订单项实体
OrderItem.java
package com.example.orderservice.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.math.BigDecimal;
/**
* 订单项实体
*/
@Entity
@Table(name = "order_items")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
@Column(name = "product_id", nullable = false)
private Long productId;
@Column(name = "product_name")
private String productName;
@Column(name = "product_price", nullable = false)
private BigDecimal productPrice;
@Column(nullable = false)
private Integer quantity;
@Column(name = "subtotal", nullable = false)
private BigDecimal subtotal;
}
3. 订单服务实现
OrderServiceImpl.java
package com.example.orderservice.service.impl;
import com.example.orderservice.client.ProductServiceClient;
import com.example.orderservice.client.UserServiceClient;
import com.example.orderservice.dto.CreateOrderRequest;
import com.example.orderservice.dto.Product;
import com.example.orderservice.dto.User;
import com.example.orderservice.entity.Order;
import com.example.orderservice.entity.OrderItem;
import com.example.orderservice.repository.OrderRepository;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 订单服务实现类
*/
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final UserServiceClient userServiceClient;
private final ProductServiceClient productServiceClient;
@Override
public Order createOrder(CreateOrderRequest request) {
log.info("Creating order for user: {}", request.getUserId());
// 1. 验证用户信息
User user = validateUser(request.getUserId());
// 2. 验证商品信息和库存
List<Product> products = validateProducts(request.getItems());
// 3. 扣减库存
boolean stockReduced = reduceStock(request.getItems());
if (!stockReduced) {
throw new RuntimeException("库存扣减失败");
}
try {
// 4. 创建订单
Order order = buildOrder(user, request, products);
Order savedOrder = orderRepository.save(order);
log.info("Order created successfully: {}", savedOrder.getOrderNo());
return savedOrder;
} catch (Exception e) {
// 5. 如果订单创建失败,恢复库存
log.error("Failed to create order, restoring stock", e);
restoreStock(request.getItems());
throw new RuntimeException("订单创建失败: " + e.getMessage());
}
}
private User validateUser(Long userId) {
try {
User user = userServiceClient.getUserById(userId);
if (user == null || user.getId() == null) {
throw new IllegalArgumentException("用户不存在: " + userId);
}
if (user.getStatus() != User.UserStatus.ACTIVE) {
throw new IllegalArgumentException("用户状态异常: " + user.getStatus());
}
return user;
} catch (Exception e) {
log.error("Failed to validate user: {}", userId, e);
throw new RuntimeException("用户验证失败: " + e.getMessage());
}
}
private List<Product> validateProducts(List<CreateOrderRequest.OrderItemRequest> items) {
List<Long> productIds = items.stream()
.map(CreateOrderRequest.OrderItemRequest::getProductId)
.collect(Collectors.toList());
try {
List<Product> products = productServiceClient.getProductsByIds(productIds);
if (products.size() != productIds.size()) {
throw new IllegalArgumentException("部分商品不存在");
}
// 验证商品状态和库存
for (CreateOrderRequest.OrderItemRequest item : items) {
Product product = products.stream()
.filter(p -> p.getId().equals(item.getProductId()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("商品不存在: " + item.getProductId()));
if (product.getStatus() != Product.ProductStatus.ACTIVE) {
throw new IllegalArgumentException("商品已下架: " + product.getName());
}
Map<String, Object> stockInfo = productServiceClient.checkStock(product.getId());
Integer availableStock = (Integer) stockInfo.get("availableStock");
if (availableStock < item.getQuantity()) {
throw new IllegalArgumentException("商品库存不足: " + product.getName());
}
}
return products;
} catch (Exception e) {
log.error("Failed to validate products", e);
throw new RuntimeException("商品验证失败: " + e.getMessage());
}
}
private boolean reduceStock(List<CreateOrderRequest.OrderItemRequest> items) {
try {
for (CreateOrderRequest.OrderItemRequest item : items) {
boolean success = productServiceClient.reduceStock(item.getProductId(), item.getQuantity());
if (!success) {
// 如果某个商品库存扣减失败,需要恢复之前已扣减的库存
log.error("Failed to reduce stock for product: {}", item.getProductId());
return false;
}
}
return true;
} catch (Exception e) {
log.error("Error reducing stock", e);
return false;
}
}
private void restoreStock(List<CreateOrderRequest.OrderItemRequest> items) {
try {
for (CreateOrderRequest.OrderItemRequest item : items) {
productServiceClient.restoreStock(item.getProductId(), item.getQuantity());
}
} catch (Exception e) {
log.error("Failed to restore stock", e);
}
}
private Order buildOrder(User user, CreateOrderRequest request, List<Product> products) {
List<OrderItem> orderItems = new ArrayList<>();
BigDecimal totalAmount = BigDecimal.ZERO;
Order order = Order.builder()
.userId(user.getId())
.userName(user.getUsername())
.userEmail(user.getEmail())
.status(Order.OrderStatus.PENDING)
.build();
for (CreateOrderRequest.OrderItemRequest itemRequest : request.getItems()) {
Product product = products.stream()
.filter(p -> p.getId().equals(itemRequest.getProductId()))
.findFirst()
.orElseThrow();
BigDecimal subtotal = product.getPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
totalAmount = totalAmount.add(subtotal);
OrderItem orderItem = OrderItem.builder()
.order(order)
.productId(product.getId())
.productName(product.getName())
.productPrice(product.getPrice())
.quantity(itemRequest.getQuantity())
.subtotal(subtotal)
.build();
orderItems.add(orderItem);
}
order.setItems(orderItems);
order.setTotalAmount(totalAmount);
return order;
}
@Override
@Transactional(readOnly = true)
public Optional<Order> getOrderById(Long id) {
return orderRepository.findById(id);
}
@Override
@Transactional(readOnly = true)
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserIdOrderByCreatedAtDesc(userId);
}
@Override
public Order updateOrderStatus(Long orderId, Order.OrderStatus status) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("订单不存在: " + orderId));
order.setStatus(status);
return orderRepository.save(order);
}
@Override
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("订单不存在: " + orderId));
if (order.getStatus() != Order.OrderStatus.PENDING) {
throw new IllegalStateException("只能取消待处理状态的订单");
}
// 恢复库存
List<CreateOrderRequest.OrderItemRequest> items = order.getItems().stream()
.map(item -> CreateOrderRequest.OrderItemRequest.builder()
.productId(item.getProductId())
.quantity(item.getQuantity())
.build())
.collect(Collectors.toList());
restoreStock(items);
// 更新订单状态
order.setStatus(Order.OrderStatus.CANCELLED);
orderRepository.save(order);
log.info("Order cancelled: {}", order.getOrderNo());
}
}
4. 订单控制器
OrderController.java
package com.example.orderservice.controller;
import com.example.orderservice.dto.CreateOrderRequest;
import com.example.orderservice.entity.Order;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 订单控制器
*/
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
@Slf4j
@Validated
public class OrderController {
private final OrderService orderService;
/**
* 创建订单
*/
@PostMapping
public ResponseEntity<Order> createOrder(@Valid @RequestBody CreateOrderRequest request) {
log.info("Creating order for user: {}", request.getUserId());
try {
Order order = orderService.createOrder(request);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
} catch (IllegalArgumentException e) {
log.error("Invalid request: {}", e.getMessage());
return ResponseEntity.badRequest().build();
} catch (Exception e) {
log.error("Failed to create order", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 根据ID获取订单
*/
@GetMapping("/{id}")
public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
return orderService.getOrderById(id)
.map(order -> ResponseEntity.ok().body(order))
.orElse(ResponseEntity.notFound().build());
}
/**
* 获取用户订单列表
*/
@GetMapping("/user/{userId}")
public ResponseEntity<List<Order>> getOrdersByUserId(@PathVariable Long userId) {
List<Order> orders = orderService.getOrdersByUserId(userId);
return ResponseEntity.ok(orders);
}
/**
* 更新订单状态
*/
@PatchMapping("/{id}/status")
public ResponseEntity<Order> updateOrderStatus(
@PathVariable Long id,
@RequestParam Order.OrderStatus status) {
try {
Order order = orderService.updateOrderStatus(id, status);
return ResponseEntity.ok(order);
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
}
}
/**
* 取消订单
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> cancelOrder(@PathVariable Long id) {
try {
orderService.cancelOrder(id);
return ResponseEntity.noContent().build();
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
} catch (IllegalStateException e) {
return ResponseEntity.badRequest().build();
}
}
}
3.5 总结
本章详细介绍了Spring Cloud中的服务调用与负载均衡:
核心技术
- OpenFeign:声明式服务调用,简化服务间通信
- LoadBalancer:客户端负载均衡,替代Ribbon
- 降级处理:提供服务不可用时的备选方案
最佳实践
- 接口设计:遵循RESTful API设计原则
- 异常处理:完善的异常处理和降级机制
- 超时配置:合理设置连接和读取超时时间
- 重试机制:配置适当的重试策略
- 监控日志:完善的日志记录和监控
注意事项
- 服务依赖:避免循环依赖
- 数据一致性:处理分布式事务问题
- 性能优化:合理使用缓存和连接池
- 安全认证:传递认证信息
在下一章中,我们将学习Spring Cloud Gateway API网关的使用,包括路由配置、过滤器、限流等功能。