异步调用feign请求头丢失数据问题解决 您所在的位置:网站首页 feign异步调用 异步调用feign请求头丢失数据问题解决

异步调用feign请求头丢失数据问题解决

2024-07-15 00:18| 来源: 网络整理| 查看: 265

一、RequestContextUtil package com.yubin.cn.facade.client.utils; import cn.hutool.core.util.StrUtil; import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.Map; /** * @auhtor * @create 2022-06-12-11:58 */ @Slf4j public class RequestContextUtil { /** * 获取请求头数据 * * @return key->请求头名称 value->请求头值 * @author * @date 2021/6/30 9:39 下午 */ public static Map getHeaderMap() { Map headerMap = Maps.newLinkedHashMap(); try { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes == null) { return headerMap; } HttpServletRequest request = requestAttributes.getRequest(); Enumeration enumeration = request.getHeaderNames(); while (enumeration.hasMoreElements()) { String key = enumeration.nextElement(); String value = request.getHeader(key); if (StrUtil.equalsIgnoreCase("authorization", key)) { headerMap.put(key, value); break; } } } catch (Exception e) { log.error("《RequestContextUtil》 获取请求头参数失败:", e); } return headerMap; } } 二、RequestHeaderHandler @Slf4j public class RequestHeaderHandler { public static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); public static void setHeaderMap(Map headerMap) { THREAD_LOCAL.set(headerMap); } public static Map getHeaderMap() { return THREAD_LOCAL.get(); } public static void remove() { THREAD_LOCAL.remove(); } } 三、FeignClientsConfiguration import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.yubin.cn.facade.client.utils.RequestContextUtil; import feign.RequestInterceptor; import feign.RequestTemplate; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Map; /** * @auhtor * @create 2022-05-20-10:05 */ @Slf4j @Configuration public class FiberDemandFeignClientsConfiguration { @Bean public RequestInterceptor requestInterceptor() { return (RequestTemplate requestTemplate) -> { //ServletRequestAttributes attributes = // (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // //if (attributes != null) { // HttpServletRequest request = attributes.getRequest(); // String authorization = request.getHeader("authorization"); // log.info("FiberDemandFeignClientsConfiguration请求头信息:{}", authorization); // requestTemplate.header("authorization", authorization); //} log.info("========================== ↓↓↓↓↓↓ 《FeignRequestInterceptor》 Start... ↓↓↓↓↓↓ ====当前线程:{}======================", Thread.currentThread().getId()); Map threadHeaderNameMap = RequestHeaderHandler.getHeaderMap(); if (CollUtil.isNotEmpty(threadHeaderNameMap)) { threadHeaderNameMap.forEach((headerName, headerValue) -> { log.info("《FeignRequestInterceptor》 多线程 headerName:【{}】 headerValue:【{}】,当前线程:【{}】", headerName, headerValue, Thread.currentThread().getId()); // 跳过 content-length ,Exception message - too many bytes written executing POST ,四种解决办法,这是其中之一 //因为服务之间调用需要携带一些用户信息之类的 所以实现了Feign的RequestInterceptor拦截器复制请求头, // 复制的时候是所有头都复制的,可能导致Content-length长度跟body不一致. 所以只需要判断如果是Content-length就跳过 if (headerName.equals("content-length")) { return; } if (StrUtil.equalsIgnoreCase("authorization", headerName)) { requestTemplate.header(headerName, headerValue); } }); } Map headerMap = RequestContextUtil.getHeaderMap(); if (CollUtil.isNotEmpty(headerMap)) { headerMap.forEach((headerName, headerValue) -> { log.info("《FeignRequestInterceptor》 headerName:【{}】 headerValue:【{}】,当前线程:【{}】", headerName, headerValue, Thread.currentThread().getId()); // 跳过 content-length if (headerName.equals("content-length")) { return; } if (StrUtil.equalsIgnoreCase("authorization", headerName)) { requestTemplate.header(headerName, headerValue); } }); } log.info("========================== ↑↑↑↑↑↑ 《FeignRequestInterceptor》 End... ↑↑↑↑↑↑ =====当前线程:{}=====================", Thread.currentThread().getId()); }; } } 四、feign调用的注解中配置 @FeignClient(url = "${feign.remote.${spring.profiles.active}}", name = "aaa", configuration = {FeignClientsConfiguration.class}, fallback = FeignClientFallback.class, contextId = "fiberDemandFeignClient") public interface FeignClient { // https://com.yubin.cn/gateway/yubin-getway/api/aaa/bbb @PostMapping(value = "/demand-getway/api/docking/pushProjectInfo") Map pushProjectInfo(@Validated @RequestBody FeignReq req); @PostMapping(value = "/demand-getway/api/docking/pushConstructionInfo") Map pushConstructionInfo(@Validated @RequestBody FeignReq req); } 五、调用

方法a中调用接口feign,获取当前请求头信息headerMap (从RequestContextUtil工具中获取请求头),然后调用方法fiberDemandFeignClient.pushProjectInfo(),此时会调用feign配置类的方法FeignClientsConfiguration.requestInterceptor Map threadHeaderNameMap = RequestHeaderHandler.getHeaderMap();这个时候获取的是空的, Map headerMap = RequestContextUtil.getHeaderMap();这个会获取到上下文的请求头放入feign的请求中。 在这里插入图片描述

当调用失败之后,启动异步调用重试机制,asyncService.retryCallPushProjectInfo()将请求头信息传过去。 在这里插入图片描述

新线程(异步也许是同一个线程)存储在自己的ThreadLocal中,然后重新调用feign方法。 在这里插入图片描述

在这里插入图片描述

然后会调用配置类FeignClientsConfiguration中的方法requestInterceptor方法,首先从当前线程取出来放入到feign请求中,然后RequestContextUtil.getHeaderMap()获取当前的请求头信息(如果重试这里应该是获取的空的)加入到当前请求中。 在这里插入图片描述

Map headerMap = RequestContextUtil.getHeaderMap(); try { log.info("pushProjectInfo请求参数:{}", JSONUtil.toJsonStr(fiberDemandFeignReq)); Map map = fiberDemandFeignClient.pushProjectInfo(fiberDemandFeignReq); log.info("pushProjectInfo返回结果:{}", JSONUtil.toJsonStr(map)); Integer responseCode = (Integer) map.get("code"); if (responseCode.compareTo(200) != 0) { log.info("第一次直接调用接口,返回的code不是200,需要重试,当前线程:{},headerMap:{}", Thread.currentThread().getId(), JSONUtil.toJsonStr(headerMap)); asyncService.retryCallPushProjectInfo(fiberDemandFeignReq, headerMap); } } catch (Exception e) { log.info("pushProjectInfo报异常,需要重试,当前线程:{},headerMap:{}", Thread.currentThread().getId(), JSONUtil.toJsonStr(headerMap)); log.info("推送pushProjectInfo失败需要重试," + e.getMessage(), e); try { asyncService.retryCallPushProjectInfo(fiberDemandFeignReq, headerMap); } catch (ExecutionException | RetryException ex) { log.error("推送项目数据给光纤销售系统重试失败!:", ex); // 失败需要重试 //throw new BusinessException("推送数据给光纤销售系统失败!"); } catch (Exception exception) { log.error("调用重试失败Exception:" + exception.getMessage(), exception); //throw new BusinessException("调用光纤销售系统失败!"); } } @Async @Override public void retryCallPushProjectInfo(FiberDemandFeignReq fiberDemandFeignReq, Map headerMap) throws ExecutionException, RetryException { demandAssignRetryerBuilder.build().call(() -> { log.info("retryCallPushProjectInfo重试,当前线程:{},headerMap:{},重试参数fiberDemandFeignReq:{}", Thread.currentThread().getId(), JSONUtil.toJsonStr(headerMap), JSONUtil.toJsonStr(fiberDemandFeignReq)); RequestHeaderHandler.setHeaderMap(headerMap); //RequestContextHolder.setRequestAttributes(attributes); Map objectMap = fiberDemandFeignClient.pushProjectInfo(fiberDemandFeignReq); log.info("retryCallPushProjectInfo重试返回结果:{}", JSONUtil.toJsonStr(objectMap)); Integer responseCode = (Integer) objectMap.get("code"); if (responseCode.compareTo(200) != 0) { throw new NeedRetryException(JSONUtil.toJsonStr(objectMap)); } return true; }); }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有