实际场景
业务场景是这样,用户下单,首先是浏览器请求订单接口,订单接口可以从请求头中获取携带的cookie里面的数据,通过实现HandlerInterceptor拦截器接口的preHandle前置(接口执行之前)方法,在preHandle方法内取到cookie里面的用户信息(userCode 用户标识),然后使用userCode去redis中查询出对应的用户信息,然后使用ThreadLocal<UserInfo>将用户信息保存在当前请求的上下文当中(一个请求在一个线程当中,可以共享ThreadLocal内的数据 (但是如果在一个请求当当中feign调用了其它服务,那么在其它服务的程序里面就无法访问当前发起调用的这边的ThreadLocal内的数据了,本文主要就是用其它方法解决这个问题))
看了feign源码就会知道,feign在远程调用之前要构造请求,会调用很多的拦截器,那么我们就只需要添加一个拦截器,将当前请求体中的request内的cookie放到构造的这个请求里面即可。然后其它服务也实现了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,就解决了这个问题!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/105990.html