介绍

feign是声明式的web service客户端,其实就是Http client 的封装,它是为了解决服务间调用的问题而使用的。

verstion: 用于指定版本,经实战验证下来不是太好用,因为一个工程中可能会有多个版本并存,这一个改就全改了,不再建议使用动态变理,可以直接编码进去

参数配置

在应用的相应配置文件(如: api-config.yaml)中添加自己的版本信息及使用服务的版本信息

api:
  # 当前服务工程路径及版本,用于其他模块调用时配置
  path: api
  version: v1
  # 引用的其他服务路径及版本
  service:
    operate:            # 声明服务标识
      name: pai-operate # 用于解决服务名变化
      url:              # 用于本地开发时直接指定,如: http://localhost:8080
      path: api         # 用于配合contextPath配置
      version: v1       # 用于指定版本,经实战验证下来不是太好用,因为一个工程中可能会有多个版本并存,这一个改就全改了,不再建议使用动态变理,可以直接编码进去

SpringBoot Application启动类引用配置,并启用FeignClient

@EnablePaiFeignClients("cn.flyrise.*")
@PropertySource(value = {"classpath:api-config.yaml"}, factory = YamlPropertySourceFactory.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

feign使用示例

SpringBoot启动类添加注解@EnableFeignClients

添加接口声明API参数及路径

@FeignClient(
   name = "pai-console",
   contextId = "userInfoApi",
   url = "${api.service.console.url:}",
   fallbackFactory = RemoteUserInfoFallbackFactory.class
)
public interface IUserService {
    /**
     * 通过用户id获取用户信息
     *
     * @param id 用户id
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/info/{id}")
    Reply<UserEntity> findUserById(@PathVariable("id") String id);

    /**
     * 通过用户id获取用户信息-无鉴权
     *
     * @param id   用户id
     * @param from 内部调用凭证
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/inner/info/{id}")
    Reply<UserEntity> findUserByIdNoAuth(@PathVariable("id") String id, @RequestHeader("from") String from);

    /**
     * 通过多个用户id获取多个用户信息-无鉴权
     *
     * @param ids  用户id集合
     * @param from 内部调用凭证
     * @return
     */
    @PostMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/inner/info/batch")
    Reply<List<UserEntity>> findUserByIdsNoAuth(@RequestBody List<String> ids, @RequestHeader("from") String from);

    /**
     * 通过多个用户id获取多个用户信息
     *
     * @param ids 用户id集合
     * @return
     */
    @PostMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/info/batch")
    Reply<List<UserEntity>> findUserByIds(@RequestBody List<String> ids);


    /**
     * 当前登录用户的基本信息
     *
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/info/me")
    Reply<UserEntity> findMyInfo();

    /**
     * 根据用户id和企业entId获取用户权限
     *
     * @param userId 用户id
     * @param entId  企业id
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/permission")
    Reply<RoleAndPermEntity> findUserPermByEntId(@RequestParam("userId") String userId,
                                                 @RequestParam("entId") String entId
    );

    /**
     * 获取简单的用户信息
     *
     * @param id 用户id
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/info/{id}")
    Reply<SimpleUserModel> findSimpleUserById(@PathVariable("id") String id);

    /**
     * 获取简单的用户信息(多个)
     *
     * @param ids 用户id集合
     * @return
     */
    @PostMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/info/batch")
    Reply<List<SimpleUserModel>> findSimpleUserByIds(@RequestBody List<String> ids);


    /**
     * 根据用户id集合获取用户名
     *
     * @param ids 用户id集合
     * @return
     */
    @PostMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/username/simple/batch")
    Reply<Map<String, String>> getUsernames(@RequestBody List<String> ids);

    /**
     * 根据用户id集合获取用户昵称
     *
     * @param ids 用户id集合
     * @return
     */
    @PostMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/nickName/simple/batch")
    Reply<Map<String, String>> getNickNames(@RequestBody List<String> ids);

    /**
     * 获取用户信息 Map<String, UserEntity>
     *
     * @param ids 用户id集合
     * @return
     */
    @PostMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/info/simple/batch")
    Reply<Map<String, UserEntity>> getUserMapEntity(@RequestBody List<String> ids);

    /**
     * 根据企业id获取用户id集合(模糊查询)
     *
     * @param entId  企业id
     * @param search 搜索条件(用户昵称)
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/id/list")
    Reply<List<String>> getUserIdsByEntId(@RequestParam("entId") String entId, @RequestParam(value = "search", required = false) String search);


    /**
     * 根据用户昵称获取用户id集合(模糊查询)
     *
     * @param nickName 用户昵称
     * @return
     */
    @GetMapping("${api.service.console.path:}/user/${api.service.console.version:v1}/name/id/list")
    Reply<List<String>> getUserIdsByNickName(@RequestParam("nickName") String nickName);
}

注意:

  1. 声明接口时需要注意,@PathVariable@RequestParam声明参数的注解value值是必输的,若参数不加注解,feign默认会使用@RequestBody注解处理。
  2. 建议统一使用Reply<?>来接收response。这样在接口返回的数据异常时,可以显式地捕获异常。
  3. 若feign调用的接口为get请求,但controller使用对象接收参数。在feign的声明中,不可使用对象传参,要把对象拆开写。平台不推荐使用feign调用这种接口

实现接口的失败回调

fallbackFactory 代码例子:

/**
 * @author wry
 * @date 2021-3-25
 */
@Component
public class RemoteUserInfoFallbackFactory implements FallbackFactory<IUserService> {

    private Reply fail() {
        return Reply.fail("500", "用户信息API访问异常");
    }

    @Override
    public IUserService create(Throwable throwable) {
        return new IUserService() {

            @Override
            public Reply<UserEntity> findUserById(String id) {
                return fail();
            }

            @Override
            public Reply<UserEntity> findUserByIdNoAuth(String id, String from) {
                return fail();
            }

            @Override
            public Reply<List<UserEntity>> findUserByIdsNoAuth(List<String> ids, String from) {
                return fail();
            }

            @Override
            public Reply<List<UserEntity>> findUserByIds(List<String> ids) {
                return fail();
            }

            @Override
            public Reply<UserEntity> findMyInfo() {
                return fail();
            }

            @Override
            public Reply<RoleAndPermEntity> findUserPermByEntId(String userId, String entId) {
                return fail();
            }

            @Override
            public Reply<SimpleUserModel> findSimpleUserById(String id) {
                return fail();
            }

            @Override
            public Reply<List<SimpleUserModel>> findSimpleUserByIds(List<String> ids) {
                return fail();
            }

            @Override
            public Reply<Map<String, String>> getUsernames(List<String> ids) {
                return fail();
            }

            @Override
            public Reply<Map<String, String>> getNickNames(List<String> ids) {
                return fail();
            }

            @Override
            public Reply<Map<String, UserEntity>> getUserMapEntity(List<String> ids) {
                return fail();
            }

            @Override
            public Reply<List<String>> getUserIdsByEntId(String entId, String search) {
                return fail();
            }

            @Override
            public Reply<List<String>> getUserIdsByNickName(String nickName) {
                return fail();
            }
        };
    }
}

如何调用接口

    @Resource
    private IUserService userService;

    @ApiOperation("获取用户名称")
    @GetMapping("/info/getUsernames")
    public Reply<?> getUsernames(@RequestBody List<String> list) {
        return userService.getUsernames(list);
    }

超时问题

方案一(全局 - 不建议)
在nacos增加全局配置,将影响所有feign调用

feign:
    client:
        config:
            default:  // 所有服务级别设置,default为任意服务
                connectTimeout: 毫秒时间,建立连接的超时时间,一般只在发现服务时用到
                readTimeout: 毫秒时间 ,接口请求的超时时间
            remote-server-name: // contextId属性,指定调用remote-server-name服务时的超时时间
                connectTimeout: 200
                readTimeout: 3000

方案二(按需 - 建议)
在FeignClient的代接口类增加Request.Options在调用时按需配置,注意要重置多态解决默认不传的场景

@FeignClient(name = "${spring.application.name}")
public interface IUserServiceClient {

    @GetMapping(path = "/user/query")
    Reply<UserVO> getUser(Request.Options options, @RequestParam("id") String id);

    @GetMapping(path = "/user/query")
    Reply<UserVO> getUser(@RequestParam("id") String id);

}

实际调用

    @ApiOperation(value = "Feign查询用户信息")
    @GetMapping(value = "/feign/query")
    public Reply<UserVO> feignQuery(String id) {
        //自定义接口超时时间
        Request.Options options = new Request.Options(/*connectTimeout*/ 8, TimeUnit.SECONDS, /*readTimeout*/ 3, TimeUnit.SECONDS, /*followRedirects*/ true);
        return client.getUser(options, id);
    }

无需指定 Request.Options 的场景就直接调用 client.getUser(id) 即可。

文档更新时间: 2024-09-21 11:52   作者:朱灿奕