Appearance
服务间接口调用
某些情况下,后台服务需要互相调用,比如: 在订单服务
创建订单时,需要调用用户服务
的用户扣减积分等场景。 这里我们就讨论几种类型的远程调用和解决方案:
- @RequestBody 类型参数调用
- @RequestParam 标注的的普通类型的参数调用
- 没有任何注解的参数 调用
- 跨服务文件上传
- 全局 fallback 配置
- 透传请求头和线程变量
各种类型的参数配置
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
。 原理如下:
- 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);
}
}
}
}