示例以谷粒商城订单系统远程调用购物车系统为例!!!
Feign远程调用丢失header请求头的问题
Feign在远程调用之前要构造请求 调用很多拦截器,
订单服务的ToTrade请求带了cookie等头信息,但是要进行远程调用时,Feign创建了一个新的请求,这个新请求里面没有任何头信息,所以会丢失请求头;导致购物车服务认为这个请求没登录;
解决:
加上feign远程调用的请求拦截器RequestInterceptor
,会首先调用请求拦截器的apply()
方法,在apply()方法里面通过RequestContextHolder
拿到刚进来这个请求,然后给新请求同步了老请求的Cookie;
新请求是RequestTemplate,老请求是HttpServletRequest;
package com.atguigu.gulimall.order.config;
@Configuration
public class OrderFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor(){
// Feign在远程调用之前都会先经过这个方法
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
// RequestContextHolder拿到刚进来这个请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(attributes != null){
HttpServletRequest request = attributes.getRequest();
if(request != null){
// 同步请求头数据
String cookie = request.getHeader("Cookie");
// RequestTemplate template是新请求,给新请求同步Cookie
template.header("Cookie", cookie);
}
}
}
};
}
}
Feign异步调用丢失header请求头的问题
因为异步线程需要新的线程,而新的线程里没有request数据,所以我们自己设置进去
将主线程的header数据又再次手动赋值给子线程;
RequestContextHolder里面是使用ThreadLocal来存储RequestAttributes的,所以异步情况下拿不到
(1)先拿到原请求数据:RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
(2)再在每个子线程进来时把数据设置进去:RequestContextHolder.setRequestAttributes(attributes);
@Override // OrderServiceImpl
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
// 获取用户,用用户信息获取购物车
MemberRespVo memberRespVo = LoginUserInterceptor.threadLocal.get();
// 封装订单
OrderConfirmVo confirmVo = new OrderConfirmVo();
// 我们要从request里获取用户数据,但是其他线程是没有这个信息的,
// 所以可以手动设置新线程里也能共享当前的request数据
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
// 1.远程查询所有的收货地址列表
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
// 因为异步线程需要新的线程,而新的线程里没有request数据,所以我们自己设置进去
RequestContextHolder.setRequestAttributes(attributes);
List<MemberAddressVo> address;
try {
address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddress(address);
} catch (Exception e) {
log.warn("\n远程调用会员服务失败 [会员服务可能未启动]");
}
}, executor);
// 2. 远程查询购物车服务,并得到每个购物项是否有库存
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
// 异步线程共享 RequestContextHolder.getRequestAttributes()
RequestContextHolder.setRequestAttributes(attributes);
// feign在远程调用之前要构造请求 调用很多拦截器
// 远程获取用户的购物项
List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
confirmVo.setItems(items);
}, executor).thenRunAsync(() -> {
RequestContextHolder.setRequestAttributes(attributes);
List<OrderItemVo> items = confirmVo.getItems();
// 获取所有商品的id
List<Long> skus = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
R hasStock = wmsFeignService.getSkuHasStock(skus);
List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {});
if (data != null) {
// 各个商品id 与 他们库存状态的映射map // 学习下收集成map的用法
Map<Long, Boolean> stocks = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
confirmVo.setStocks(stocks);
}
}, executor);
// 3.查询用户积分
Integer integration = memberRespVo.getIntegration();
confirmVo.setIntegration(integration);
// 4.其他数据在类内部自动计算
// TODO 5.防重令牌 设置用户的令牌
String token = UUID.randomUUID().toString().replace("-", "");
confirmVo.setOrderToken(token);
// redis中添加用户id,这个设置可以防止订单重复提交。生成完一次订单后删除redis
stringRedisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 10, TimeUnit.MINUTES);
// 等待所有异步任务完成
CompletableFuture.allOf(getAddressFuture, cartFuture).get();
return confirmVo;
}