335 lines
9.2 KiB
Markdown
335 lines
9.2 KiB
Markdown
|
|
# WMS 微服务 Feign 使用指南
|
|||
|
|
|
|||
|
|
## 一、OpenFeign 简介
|
|||
|
|
|
|||
|
|
OpenFeign 是一个声明式的 Web Service 客户端,它让微服务之间的调用变得更简单。
|
|||
|
|
使用 Feign 只需要创建接口并在接口上添加注解即可,无需手动构建 HTTP 请求。
|
|||
|
|
|
|||
|
|
## 二、项目中的 Feign 配置
|
|||
|
|
|
|||
|
|
### 2.1 依赖配置
|
|||
|
|
|
|||
|
|
已在所有微服务的 `pom.xml` 中添加以下依赖:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- OpenFeign 服务间调用 -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>org.springframework.cloud</groupId>
|
|||
|
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
|||
|
|
<version>4.1.3</version>
|
|||
|
|
</dependency>
|
|||
|
|
|
|||
|
|
<!-- OkHttp 作为 Feign 的 HTTP 客户端 -->
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>io.github.openfeign</groupId>
|
|||
|
|
<artifactId>feign-okhttp</artifactId>
|
|||
|
|
<version>13.5</version>
|
|||
|
|
</dependency>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 Feign Client 配置
|
|||
|
|
|
|||
|
|
**配置类**: `org.cpte.feign.config.FeignClientConfiguration`
|
|||
|
|
|
|||
|
|
主要配置项:
|
|||
|
|
- **HTTP 客户端**: 使用 OkHttp
|
|||
|
|
- **连接超时**: 5 秒
|
|||
|
|
- **读取超时**: 10 秒
|
|||
|
|
- **日志级别**: BASIC(可调整为 FULL 查看完整日志)
|
|||
|
|
- **重试机制**: 最多重试 3 次
|
|||
|
|
|
|||
|
|
### 2.3 Fallback 降级处理
|
|||
|
|
|
|||
|
|
为每个 Feign Client 都配置了 FallbackFactory,当服务调用失败时会执行降级逻辑:
|
|||
|
|
- `BasicServiceFallbackFactory` - 基础服务降级
|
|||
|
|
- `InventoryServiceFallbackFactory` - 库存服务降级
|
|||
|
|
- `ScheduleServiceFallbackFactory` - 调度服务降级
|
|||
|
|
|
|||
|
|
## 三、已有的 Feign Client
|
|||
|
|
|
|||
|
|
### 3.1 基础服务 Client
|
|||
|
|
|
|||
|
|
**接口**: `org.cpte.feign.client.BasicServiceClient`
|
|||
|
|
|
|||
|
|
**功能**:
|
|||
|
|
- 获取物品信息(按 ID/编码)
|
|||
|
|
- 获取区域信息(按 ID/编码)
|
|||
|
|
- 获取点位信息(按 ID/编码)
|
|||
|
|
- 批量查询物品/点位列表
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
```java
|
|||
|
|
@Autowired
|
|||
|
|
private BasicServiceClient basicServiceClient;
|
|||
|
|
|
|||
|
|
// 获取物品信息
|
|||
|
|
Result<Map<String, Object>> result = basicServiceClient.getItemById("item123");
|
|||
|
|
|
|||
|
|
// 批量获取点位
|
|||
|
|
List<String> pointCodes = Arrays.asList("P001", "P002", "P003");
|
|||
|
|
Result<List<Map<String, Object>>> points = basicServiceClient.getPointByCodes(pointCodes);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 库存服务 Client
|
|||
|
|
|
|||
|
|
**接口**: `org.cpte.feign.client.InventoryServiceClient`
|
|||
|
|
|
|||
|
|
**功能**:
|
|||
|
|
- 查询库存(单个/批量)
|
|||
|
|
- 增加库存
|
|||
|
|
- 扣减库存
|
|||
|
|
- 预占库存
|
|||
|
|
- 释放预占库存
|
|||
|
|
- 查询库存流水
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
```java
|
|||
|
|
@Autowired
|
|||
|
|
private InventoryServiceClient inventoryServiceClient;
|
|||
|
|
|
|||
|
|
// 查询库存
|
|||
|
|
Result<Map<String, Object>> stock = inventoryServiceClient.queryInventory("item123", "point456");
|
|||
|
|
|
|||
|
|
// 增加库存
|
|||
|
|
Map<String, Object> params = new HashMap<>();
|
|||
|
|
params.put("itemId", "item123");
|
|||
|
|
params.put("quantity", 100);
|
|||
|
|
Result<Boolean> result = inventoryServiceClient.increaseInventory(params);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 调度服务 Client
|
|||
|
|
|
|||
|
|
**接口**: `org.cpte.feign.client.ScheduleServiceClient`
|
|||
|
|
|
|||
|
|
**功能**:
|
|||
|
|
- 创建 AGV 上架任务
|
|||
|
|
- 创建 AGV 下架任务
|
|||
|
|
- 创建 AGV 搬运任务
|
|||
|
|
- 查询任务状态
|
|||
|
|
- 取消任务
|
|||
|
|
- 等待任务完成
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
```java
|
|||
|
|
@Autowired
|
|||
|
|
private ScheduleServiceClient scheduleServiceClient;
|
|||
|
|
|
|||
|
|
// 创建上架任务
|
|||
|
|
Map<String, Object> params = new HashMap<>();
|
|||
|
|
params.put("fromPoint", "RECEIVE_AREA");
|
|||
|
|
params.put("toPoint", "POINT001");
|
|||
|
|
params.put("taskId", "TASK_001");
|
|||
|
|
Result<String> result = scheduleServiceClient.createPutTask(params);
|
|||
|
|
|
|||
|
|
// 等待任务完成
|
|||
|
|
Result<Map<String, Object>> taskResult = scheduleServiceClient.waitTaskComplete("TASK_001", 300000L);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、完整业务流程示例
|
|||
|
|
|
|||
|
|
### 4.1 入库流程
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Service
|
|||
|
|
public class InboundService {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private BasicServiceClient basicServiceClient;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private InventoryServiceClient inventoryServiceClient;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private ScheduleServiceClient scheduleServiceClient;
|
|||
|
|
|
|||
|
|
@Transactional(rollbackFor = Exception.class)
|
|||
|
|
public Result<String> inbound(String itemId, String pointId, Integer quantity) {
|
|||
|
|
// 1. 验证物品和库位
|
|||
|
|
Result<Map<String, Object>> itemResult = basicServiceClient.getItemById(itemId);
|
|||
|
|
if (!itemResult.isSuccess()) {
|
|||
|
|
return Result.error("物品不存在");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Result<Map<String, Object>> pointResult = basicServiceClient.getPointById(pointId);
|
|||
|
|
if (!pointResult.isSuccess()) {
|
|||
|
|
return Result.error("库位不存在");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 增加库存
|
|||
|
|
Map<String, Object> params = new HashMap<>();
|
|||
|
|
params.put("itemId", itemId);
|
|||
|
|
params.put("pointId", pointId);
|
|||
|
|
params.put("quantity", quantity);
|
|||
|
|
Result<Boolean> increaseResult = inventoryServiceClient.increaseInventory(params);
|
|||
|
|
if (!increaseResult.isSuccess()) {
|
|||
|
|
return Result.error("增加库存失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 创建 AGV 上架任务
|
|||
|
|
Map<String, Object> taskParams = new HashMap<>();
|
|||
|
|
taskParams.put("fromPoint", "RECEIVE_AREA");
|
|||
|
|
taskParams.put("toPoint", pointId);
|
|||
|
|
taskParams.put("taskId", "PUT_" + System.currentTimeMillis());
|
|||
|
|
taskParams.put("taskType", "PUT");
|
|||
|
|
Result<String> taskResult = scheduleServiceClient.createPutTask(taskParams);
|
|||
|
|
|
|||
|
|
return Result.OK("入库成功", taskResult.getResult());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 出库流程
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Service
|
|||
|
|
public class OutboundService {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private InventoryServiceClient inventoryServiceClient;
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private ScheduleServiceClient scheduleServiceClient;
|
|||
|
|
|
|||
|
|
@Transactional(rollbackFor = Exception.class)
|
|||
|
|
public Result<String> outbound(String itemId, String pointId, Integer quantity, String orderNo) {
|
|||
|
|
// 1. 预占库存
|
|||
|
|
Map<String, Object> reserveParams = new HashMap<>();
|
|||
|
|
reserveParams.put("itemId", itemId);
|
|||
|
|
reserveParams.put("pointId", pointId);
|
|||
|
|
reserveParams.put("quantity", quantity);
|
|||
|
|
reserveParams.put("orderNo", orderNo);
|
|||
|
|
Result<Boolean> reserveResult = inventoryServiceClient.reserveInventory(reserveParams);
|
|||
|
|
if (!reserveResult.isSuccess()) {
|
|||
|
|
return Result.error("预占库存失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 创建 AGV 下架任务
|
|||
|
|
Map<String, Object> taskParams = new HashMap<>();
|
|||
|
|
taskParams.put("fromPoint", pointId);
|
|||
|
|
taskParams.put("toPoint", "SHIPMENT_AREA");
|
|||
|
|
taskParams.put("taskId", "REMOVE_" + orderNo);
|
|||
|
|
taskParams.put("taskType", "REMOVE");
|
|||
|
|
Result<String> taskResult = scheduleServiceClient.createRemoveTask(taskParams);
|
|||
|
|
|
|||
|
|
return Result.OK("出库成功", taskResult.getResult());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 五、配置说明
|
|||
|
|
|
|||
|
|
### 5.1 application.yml 配置
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# Feign 配置
|
|||
|
|
feign:
|
|||
|
|
client:
|
|||
|
|
config:
|
|||
|
|
default:
|
|||
|
|
connectTimeout: 5000 # 连接超时 5 秒
|
|||
|
|
readTimeout: 10000 # 读取超时 10 秒
|
|||
|
|
loggerLevel: BASIC # 日志级别
|
|||
|
|
okhttp:
|
|||
|
|
enabled: true # 启用 OkHttp
|
|||
|
|
compression:
|
|||
|
|
request:
|
|||
|
|
enabled: true # 启用请求压缩
|
|||
|
|
response:
|
|||
|
|
enabled: true # 启用响应压缩
|
|||
|
|
|
|||
|
|
# 服务地址配置(K8s 环境使用服务名)
|
|||
|
|
feign:
|
|||
|
|
client:
|
|||
|
|
wms-basic:
|
|||
|
|
url: http://wms-basic-service:80
|
|||
|
|
wms-inventory:
|
|||
|
|
url: http://wms-inventory-service:80
|
|||
|
|
wms-schedule:
|
|||
|
|
url: http://wms-schedule-service:80
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 日志级别调整
|
|||
|
|
|
|||
|
|
开发环境可调整为 FULL 查看详细请求响应:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
logging:
|
|||
|
|
level:
|
|||
|
|
org.cpte.feign.client: DEBUG
|
|||
|
|
feign:
|
|||
|
|
client:
|
|||
|
|
config:
|
|||
|
|
default:
|
|||
|
|
loggerLevel: FULL
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 六、注意事项
|
|||
|
|
|
|||
|
|
### 6.1 服务调用超时
|
|||
|
|
|
|||
|
|
- 默认连接超时 5 秒,读取超时 10 秒
|
|||
|
|
- 对于耗时操作(如等待 AGV 任务),使用异步方式或增加超时时间
|
|||
|
|
- 建议设置合理的超时时间,避免线程阻塞
|
|||
|
|
|
|||
|
|
### 6.2 服务降级
|
|||
|
|
|
|||
|
|
- 所有 Feign Client 都配置了 FallbackFactory
|
|||
|
|
- 降级处理会返回友好的错误信息
|
|||
|
|
- 生产环境建议实现更完善的降级策略(如返回缓存数据)
|
|||
|
|
|
|||
|
|
### 6.3 事务处理
|
|||
|
|
|
|||
|
|
- 跨服务调用时,本地事务无法保证分布式一致性
|
|||
|
|
- 建议使用最终一致性方案(如消息队列、补偿机制)
|
|||
|
|
- 关键业务操作需要实现幂等性
|
|||
|
|
|
|||
|
|
### 6.4 性能优化
|
|||
|
|
|
|||
|
|
- 启用 HTTP 压缩减少网络传输
|
|||
|
|
- 使用连接池提高连接复用率
|
|||
|
|
- 批量查询代替循环调用
|
|||
|
|
- 合理使用缓存减少服务调用
|
|||
|
|
|
|||
|
|
## 七、调试技巧
|
|||
|
|
|
|||
|
|
### 7.1 查看 Feign 日志
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
logging:
|
|||
|
|
level:
|
|||
|
|
org.cpte.feign.client: DEBUG
|
|||
|
|
feign: DEBUG
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 监控服务调用
|
|||
|
|
|
|||
|
|
- 使用 Actuator 端点监控 HTTP 请求
|
|||
|
|
- 集成 Prometheus + Grafana 监控服务调用指标
|
|||
|
|
- 使用 SkyWalking 进行链路追踪
|
|||
|
|
|
|||
|
|
### 7.3 本地测试
|
|||
|
|
|
|||
|
|
本地开发时,可以通过 Host 映射或配置中心指定服务地址:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# 本地开发环境
|
|||
|
|
feign:
|
|||
|
|
client:
|
|||
|
|
wms-basic:
|
|||
|
|
url: http://localhost:8001
|
|||
|
|
wms-inventory:
|
|||
|
|
url: http://localhost:8004
|
|||
|
|
wms-schedule:
|
|||
|
|
url: http://localhost:8005
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 八、最佳实践
|
|||
|
|
|
|||
|
|
1. **统一响应格式**: 所有服务接口返回统一的 Result 对象
|
|||
|
|
2. **参数校验**: 在调用前校验参数,减少无效调用
|
|||
|
|
3. **批量操作**: 优先使用批量接口,减少调用次数
|
|||
|
|
4. **超时控制**: 根据业务场景设置合理的超时时间
|
|||
|
|
5. **降级策略**: 实现完善的降级逻辑,提高系统可用性
|
|||
|
|
6. **幂等性**: 关键操作实现幂等性,支持重试
|
|||
|
|
7. **链路追踪**: 集成 SkyWalking 等工具,便于问题排查
|