Skip to content

服务间接口调用

某些情况下,后台服务需要互相调用,比如: 在订单服务创建订单时,需要调用用户服务的用户扣减积分等场景。 这里我们就讨论几种类型的远程调用和解决方案:

  1. @RequestBody 类型参数调用
  2. @RequestParam 标注的的普通类型的参数调用
  3. 没有任何注解的参数 调用
  4. 跨服务文件上传
  5. 全局 fallback 配置
  6. 透传请求头和线程变量

各种类型的参数配置

java
@FeignClient(name = "${acuity.feign.demo-server:acuity-demo-server}", path = "/date")
public interface TestDateApi {
    // @RequestBody 类型参数调用
  	@PostMapping("/post1")
    R<DateDTO> bodyPos1(@RequestBody DateDTO data);
		
  	// 调用这个接口会报错,原因是FeignClient 不支持GET请求,传复杂对象
    @GetMapping("/get1")
    R<DateDTO> get(DateDTO data);
    
  	// @RequestParam 标注的的普通类型的参数调用
  	@GetMapping("/get2")
    R<DateDTO> get2(@RequestParam(required = false, value = "date") Date date,
                    @RequestParam(required = false, value = "dt") LocalDateTime dt,
                    @RequestParam(required = false, value = "d") LocalDate d,
                    @RequestParam(required = false, value = "t") LocalTime t);
}
@FeignClient(name = "${acuity.feign.demo-server:acuity-demo-server}", path = "/date")
public interface TestDateApi {
    // @RequestBody 类型参数调用
  	@PostMapping("/post1")
    R<DateDTO> bodyPos1(@RequestBody DateDTO data);
		
  	// 调用这个接口会报错,原因是FeignClient 不支持GET请求,传复杂对象
    @GetMapping("/get1")
    R<DateDTO> get(DateDTO data);
    
  	// @RequestParam 标注的的普通类型的参数调用
  	@GetMapping("/get2")
    R<DateDTO> get2(@RequestParam(required = false, value = "date") Date date,
                    @RequestParam(required = false, value = "dt") LocalDateTime dt,
                    @RequestParam(required = false, value = "d") LocalDate d,
                    @RequestParam(required = false, value = "t") LocalTime t);
}

全局 fallback 配置 (会员版才有这个功能)

参考acuity-cloud-starer模块 AcuitySentinelInvocationHandler 类, 启动时,会加载该类中的全局bean, 动态扫描项目中标记了 @FeignClient 注解, 且注解中没有指定 fallback 和 fallbackFactory 参数的类, 将其动态注入一个全局的Fallback 实现类(打印报错异常和返回同一个json错误信息)

远程调用时,如何透传请求头和线程变量?

由于项目中使用的是ThreadLocal在存储当前用户信息,但出现跨线程请求时,就无法获取用户身份了,但使用FeignClient进行远程调用时,默认会新启动一个线程继续调用,故而feign的被调用方和调用方就不在同一个线程,所有被调用方无法从ContextUtil中获取用户信息。 具体实现参考:acuity-cloud-starter 模块的FeignAddHeaderRequestInterceptor。 原理如下:

  1. feign 调用远程接口时,会经过FeignAddHeaderRequestInterceptor 拦截器,将线程变量中的参数或请求头中的数据再次封装到新请求的请求头
java
@Slf4j
public class FeignAddHeaderRequestInterceptor implements RequestInterceptor {

    public FeignAddHeaderRequestInterceptor() {
        super();
    }

    @Override
    public void apply(RequestTemplate template) {
        String xid = RootContext.getXID();
        if (StrUtil.isNotEmpty(xid)) {
            template.header(RootContext.KEY_XID, xid);
        }

        template.header(ContextConstants.FEIGN, StrPool.TRUE);
        log.info("thread id ={}, name={}", Thread.currentThread().getId(), Thread.currentThread().getName());
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            Map<String, String> localMap = ContextUtil.getLocalMap();
            localMap.forEach((key, value) -> template.header(key, URLUtil.encode(value)));
            return;
        }

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        if (request == null) {
            log.warn("path={}, 在FeignClient API接口未配置FeignConfiguration类, 故而无法在远程调用时获取请求头中的参数!", template.path());
            return;
        }
        // 传递请求头
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                String values = request.getHeader(key);
                values = ObjectUtil.isEmpty(values) ? URLUtil.encode(ContextUtil.get(key)) : values;
                template.header(key, values);
            }
        }
    }
}
@Slf4j
public class FeignAddHeaderRequestInterceptor implements RequestInterceptor {

    public FeignAddHeaderRequestInterceptor() {
        super();
    }

    @Override
    public void apply(RequestTemplate template) {
        String xid = RootContext.getXID();
        if (StrUtil.isNotEmpty(xid)) {
            template.header(RootContext.KEY_XID, xid);
        }

        template.header(ContextConstants.FEIGN, StrPool.TRUE);
        log.info("thread id ={}, name={}", Thread.currentThread().getId(), Thread.currentThread().getName());
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            Map<String, String> localMap = ContextUtil.getLocalMap();
            localMap.forEach((key, value) -> template.header(key, URLUtil.encode(value)));
            return;
        }

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        if (request == null) {
            log.warn("path={}, 在FeignClient API接口未配置FeignConfiguration类, 故而无法在远程调用时获取请求头中的参数!", template.path());
            return;
        }
        // 传递请求头
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                String values = request.getHeader(key);
                values = ObjectUtil.isEmpty(values) ? URLUtil.encode(ContextUtil.get(key)) : values;
                template.header(key, values);
            }
        }
    }
}

欢迎使用天源云Saas快速开发系统