# WMS 微服务 Feign 使用指南 ## 一、OpenFeign 简介 OpenFeign 是一个声明式的 Web Service 客户端,它让微服务之间的调用变得更简单。 使用 Feign 只需要创建接口并在接口上添加注解即可,无需手动构建 HTTP 请求。 ## 二、项目中的 Feign 配置 ### 2.1 依赖配置 已在所有微服务的 `pom.xml` 中添加以下依赖: ```xml org.springframework.cloud spring-cloud-starter-openfeign 4.1.3 io.github.openfeign feign-okhttp 13.5 ``` ### 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> result = basicServiceClient.getItemById("item123"); // 批量获取点位 List pointCodes = Arrays.asList("P001", "P002", "P003"); Result>> points = basicServiceClient.getPointByCodes(pointCodes); ``` ### 3.2 库存服务 Client **接口**: `org.cpte.feign.client.InventoryServiceClient` **功能**: - 查询库存(单个/批量) - 增加库存 - 扣减库存 - 预占库存 - 释放预占库存 - 查询库存流水 **使用示例**: ```java @Autowired private InventoryServiceClient inventoryServiceClient; // 查询库存 Result> stock = inventoryServiceClient.queryInventory("item123", "point456"); // 增加库存 Map params = new HashMap<>(); params.put("itemId", "item123"); params.put("quantity", 100); Result result = inventoryServiceClient.increaseInventory(params); ``` ### 3.3 调度服务 Client **接口**: `org.cpte.feign.client.ScheduleServiceClient` **功能**: - 创建 AGV 上架任务 - 创建 AGV 下架任务 - 创建 AGV 搬运任务 - 查询任务状态 - 取消任务 - 等待任务完成 **使用示例**: ```java @Autowired private ScheduleServiceClient scheduleServiceClient; // 创建上架任务 Map params = new HashMap<>(); params.put("fromPoint", "RECEIVE_AREA"); params.put("toPoint", "POINT001"); params.put("taskId", "TASK_001"); Result result = scheduleServiceClient.createPutTask(params); // 等待任务完成 Result> 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 inbound(String itemId, String pointId, Integer quantity) { // 1. 验证物品和库位 Result> itemResult = basicServiceClient.getItemById(itemId); if (!itemResult.isSuccess()) { return Result.error("物品不存在"); } Result> pointResult = basicServiceClient.getPointById(pointId); if (!pointResult.isSuccess()) { return Result.error("库位不存在"); } // 2. 增加库存 Map params = new HashMap<>(); params.put("itemId", itemId); params.put("pointId", pointId); params.put("quantity", quantity); Result increaseResult = inventoryServiceClient.increaseInventory(params); if (!increaseResult.isSuccess()) { return Result.error("增加库存失败"); } // 3. 创建 AGV 上架任务 Map taskParams = new HashMap<>(); taskParams.put("fromPoint", "RECEIVE_AREA"); taskParams.put("toPoint", pointId); taskParams.put("taskId", "PUT_" + System.currentTimeMillis()); taskParams.put("taskType", "PUT"); Result 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 outbound(String itemId, String pointId, Integer quantity, String orderNo) { // 1. 预占库存 Map reserveParams = new HashMap<>(); reserveParams.put("itemId", itemId); reserveParams.put("pointId", pointId); reserveParams.put("quantity", quantity); reserveParams.put("orderNo", orderNo); Result reserveResult = inventoryServiceClient.reserveInventory(reserveParams); if (!reserveResult.isSuccess()) { return Result.error("预占库存失败"); } // 2. 创建 AGV 下架任务 Map taskParams = new HashMap<>(); taskParams.put("fromPoint", pointId); taskParams.put("toPoint", "SHIPMENT_AREA"); taskParams.put("taskId", "REMOVE_" + orderNo); taskParams.put("taskType", "REMOVE"); Result 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 等工具,便于问题排查