文章目录
- 一,265-商城业务-订单服务-订单确认页模型抽取
- 1,订单确认页面
- 2,页面Vo
- 二,266-商城业务-订单服务-订单确认页数据获取
- 1,会员地址信息
- 2,购物车勾选中的购物项信息
- 3,获取用户积分信息
- 三,267-商城业务-订单服务-Feign远程调用丢失请求头问题
- 1,Feign远程调用丢失请求头
- 2,解决方案
- 解释说明
- 如何起作用
- 四,268-商城业务-订单服务-Feign异步调用丢失请求头问题
- 单线程下生效的原理
- 多线程下Interceptor不生效的原因
- 解决方案
- 1,不优雅的方法
- 2,优雅的方法
- 4,查询库存信息
一,265-商城业务-订单服务-订单确认页模型抽取
1,订单确认页面
从购物车点击结算,进入订单确认页面:
2,页面Vo
订单确认页面包含:
- 订单会员地址信息
- 订单明细信息
- 支付方式
- 会员积分
- 订单总额
- 应付总额
package com.atguigu.gulimall.order.vo;public class OrderConfirmVo {@Getter @Setter/** 会员收获地址列表 **/List<MemberAddressVo> memberAddressVos;@Getter @Setter/** 所有选中的购物项 **/List<OrderItemVo> items;/** 发票记录 **/@Getter @Setter/** 优惠券(会员积分) **/private Integer integration;/** 防止重复提交的令牌 **/@Getter @Setterprivate String orderToken;@Getter @SetterMap<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();}
}
二,266-商城业务-订单服务-订单确认页数据获取
这一节的主要内容,是获取订单确认页面需要的数据。
1,会员地址信息
要获取会员地址信息,需要远程调用会员服务。
会员地址存储在ums_member_receive_address
表中,根据member_id
即会员Id即可获取。
2,购物车勾选中的购物项信息
用户在购物车点击结算,进入确认界面,确认界面要展示所有用户勾选的购物商品信息,以便会员确认。
所以需要调用购物车服务的接口获取购物项信息。
3,获取用户积分信息
用户积分信息保存在用户信息中,登录时会查询出来保存在session中,直接从session取出即可。
三,267-商城业务-订单服务-Feign远程调用丢失请求头问题
1,Feign远程调用丢失请求头
根据图中的流程,Feign远程调用丢失cookie的原因可以分析如下:
-
浏览器发送请求:浏览器向
order服务
发送请求,请求头中自动携带了cookie。 -
Feign远程调用:
order服务
通过Feign远程调用cart服务
。 -
创建新request:在Feign远程调用过程中,创建了一个新的request,这个新request没有任何请求头,没把浏览器携带的请求头复制到新的请求头中。
-
丢失cookie:由于新request没有携带任何请求头,因此丢失了原本在浏览器请求中自动携带的cookie。
-
cart服务认为未登录:
cart服务
接收到的请求没有请求头,因此认为用户未登录。
Feign远程调用过程中,由于创建了一个新的request,这个新request没有携带任何请求头,包括原本在浏览器请求中自动携带的cookie,导致cart服务
接收到的请求没有cookie,从而认为用户未登录。
2,解决方案
这段代码定义了一个名为GuliFeignConfig
的配置类,该类中包含一个@Bean
注解的方法requestInterceptor
,用于创建和配置一个RequestInterceptor
实例。RequestInterceptor
是一个接口,用于拦截Feign客户端的请求,并在请求发送之前对其进行修改。
解释说明
-
创建拦截器实例:
@Bean("requestInterceptor") public RequestInterceptor requestInterceptor() {// 创建RequestInterceptor实例RequestInterceptor requestInterceptor = new RequestInterceptor() {// 实现apply方法@Overridepublic void apply(RequestTemplate template) {// 在这里添加自定义的逻辑}};return requestInterceptor; }
这段代码创建了一个
RequestInterceptor
的实例,并实现了apply
方法。apply
方法会在Feign客户端发送请求之前被调用,用于修改请求模板。 -
获取请求头:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) {HttpServletRequest request = requestAttributes.getRequest();if (request != null) {String cookie = request.getHeader("Cookie");template.header("Cookie", cookie);} }
在
apply
方法中,首先通过RequestContextHolder.getRequestAttributes()
获取当前请求的ServletRequestAttributes
。然后,从ServletRequestAttributes
中获取HttpServletRequest
对象,从而获取到当前请求的请求头。最后,从请求头中获取Cookie
值,并将其添加到RequestTemplate
的请求头中。
如何起作用
当Feign客户端发送请求时,RequestInterceptor
的apply
方法会被调用。在这个方法中,通过RequestContextHolder
获取到当前请求的HttpServletRequest
对象,从而获取到请求头中的Cookie
值。然后,将这个Cookie
值添加到RequestTemplate
的请求头中,确保Feign远程调用时携带了这个Cookie
。
通过这种方式,即使在Feign远程调用过程中创建了新的请求,这个新的请求也会携带原来的Cookie
,从而解决了Feign远程调用丢失cookie的问题。
四,268-商城业务-订单服务-Feign异步调用丢失请求头问题
在请求多个信息时,我们使用了多线程,这就带来了一个问题,前面我们解决Feign丢失请求头的方案在多线程下,不再有效,丢失请求头的问题再度出现。
在请求多个信息时,我们使用了多线程,这就带来了一个问题,前面我们解决Feign丢失请求头的方案在多线程下,不再有效,丢失请求头的问题再度出现。
单线程下生效的原理
-
请求处理流程:
在单线程环境下,请求的处理流程是顺序的。当一个请求到达时,它会被控制器(Controller)处理。控制器会调用服务(Service)来处理业务逻辑。在这个过程中,请求相关的数据会被存储在ThreadLocal
中。 -
RequestContextHolder:
RequestContextHolder
是一个工具类,它提供了获取当前请求相关数据的方法。在单线程环境下,当一个请求被处理时,RequestContextHolder.getRequestAttributes()
会返回当前请求的ServletRequestAttributes
。这个ServletRequestAttributes
包含了请求相关的数据,包括请求头。 -
RequestInterceptor:
当Feign客户端发送请求时,RequestInterceptor
的apply
方法会被调用。在这个方法中,通过RequestContextHolder.getRequestAttributes()
获取当前请求的ServletRequestAttributes
,然后将请求头添加到新的请求中。由于在单线程环境下,请求的处理是顺序的,因此RequestInterceptor
能够正确地获取到请求头,并将其添加到新的请求中。
多线程下Interceptor不生效的原因
-
多线程环境:
在多线程环境下,每个线程都有自己的请求上下文。当一个线程处理请求时,它会将请求相关的数据存储在ThreadLocal
中。然而,当这个线程处理完请求后,它会释放这些数据,以便其他线程可以使用ThreadLocal
来存储自己的请求数据。 -
RequestContextHolder:
在多线程环境下,当一个线程处理完请求后,它会释放ThreadLocal
中的请求数据。这意味着,当另一个线程处理请求时,它不会访问到前一个线程的请求数据,包括请求头。因此,即使使用了RequestInterceptor
,多线程下还是会丢失header头。 -
Controller和Service是否在同一线程:
在多线程环境下,控制器(Controller)和服务(Service)可能不在同一个线程中。当控制器处理请求时,它会将请求相关的数据存储在ThreadLocal
中。然而,当控制器调用服务来处理业务逻辑时,这个请求数据可能会被释放,以便其他线程可以使用ThreadLocal
来存储自己的请求数据。因此,即使服务在同一个线程中,它也可能无法访问到控制器的请求数据,包括请求头。
通过以上分析,单线程下生效的原理在于请求的处理是顺序的,RequestInterceptor
能够正确地获取到请求头,并将其添加到新的请求中。而多线程下Interceptor不生效的原因在于ThreadLocal
的作用域和多线程环境,以及控制器和服务可能不在同一个线程中。
解决方案
1,不优雅的方法
在创建线程的之前把ThreadLocal中内容取出,然后设置到子线程的ThreadLocal中。
但这样做会侵入业务代码,且每使用一个线程就要写这样一段代码,还会导致大量冗余代码。
2,优雅的方法
创建一个抽象类,实现Runnable
方法。
public abstract class MyRunnable implements Runnable{private RequestAttributes requestAttributes;public MyRunnable() {requestAttributes = RequestContextHolder.getRequestAttributes();}public abstract void myRun();@Overridepublic void run() {//每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);myRun();}
}
用法如下:
使用第二种方法,巧妙地把设置对象的工作放在了创建线程对象的构造过程中。