您觉得本文档还缺少什么内容?可以自己补充~
某些情况下,后台服务需要互相调用,比如: 在订单服务
创建订单时,需要调用用户服务
的用户扣减积分等场景。
这里我们就讨论几种类型的远程调用和解决方案:
- @RequestBody 类型参数调用
- @RequestParam 标注的的普通类型的参数调用
- 没有任何注解的参数 调用
- 跨服务文件上传
- 透传请求头和线程变量
演示各种类型的参数配置
@FeignClient(name = "${bc.feign.demo-server:bc-demo-server}", path = "/date")
public interface TestDateApi {
@PostMapping("/post1")
R<DateDTO> bodyPos1(@RequestBody DateDTO data);
// 调用这个接口会报错,原因是FeignClient 不支持GET请求,传复杂对象
@GetMapping("/get1")
R<DateDTO> get(DateDTO data);
@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);
}
远程调用时,如何透传请求头和线程变量?
由于项目中使用的是ThreadLocal在存储当前用户信息,但出现跨线程请求时,就无法获取用户身份了,但使用FeignClient进行远程调用时,默认会新启动一个线程继续调用,故而feign的被调用方和调用方就不在同一个线程,所有被调用方无法从ContextUtil
中获取用户信息。
具体实现参考:bc-cloud-starter
模块的 FeignAddHeaderRequestInterceptor
。 原理如下:
FeignAddHeaderRequestInterceptor 拦截器,将线程变量中的信息再次封装套请求头
@Slf4j
public class FeignAddHeaderRequestInterceptor implements RequestInterceptor {
public static final List<String> HEADER_NAME_LIST = Arrays.asList(
ContextConstants.JWT_KEY_TENANT, ContextConstants.JWT_KEY_USER_ID,
ContextConstants.JWT_KEY_ACCOUNT, ContextConstants.JWT_KEY_NAME, ContextConstants.GRAY_VERSION,
ContextConstants.TRACE_ID_HEADER, "X-Real-IP", "x-forwarded-for"
);
public FeignAddHeaderRequestInterceptor() {
super();
}
@Override
public void apply(RequestTemplate template) {
String xid = RootContext.getXID();
if (StrUtil.isNotEmpty(xid)) {
template.header(RootContext.KEY_XID, xid);
}
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
HEADER_NAME_LIST.forEach((headerName) -> template.header(headerName, ContextUtil.get(headerName)));
return;
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
if (request == null) {
log.warn("path={}, 在FeignClient API接口未配置FeignConfiguration类, 故而无法在远程调用时获取请求头中的参数!", template.path());
return;
}
HEADER_NAME_LIST.forEach((headerName) -> {
String header = request.getHeader(headerName);
template.header(headerName, ObjectUtil.isEmpty(header) ? ContextUtil.get(headerName) : header);
});
}
}