实际场景

(但是如果在一个请求当当中feign调用了其它服务,那么在其它服务的程序里面就无法访问当前发起调用的这边的ThreadLocal内的数据了,本文主要就是用其它方法解决这个问题))

其它服务也实现了HandlerInterceptor拦截器接口的preHandle方法,也是在方法内取到cookie里面的用户信息(userCode  用户标识),然后使用userCode去redis中查询出对应的用户信息,然后使用ThreadLocal<UserInfo>将用户信息保存在当前请求的上下文当中。

上代码

WsFeignConfig.class   添加一个feign在远程调用之前要调用的拦截器

@Configuration
public class WsFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                //1.RequestContextHolder拿到request请求(通过threadLocal)
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = attributes.getRequest();//旧请求
                //同步请求头数据,cookie  将当前请求头的cookie携带在发往的请求头中
                if (request != null) {
                    //2.同步请求头信息Cookie
                    String cookie = request.getHeader("Cookie");
                    //给新请求同步了老请求的cookie
                    template.header("Cookie", cookie);
                    //System.out.println("feign远程之前先进行requestInterceptor()");
                }
            }
        };
    }
}

确认订单方法

/**
     * 确认订单
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        //拿到当前主线程的request里面的数据
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //1.远程客户地址列表
            //每个线程共享之前的请求数据
            //异步线程会丢失当前主线程的request上下文信息,所以需要将主线程中的request信息保存在异步线程中
            //将主线程request里面的数据赋给当前异步的线程中的request
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeginService.getAddress(memberRespVo.getId());
            confirmVo.setAddress(address);
        }, executor);
        CompletableFuture<Void> getCartFuture = CompletableFuture.runAsync(() -> {
            //2.远程购物车所有选中的购物项
            //每个线程共享之前的请求数据
            //异步线程会丢失当前主线程的request上下文信息,所以需要将主线程中的request信息保存在异步线程中
            //将主线程request里面的数据赋给当前异步的线程中的request
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //feign在远程调用之前要构造请求,会调用很多的拦截器
            List<OrderItemVo> items = cartFeginService.getCurrentUserCartItems();
            confirmVo.setItems(items);
        }, executor).thenRunAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = confirmVo.getItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            R hasStock = wmsFeignService.getSkuHasStock(collect);
            List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
            });
            if (data != null) {
                Map<Long, Boolean> collect1 = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(collect1);
            }
        }, executor);
        //3.查询用户积分
        Integer integration = memberRespVo.getIntegration();
        confirmVo.setIntegration(integration);
        //4.防重复令牌提交
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES);
        confirmVo.setToken(token);
        CompletableFuture.allOf(getAddressFuture, getCartFuture).get();
        return confirmVo;
    }

执行接口之前的拦截器(获取用户信息,并将用户信息放到ThreadLocal中(当前线程的上下文))

@Component
public class LoginUserInterceptor implements HandlerInterceptor{

    public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //某些请求不需要进行认证,获取到请求的url,判断如果是不需要认证的请求直接返回true
        String uri = request.getRequestURI();
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        boolean match = antPathMatcher.match("/order/order/status/**", uri);
        boolean match1 =antPathMatcher.match("/payed/notify", uri);
        boolean match2 =antPathMatcher.match("/swagger-ui.html", uri);
        if(match || match1 || match2){
            return true;
        }
        MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);
        if(attribute!=null){
            loginUser.set(attribute);
            return true;
        }else {
            //跳转登录页
            request.getSession().setAttribute("msg","请先登录");
            response.sendRedirect("http://auth.gulimall.com/login.html");
            return false;
        }
    }
}

总结

        上实际业务场景,用户在网页进行下单,首先打到order服务的下单接口,这一次请求是从浏览器发起的请求,所以请求头会携带上浏览器上的所有cookie,然后下单接口执行之前会执行拦截器的前置方法将cookie里面的userCode取出来,然后通过userCode去redis中获取到对应的用户信息,并将用户信息保存到ThreadLocal中(存放在当前线程的上下文当中),然后执行了一大堆封装操作后,会调用购物车服务,获取用户在浏览器选中的商品,并且要实时获取选中的商品最新的价格,因为购物车的数据是缓存在redis中的,商品的价格可能不是最新的价格,所以价格是需要去商品中心重新查询一次的。那么问题就来了,调用购物车服务查询选中商品的接口没有传任何数据,购物车服务也有拦截器,也是从请求头中获取到所有cookie,并将userCode取出来然后去reids中获取对应的用户信息,并将用户信息放ThreadLocal中(当前线程上下文),然后通过用户唯一标识去redis中获取选中的商品信息,然后使用sku遍历去商品中心查询每个商品信息最新的价格,然后返回。所以最主要解决的问题就是在order服务中使用Feign远程调用购物车服务的时候,只要能在Feign构造的请求头中携带上浏览器发送给order服务请求头中的所有cookie,就解决了这个问题!

java feign调用自定义decoder feign调用header_spring