谷粒商城-高级-68 -商城业务-订单服务-Feign 远程调用丢失请求头问题
一、订单结算页
订单结算页Vo:gulimall-order/xxx/order/vo/OrderConfirmVo.java
package com.atguigu.gulimall.order.vo;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* 订单确认页需要用到的数据
*
* @author: kaiyi
* @create: 2020-09-14 00:38
*/
public class OrderConfirmVo {
// 收货地址,ums_member_receive_address 表
@Getter @Setter
/** 会员收获地址列表 **/
List<MemberAddressVo> address;
@Getter @Setter
/** 所有选中的购物项 **/
List<OrderItemVo> items;
/** 发票记录 **/
@Getter @Setter
/** 优惠券(会员积分) **/
private Integer integration;
/** 防止重复提交的令牌 **/
@Getter @Setter
private String orderToken;
@Getter @Setter
Map<Long,Boolean> stocks;
public Integer getCount() {
Integer count = 0;
if (items != null && items.size() > 0) {
for (OrderItemVo item : items) {
count += item.getCount();
}
}
return count;
}
/** 订单总额 **/
//BigDecimal total;
//计算订单总额
public BigDecimal getTotal() {
BigDecimal totalNum = BigDecimal.ZERO;
if (items != null && items.size() > 0) {
for (OrderItemVo item : items) {
//计算当前商品的总价格
BigDecimal itemPrice = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
//再计算全部商品的总价格
totalNum = totalNum.add(itemPrice);
}
}
return totalNum;
}
/** 应付价格 **/
//BigDecimal payPrice;
public BigDecimal getPayPrice() {
return getTotal();
}
}
订单结算页控制器:gulimall-order/xxx/order/web/OrderWebController.java
/**
* @author: kaiyi
* @create: 2020-09-14 09:43
*/
@Controller
public class OrderWebController {
@Autowired
OrderService orderService;
@GetMapping("/toTrade")
public String toTrade(Model model, HttpServletRequest request){
OrderConfirmVo confirmVo = orderService.confirmOrder();
model.addAttribute("confirmOrderData",confirmVo);
//展示订单确认的数据
// 展示订单确认的数据
return "confirm";
}
}
实现类:gulimall-order/xxx/order/service/impl/OrderServiceImpl.java
@Override
public OrderConfirmVo confirmOrder() {
// 构建OrderConfirmVo
OrderConfirmVo confirmVo = new OrderConfirmVo();
// 获取当前用户登录的信息
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
// 1、远程查询所有的收货地址列表
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
confirmVo.setAddress(address);
// 2、远程获取购物车所有选中的的购物项
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
confirmVo.setItems(currentCartItems);
// 3、查询用户积分
Integer integration = memberResponseVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、价格数据自动计算
// TODO 5、防重令牌
return confirmVo;
}
展示结算页面:
http://order.gulimall.com/toTrade
二、Feign远程调用丢失请求头的问题
在远程调用购物车会有问题,因为通过feign api接口请求购物车的微服务没有携带cookie信息,购物车校验没登录信息,直接返回空了,所以,获取不到购物车的信息。
要解决Feign调用丢失请求头的问题,加上Feign远程调用的请求拦截器。在Feign构建请求参数的时候,Feign拦截器会把增加的头信息参数添加进来。
创建Feign拦截器:gulimall-order/xxx/order/config/GuliFeignConfig.java
package com.atguigu.gulimall.order.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* feign拦截器功能(在远程请求时携带认证信息cookie)
*
* @author: kaiyi
* @create: 2020-09-14 14:26
*/
@Configuration
public class GuliFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
//1、使用RequestContextHolder拿到刚进来的请求数据
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
//老请求,携带了cookie
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
//2、同步请求头的数据(主要是cookie)
//把老请求的cookie值放到新请求上来,进行一个同步
String cookie = request.getHeader("Cookie");
// 给新请求同步了老请求的cookie
template.header("Cookie", cookie);
}
}
}
};
return requestInterceptor;
}
}
添加了Feign拦截器之后,然后在 gulimall-cart
拦截器添加断点进行调试,可以看到,通过 gulimall-order
远程调用购物车服务,已经携带了cookie,购物车也可以根据cookie获取到登录的信息。
三、异步编排
在订单确认页,需要请求的数据比较多,该方法里边有两个远程调用,为了提高页面的响应速度,所以,可以考虑将确认订单的代码异步编排。
需要优化的代码:gulimall-order/xxx/order/service/impl/OrderServiceImpl.java
@Override
public OrderConfirmVo confirmOrder() {
// 构建OrderConfirmVo
OrderConfirmVo confirmVo = new OrderConfirmVo();
// 获取当前用户登录的信息
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
// 1、远程查询所有的收货地址列表
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
confirmVo.setAddress(address);
// 2、远程获取购物车所有选中的的购物项
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
confirmVo.setItems(currentCartItems);
// 3、查询用户积分
Integer integration = memberResponseVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、价格数据自动计算
// TODO 5、防重令牌
return confirmVo;
}
订单微服务gulimall-order
引入线程池相关配置,可以参考我们gulimall-cart
微服务的相关文章。
异步编排后的代码:
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
// 构建OrderConfirmVo
OrderConfirmVo confirmVo = new OrderConfirmVo();
// 获取当前用户登录的信息
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
// 开启第一个异步任务
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(()->{
// 1、远程查询所有的收货地址列表
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
confirmVo.setAddress(address);
}, threadPoolExecutor);
// 开启第二个异步任务
CompletableFuture<Void> cartInfoFuture = CompletableFuture.runAsync(()->{
// 2、远程获取购物车所有选中的的购物项
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
confirmVo.setItems(currentCartItems);
// feign在远程调用之前要构造请求,调用很多的拦截器
// RequestInterceptor interceptor: requestInterceptors
}, threadPoolExecutor);
// 3、查询用户积分
Integer integration = memberResponseVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、价格数据自动计算
// TODO 5、防重令牌
// 异步编排
CompletableFuture.allOf(addressFuture,cartInfoFuture).get();
return confirmVo;
}
四、Feign异步情况调用丢失请求头的问题
通过上边的异步编排,我们在调试时发现了一个问题,即在异步模式下,Feign丢失了请求头,这是什么原因造成的呢?原因是在一连串调用(同步调用)时,从前到后都是同一条线程,所以,上下文、ThreadLocal 共享,如果使用异步,则新开了一个线程,而新开的线程没有相关的请求头信息。
上图说明:72表示orderService 主线程, 101表示异步获取address的线程,102表示异步获取cart的线程,这两个异步线程通过拦截器拿不到72号的主线程的共享ThreadLocal,在拦截器会报空指针异常。
为了解决异步请求头丢失的问题,我们可以在异步调用时,RequestContextHolder
获取当前主线程的请求头信息,然后将住线程请求头信息放入到异步请求属性里边。
//TODO :获取当前线程(主线程)请求头信息(解决Feign异步调用丢失请求头问题)
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//每一个线程都来共享之前的请求数据(设置属性代码需要放在异步任务代码片段里边)
RequestContextHolder.setRequestAttributes(requestAttributes);
完整代码:
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
// 构建OrderConfirmVo
OrderConfirmVo confirmVo = new OrderConfirmVo();
// 获取当前用户登录的信息
MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
//TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题)
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 开启第一个异步任务
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(()->{
//每一个线程都来共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
// 1、远程查询所有的收货地址列表
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
confirmVo.setAddress(address);
}, threadPoolExecutor);
// 开启第二个异步任务
CompletableFuture<Void> cartInfoFuture = CompletableFuture.runAsync(()->{
//每一个线程都来共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
// 2、远程获取购物车所有选中的的购物项
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
confirmVo.setItems(currentCartItems);
// feign在远程调用之前要构造请求,调用很多的拦截器
// RequestInterceptor interceptor: requestInterceptors
}, threadPoolExecutor);
// 3、查询用户积分
Integer integration = memberResponseVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、价格数据自动计算
// TODO 5、防重令牌
// 异步编排
CompletableFuture.allOf(addressFuture,cartInfoFuture).get();
return confirmVo;
}
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)