介绍

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

feign使用示例

springBoot启动类添加注解@EnableFeignClients

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

@FeignClient(name = "pai-console", contextId = "userInfoApi", 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) 即可。

文档更新时间: 2022-12-07 10:03   作者:朱灿奕