Skip to content

Feign 接口定义指南 ​

一、概述 ​

在 PigX 微服务架构中,服务间通信使用 OpenFeign。本指南说明如何定义和使用 Feign 接口。

二、Feign 接口结构 ​

模块划分 ​

pigx-standard/
├── pigx-standard-api/          # Feign 接口定义(供其他服务调用)
│   └── src/main/java/com/pig4cloud/pigx/standard/
│       ├── entity/             # 实体类
│       ├── dto/                # 数据传输对象
│       ├── vo/                 # 视图对象
│       └── feign/              # Feign 接口
│           ├── RemoteStandardUserService.java
│           └── factory/        # Feign 降级工厂
│               └── RemoteStandardUserServiceFallbackFactory.java
└── pigx-standard-biz/          # Feign 接口实现(Controller 提供)
    └── src/main/java/com/pig4cloud/pigx/standard/
        └── controller/
            └── StandardUserController.java

三、定义 Feign 接口 ​

1. 在 pigx-standard-api 中定义接口 ​

java
package com.pig4cloud.pigx.standard.feign;

import com.pig4cloud.pigx.common.core.constant.ServiceNameConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.feign.annotation.FeignClient;
import com.pig4cloud.pigx.common.security.annotation.Inner;
import com.pig4cloud.pigx.standard.entity.StandardUser;
import com.pig4cloud.pigx.standard.feign.factory.RemoteStandardUserServiceFallbackFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * 用户服务远程调用接口
 *
 * @author pigx-standard
 * @date 2025-12-16
 */
@FeignClient(
    contextId = "remoteStandardUserService",
    value = ServiceNameConstants.STANDARD_SERVICE,
    fallbackFactory = RemoteStandardUserServiceFallbackFactory.class
)
public interface RemoteStandardUserService {

    /**
     * 根据用户ID获取用户信息(内部调用)
     * @param id 用户ID
     * @param from 调用来源(内部标识)
     * @return 用户信息
     */
    @Inner
    @GetMapping("/user/inner/{id}")
    R<StandardUser> getById(@PathVariable("id") Long id, @RequestHeader(SecurityConstants.FROM) String from);

    /**
     * 根据用户名获取用户信息(内部调用)
     * @param username 用户名
     * @param from 调用来源(内部标识)
     * @return 用户信息
     */
    @Inner
    @GetMapping("/user/inner/username/{username}")
    R<StandardUser> getByUsername(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM) String from);

    /**
     * 创建用户(内部调用)
     * @param user 用户信息
     * @param from 调用来源(内部标识)
     * @return 创建结果
     */
    @Inner
    @PostMapping("/user/inner")
    R<Boolean> save(@RequestBody StandardUser user, @RequestHeader(SecurityConstants.FROM) String from);
}

关键点:

  • @FeignClient: 指定服务名和降级工厂
  • contextId: 唯一标识,避免多个 Feign 接口冲突
  • value: 服务名称(在 Nacos 中注册的名称)
  • @Inner: 标记为内部接口,不需要用户认证
  • @RequestHeader(SecurityConstants.FROM): 内部调用标识

2. 定义服务名称常量 ​

在 pigx-common-core 或自定义常量类中:

java
package com.pig4cloud.pigx.common.core.constant;

/**
 * 服务名称常量
 */
public interface ServiceNameConstants {

    /**
     * 认证服务
     */
    String AUTH_SERVICE = "pigx-auth";

    /**
     * UPMS 服务
     */
    String UPMS_SERVICE = "pigx-upms-biz";

    /**
     * Standard 服务
     */
    String STANDARD_SERVICE = "pigx-standard-biz";
}

3. 定义降级工厂 ​

java
package com.pig4cloud.pigx.standard.feign.factory;

import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.standard.entity.StandardUser;
import com.pig4cloud.pigx.standard.feign.RemoteStandardUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * 用户服务降级工厂
 *
 * @author pigx-standard
 * @date 2025-12-16
 */
@Slf4j
@Component
public class RemoteStandardUserServiceFallbackFactory implements FallbackFactory<RemoteStandardUserService> {

    @Override
    public RemoteStandardUserService create(Throwable throwable) {
        log.error("用户服务调用失败", throwable);

        return new RemoteStandardUserService() {
            @Override
            public R<StandardUser> getById(Long id, String from) {
                log.error("根据ID获取用户失败: id={}, 错误={}", id, throwable.getMessage());
                return R.failed("获取用户信息失败: " + throwable.getMessage());
            }

            @Override
            public R<StandardUser> getByUsername(String username, String from) {
                log.error("根据用户名获取用户失败: username={}, 错误={}", username, throwable.getMessage());
                return R.failed("获取用户信息失败: " + throwable.getMessage());
            }

            @Override
            public R<Boolean> save(StandardUser user, String from) {
                log.error("创建用户失败: user={}, 错误={}", user, throwable.getMessage());
                return R.failed("创建用户失败: " + throwable.getMessage());
            }
        };
    }
}

降级说明:

  • 当远程服务不可用时,自动触发降级
  • 记录错误日志
  • 返回友好的错误信息

四、实现 Feign 接口(控制器) ​

在 pigx-standard-biz 中提供实现 ​

java
package com.pig4cloud.pigx.standard.controller;

import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.security.annotation.Inner;
import com.pig4cloud.pigx.standard.entity.StandardUser;
import com.pig4cloud.pigx.standard.service.StandardUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
 * 用户管理控制器
 *
 * @author pigx-standard
 * @date 2025-12-16
 */
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class StandardUserController {

    private final StandardUserService userService;

    // ==================== 内部接口(Feign 调用) ====================

    /**
     * 根据用户ID获取用户信息(内部调用)
     */
    @Inner
    @GetMapping("/inner/{id}")
    public R<StandardUser> getById(@PathVariable Long id) {
        return R.ok(userService.getById(id));
    }

    /**
     * 根据用户名获取用户信息(内部调用)
     */
    @Inner
    @GetMapping("/inner/username/{username}")
    public R<StandardUser> getByUsername(@PathVariable String username) {
        return R.ok(userService.getByUsername(username));
    }

    /**
     * 创建用户(内部调用)
     */
    @Inner
    @PostMapping("/inner")
    public R<Boolean> saveUser(@RequestBody StandardUser user) {
        return R.ok(userService.save(user));
    }

    // ==================== 外部接口(需要权限) ====================

    /**
     * 根据ID查询用户(需要权限)
     */
    @GetMapping("/{id}")
    @PreAuthorize("@pms.hasPermission('standard_user_view')")
    public R<StandardUser> getUserById(@PathVariable Long id) {
        return R.ok(userService.getById(id));
    }

    // ... 其他外部接口
}

接口划分:

  • /user/inner/*: 内部接口,添加 @Inner 注解,不需要权限验证
  • /user/*: 外部接口,需要权限验证

五、使用 Feign 接口 ​

1. 在其他服务中使用 ​

添加依赖(在调用方服务的 pom.xml):

xml
<dependency>
    <groupId>com.pig4cloud</groupId>
    <artifactId>pigx-standard-api</artifactId>
    <version>5.8.0</version>
</dependency>

注入并使用:

java
package com.pig4cloud.pigx.audit.service.impl;

import com.pig4cloud.pigx.standard.feign.RemoteStandardUserService;
import com.pig4cloud.pigx.standard.entity.StandardUser;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AuditServiceImpl {

    private final RemoteStandardUserService remoteStandardUserService;

    public void doSomething() {
        // 调用 Standard 服务获取用户信息
        R<StandardUser> result = remoteStandardUserService.getById(
            1L,
            SecurityConstants.FROM_IN  // 内部调用标识
        );

        if (result.isSuccess()) {
            StandardUser user = result.getData();
            // 处理用户信息
        } else {
            // 处理失败情况
            log.error("获取用户信息失败: {}", result.getMsg());
        }
    }
}

2. 启动类配置 ​

确保启动类已添加 @EnablePigxFeignClients:

java
@EnablePigxFeignClients  // 扫描 Feign 客户端
@SpringBootApplication
public class PigxAuditApplication {
    public static void main(String[] args) {
        SpringApplication.run(PigxAuditApplication.class, args);
    }
}

六、Feign 配置 ​

1. 超时配置 ​

在 application.yml 或 Nacos 配置:

yaml
feign:
  client:
    config:
      default:
        connectTimeout: 5000      # 连接超时(毫秒)
        readTimeout: 10000        # 读取超时(毫秒)
      pigx-standard-biz:          # 针对特定服务的配置
        connectTimeout: 3000
        readTimeout: 5000

2. 重试配置 ​

yaml
feign:
  client:
    config:
      default:
        retryer: com.pig4cloud.pigx.common.feign.FeignRetryerConfiguration

3. 日志配置 ​

yaml
feign:
  client:
    config:
      default:
        loggerLevel: FULL  # NONE, BASIC, HEADERS, FULL

在 logback-spring.xml 中:

xml
<!-- Feign 日志 -->
<logger name="com.pig4cloud.pigx.standard.feign" level="DEBUG"/>

七、最佳实践 ​

1. 接口设计原则 ​

java
// ✅ 好的设计:参数明确,返回类型清晰
@GetMapping("/user/inner/{id}")
R<StandardUser> getById(@PathVariable("id") Long id, @RequestHeader(SecurityConstants.FROM) String from);

// ❌ 不好的设计:参数模糊,容易出错
@GetMapping("/user")
R<StandardUser> getUser(@RequestParam Map<String, Object> params);

2. DTO 使用 ​

对于复杂的返回结果,使用 DTO:

java
// 定义 DTO
@Data
public class StandardUserDTO {
    private Long id;
    private String username;
    private String realName;
    private String deptName;
    // 只包含需要的字段
}

// Feign 接口
@GetMapping("/user/inner/detail/{id}")
R<StandardUserDTO> getUserDetail(@PathVariable("id") Long id, @RequestHeader(SecurityConstants.FROM) String from);

3. 批量查询 ​

java
// Feign 接口
@PostMapping("/user/inner/batch")
R<List<StandardUser>> listByIds(@RequestBody List<Long> ids, @RequestHeader(SecurityConstants.FROM) String from);

// Controller 实现
@Inner
@PostMapping("/inner/batch")
public R<List<StandardUser>> listByIds(@RequestBody List<Long> ids) {
    return R.ok(userService.listByIds(ids));
}

4. 异常处理 ​

java
// 在 Service 中统一处理 Feign 调用
public StandardUser getUserById(Long id) {
    R<StandardUser> result = remoteStandardUserService.getById(id, SecurityConstants.FROM_IN);

    if (!result.isSuccess()) {
        throw new BusinessException("获取用户信息失败: " + result.getMsg());
    }

    return result.getData();
}

八、调试技巧 ​

1. 查看 Feign 请求日志 ​

yaml
# application.yml
logging:
  level:
    com.pig4cloud.pigx.standard.feign: DEBUG

2. 使用 Actuator 查看 Feign 信息 ​

bash
# 查看 Feign 客户端列表
curl http://localhost:6064/actuator/beans | grep FeignClient

3. 模拟 Feign 调用 ​

bash
# 直接调用内部接口(需要添加内部调用标识)
curl -X GET http://localhost:6064/user/inner/1 \
  -H "from: inner"

九、常见问题 ​

Q1: Feign 调用返回 404 ​

原因:

  • Controller 路径不匹配
  • Feign 接口路径定义错误
  • 服务未启动或未注册

解决:

bash
# 1. 检查服务是否注册
curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=pigx-standard-biz

# 2. 检查 Controller 路径
# Feign: @GetMapping("/user/inner/{id}")
# Controller: @GetMapping("/inner/{id}") + @RequestMapping("/user")
# 实际路径: /user/inner/{id} ✅

Q2: Feign 调用超时 ​

原因:

  • 被调用服务响应慢
  • 网络问题
  • 超时配置过短

解决:

yaml
feign:
  client:
    config:
      pigx-standard-biz:
        connectTimeout: 10000
        readTimeout: 30000

Q3: 降级不生效 ​

原因:

  • 未配置降级工厂
  • Hystrix/Sentinel 未启用

解决:

yaml
feign:
  circuitbreaker:
    enabled: true

Q4: @Inner 注解不生效 ​

原因:

  • 未添加 from 请求头
  • Security 配置问题

解决:

java
// 确保调用时传入 from 参数
remoteStandardUserService.getById(id, SecurityConstants.FROM_IN);

十、检查清单 ​

  • [ ] Feign 接口已定义在 pigx-standard-api 模块
  • [ ] 添加了 @FeignClient 注解
  • [ ] 定义了降级工厂
  • [ ] Controller 提供了对应的实现
  • [ ] 添加了 @Inner 注解
  • [ ] 启动类添加了 @EnablePigxFeignClients
  • [ ] 调用方已添加 pigx-standard-api 依赖
  • [ ] Feign 配置正确(超时、重试等)
  • [ ] 日志配置正确
  • [ ] 测试调用成功

文档版本: v1.0 创建时间: 2025-12-16 最后更新: 2025-12-16