谷粒商城实战笔记-265~268-商城业务-订单服务-订单确认页模型抽取和数据填充-Feign丢失数据问题

一,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 @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();
    }
}

二,266-商城业务-订单服务-订单确认页数据获取

这一节的主要内容,是获取订单确认页面需要的数据。

1,会员地址信息

要获取会员地址信息,需要远程调用会员服务。

会员地址存储在ums_member_receive_address表中,根据member_id即会员Id即可获取。

2,购物车勾选中的购物项信息

用户在购物车点击结算,进入确认界面,确认界面要展示所有用户勾选的购物商品信息,以便会员确认。

所以需要调用购物车服务的接口获取购物项信息。

3,获取用户积分信息

用户积分信息保存在用户信息中,登录时会查询出来保存在session中,直接从session取出即可。

三,267-商城业务-订单服务-Feign远程调用丢失请求头问题

1,Feign远程调用丢失请求头

在这里插入图片描述
根据图中的流程,Feign远程调用丢失cookie的原因可以分析如下:

  1. 浏览器发送请求:浏览器向order服务发送请求,请求头中自动携带了cookie。

  2. Feign远程调用order服务通过Feign远程调用cart服务

  3. 创建新request:在Feign远程调用过程中,创建了一个新的request,这个新request没有任何请求头,没把浏览器携带的请求头复制到新的请求头中。

  4. 丢失cookie:由于新request没有携带任何请求头,因此丢失了原本在浏览器请求中自动携带的cookie。

  5. cart服务认为未登录cart服务接收到的请求没有请求头,因此认为用户未登录。

Feign远程调用过程中,由于创建了一个新的request,这个新request没有携带任何请求头,包括原本在浏览器请求中自动携带的cookie,导致cart服务接收到的请求没有cookie,从而认为用户未登录。

2,解决方案

这段代码定义了一个名为GuliFeignConfig的配置类,该类中包含一个@Bean注解的方法requestInterceptor,用于创建和配置一个RequestInterceptor实例。RequestInterceptor是一个接口,用于拦截Feign客户端的请求,并在请求发送之前对其进行修改。

解释说明

  1. 创建拦截器实例

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
        // 创建RequestInterceptor实例
        RequestInterceptor requestInterceptor = new RequestInterceptor() {
            // 实现apply方法
            @Override
            public void apply(RequestTemplate template) {
                // 在这里添加自定义的逻辑
            }
        };
        return requestInterceptor;
    }
    

    这段代码创建了一个RequestInterceptor的实例,并实现了apply方法。apply方法会在Feign客户端发送请求之前被调用,用于修改请求模板。

  2. 获取请求头

    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客户端发送请求时,RequestInterceptorapply方法会被调用。在这个方法中,通过RequestContextHolder获取到当前请求的HttpServletRequest对象,从而获取到请求头中的Cookie值。然后,将这个Cookie值添加到RequestTemplate的请求头中,确保Feign远程调用时携带了这个Cookie

通过这种方式,即使在Feign远程调用过程中创建了新的请求,这个新的请求也会携带原来的Cookie,从而解决了Feign远程调用丢失cookie的问题。

四,268-商城业务-订单服务-Feign异步调用丢失请求头问题

在请求多个信息时,我们使用了多线程,这就带来了一个问题,前面我们解决Feign丢失请求头的方案在多线程下,不再有效,丢失请求头的问题再度出现。

在请求多个信息时,我们使用了多线程,这就带来了一个问题,前面我们解决Feign丢失请求头的方案在多线程下,不再有效,丢失请求头的问题再度出现。

在这里插入图片描述

单线程下生效的原理

  1. 请求处理流程
    在单线程环境下,请求的处理流程是顺序的。当一个请求到达时,它会被控制器(Controller)处理。控制器会调用服务(Service)来处理业务逻辑。在这个过程中,请求相关的数据会被存储在ThreadLocal中。

  2. RequestContextHolder
    RequestContextHolder是一个工具类,它提供了获取当前请求相关数据的方法。在单线程环境下,当一个请求被处理时,RequestContextHolder.getRequestAttributes()会返回当前请求的ServletRequestAttributes。这个ServletRequestAttributes包含了请求相关的数据,包括请求头。

  3. RequestInterceptor
    当Feign客户端发送请求时,RequestInterceptorapply方法会被调用。在这个方法中,通过RequestContextHolder.getRequestAttributes()获取当前请求的ServletRequestAttributes,然后将请求头添加到新的请求中。由于在单线程环境下,请求的处理是顺序的,因此RequestInterceptor能够正确地获取到请求头,并将其添加到新的请求中。

多线程下Interceptor不生效的原因

  1. 多线程环境
    在多线程环境下,每个线程都有自己的请求上下文。当一个线程处理请求时,它会将请求相关的数据存储在ThreadLocal中。然而,当这个线程处理完请求后,它会释放这些数据,以便其他线程可以使用ThreadLocal来存储自己的请求数据。

  2. RequestContextHolder
    在多线程环境下,当一个线程处理完请求后,它会释放ThreadLocal中的请求数据。这意味着,当另一个线程处理请求时,它不会访问到前一个线程的请求数据,包括请求头。因此,即使使用了RequestInterceptor,多线程下还是会丢失header头。

  3. 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();

    @Override
    public void run() {
        //每一个线程都来共享之前的请求数据
        RequestContextHolder.setRequestAttributes(requestAttributes);
        myRun();
    }
}

用法如下:
在这里插入图片描述
使用第二种方法,巧妙地把设置对象的工作放在了创建线程对象的构造过程中。

4,查询库存信息

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小手追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值