关于学习鼓励商城微服务中Feign远程调用请求头丢失问题记录

单线程中Feign远程调用丢失请求头的情况

代码案例

订单服务:

Controller:

	/**
     * 当在购物车服务选中商品并点击“去结算”时触发订单服务。
     * 该controller主要用于返回订单结算详情等信息
     * */
@GetMapping("/toTrade")
    public String toTrade(Model model){

         OrderConfirmVo orderConfirmVo = orderService.confirmOrder();

         model.addAttribute("orderConfirmData",orderConfirmVo);

        return "confirm";

    }

拦截器:

	/**
     * 该拦截器用于在跳转到订单服务时,判断用户是否登陆。若未登陆,则跳转到登陆页面。
     * 若用于已登陆,则将用户的信息保存到ThreadLocal中。
     * */
	@Component
	public class OrderLoginIntercepted implements HandlerInterceptor {

    /**
     * 本地线程,线程与线程之间的数据隔离
     * */
    public static ThreadLocal<MemberResponseVo> threadLocal = new ThreadLocal<>();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //获取登录的用户信息
        MemberResponseVo attribute = (MemberResponseVo)request.getSession().getAttribute(AuthConstant.LOGIN_USER);

        if(attribute!=null){
            //把登录后用户的信息放在ThreadLocal里面进行保存
            threadLocal.set(attribute);
            return true;
        }
        else{
             request.getSession().setAttribute("msg", "请先进行登录");
             response.sendRedirect("http://auth.grapesmail.com/login.html");
            return false;
        }
    }

}

ServiceImpl:

	/**
     * service实现层
     * */
	@Override
    public OrderConfirmVo confirmOrder(){

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
			
		/**
    	 * 调用订单服务时,若已经登陆,则从订单服务的ThreadLocal中获取用户信息
    	 * */
        MemberResponseVo memberResponseVo = OrderLoginIntercepted.threadLocal.get();

        //远程调用会员服务查询用户地址
        List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
        orderConfirmVo.setMemberAddressVos(address);

        //远程调用购物车服务查询购物车所有购物项。
        //购物车服务涉及到拦截器session判断是否登陆。而Feign远程调用会构造一个新的request请求,该请求不携带请求头
        //即:通过Feign远程调用会导致请求头消失,请求头消失,session,cookie等信息丢失,购物车就认为没有登陆
        //解决方法:加上Feign的自己的拦截器
        List<OrderItemVo> currentUserCarItems = cartFeignService.getCurrentUserCarItems();
        orderConfirmVo.setItems(currentUserCarItems);

        //查询用户积分
        Integer integration = memberResponseVo.getIntegration();
        orderConfirmVo.setIntegration(integration);
        
        //TODO 接口幂等性,防重令牌
        
        return orderConfirmVo;
    }

购物车服务:

拦截器部分代码:

	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //登陆了就有用户id,没有登陆就为用户设置临时key
        UserInfoTo userInfoTo = new UserInfoTo();

        //通过SpringSession统一管理各服务之间的session
        HttpSession session = request.getSession();
        MemberResponseVo user = (MemberResponseVo)session.getAttribute(AuthConstant.LOGIN_USER);
        //用户已登录,设置id,无需设置key,拿到该id
        if(user!=null){
            userInfoTo.setUserId(user.getId());
        }
    }

总结一下流程,即:当用户在购物车中选好商品并点击结算时,需要跳转到订单详情页。此时触发订单服务,订单服务"toTrade"在service层通过Feign远程调用,获取用户的收货地址以及购物车中选中的商品详情。而购物车服务中存在一个拦截器,该拦截器的作用是从session域中获取信息判断用户是否登陆。而通过远程Feign调用的话,Feign会默认构造一个新的request请求,该请求不携带头信息,因此购物车服务就无法通过拦截器从session域中得知用户是否登陆,这个时候默认用户没有登陆,而没有登陆的用户无法获取到购物车详情信息。

源码解析丢失请求头原因:

在这里插入图片描述
流程图解释:

在这里插入图片描述
解决方法:

在对应的controller中,添加request参数。以确保订单服务中的RequestInterceptor拦截器中,能够通过线程上下文拿到该请求。Order服务Controller->Order服务service->Order服务Feign调用订单服务->订单服务requestIntecerptor拦截器,一条线程中执行,由一个ThreadLocal共享线程数据。因此能够通过线程上下文拿到对应的请求。

@GetMapping("/toTrade")
    public String toTrade(Model model){

         OrderConfirmVo orderConfirmVo = orderService.confirmOrder();

         model.addAttribute("orderConfirmData",orderConfirmVo);

        return "confirm";

    }

在订单服务中添加RequestInterceptor拦截器到容器中

@Configuration
public class mailFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //远程之前均会先进行requestInterceptor.apply方法
                //使用RequestContextHolder从请求下文中获得请求。即从controller参数中获得request信息
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = requestAttributes.getRequest();//老请求
                if(request != null){
                    //同步头信息,cookie
                    String cookie = request.getHeader("Cookie");
                    //构造新请求,获取cookie
                    requestTemplate.header("Cookie",cookie);
                }
            }
        };
    }

}

异步调用中Feign远程调用丢失请求头的情况

由于异步编排是多线程执行,不属于以上Controller->service->Feign->RequestIntecerptor一条线程执行,因此可以手动通过RequestContextHolder设置请求信息,为每个线程保存自己的ThreadLocal变量,让各自的RequestIntecerptor去Threadlocal中获取自己需要的信息,示例代码如下:

代码案例

 @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

        MemberResponseVo memberResponseVo = OrderLoginIntercepted.threadLocal.get();

        //在多线程异步任务之前,通过线程上下文获取到旧请求的属性(带头信息)。即从订单服务Controller防范参数的request中获取旧请求的属性。
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> {
        	//手动设置请求属性,确保会员服务的RequestIntecerptor能够拿到该请求信息
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //远程查询用户地址
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
            orderConfirmVo.setMemberAddressVos(address);
        }, threadPoolExecutor);

        CompletableFuture<Void> getCurrentUserCartItems = CompletableFuture.runAsync(() -> {
            //手动设置旧属性,确保订单服务的RequestIntecerptor能够拿到该请求信息
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //远程查询购物车所有购物项。
            List<OrderItemVo> currentUserCarItems = cartFeignService.getCurrentUserCarItems();
            orderConfirmVo.setItems(currentUserCarItems);
        }, threadPoolExecutor);

        //查询用户积分
        Integer integration = memberResponseVo.getIntegration();
        orderConfirmVo.setIntegration(integration);

        //其他数据自动计算

        //TODO 接口幂等性,防重令牌

        //等待所有异步任务完成
        CompletableFuture.allOf(getAddress,getCurrentUserCartItems).get();


        return orderConfirmVo;
    }

两者区别

在这里插入图片描述
详细视频教学:Feign远程调用丢失

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值