谷粒商城-订单业务

目录 

 

商城业务-订单服务-页面环境搭建

商城业务-订单服务-整合SpringSession

商城业务-订单服务-订单基本概念

商城业务-订单服务-订单登录拦截

商城业务-订单服务-订单确认页模型抽取

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

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

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

商城业务-订单服务-bug修改

商城业务-订单服务-订单确认页渲染

商城业务-订单服务-订单确认页库存查询

商城业务-订单服务-订单确认页模拟运费效果

商城业务-订单服务-订单确认页细节显示

商城业务-订单服务-接口幂等性讨论

商城业务-订单服务-订单确认页完成

商城业务-订单服务-原子验令牌

商城业务-订单服务-构造订单数据

商城业务-订单服务-构造订单项数据

商城业务-订单服务-订单验价

商城业务-订单服务-保存订单数据

商城业务-订单服务-锁定库存

商城业务-订单服务-提交订单的问题


商城业务-订单服务-页面环境搭建

1.将订单服务注册到注册中心去

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

谷粒商城-订单业务_第1张图片2. 导入thymeleaf依赖并在开发期间禁用缓存



    org.springframework.boot
    spring-boot-starter-thymeleaf
spring:
  thymeleaf:
    cache: false # thymeleaf缓存关闭

3.在/mydata/nginx/html/static下面创建order文件夹,在order文件夹下分别创建detail(订单详情)、list(订单列表)、confirm(确认订单)、pay(支付订单)文件夹,用于存放订单相关的静态资源

谷粒商城-订单业务_第2张图片

谷粒商城-订单业务_第3张图片谷粒商城-订单业务_第4张图片谷粒商城-订单业务_第5张图片

谷粒商城-订单业务_第6张图片

4. 将index.html依次复制到templates并进行更名,修改其中的请求资源路径

谷粒商城-订单业务_第7张图片

谷粒商城-订单业务_第8张图片 

并加入thymeleaf的名称空间

5.配置订单服务的域名

谷粒商城-订单业务_第9张图片

6.配置网关

7. 编写Controller访问订单页面

谷粒商城-订单业务_第10张图片

出现错误:confirm.html 报 Unfinished block structure

谷粒商城-订单业务_第11张图片

解决方案: 将/*删除即可

谷粒商城-订单业务_第12张图片

商城业务-订单服务-整合SpringSession

1.Redis默认使用lettuce作为客户端可能导致内存泄漏,因此需要排除lettuce依赖,使用jedis作为客户端或者使用高版本的Redis依赖即可解决内存泄漏问题

解决方案1:

 

     org.springframework.boot
     spring-boot-starter-data-redis



    redis.clients
    jedis

解决方案2: 我使用时官方已经修复了lettuce内存泄漏的问题,因此,我采用的是方案2

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

配置 Redis

spring:
  redis:
    host: 192.168.56.22
    port: 6379

2. 导入Session依赖

        
        
            org.springframework.session
            spring-session-data-redis
        

配置 Session的存储类型

# 会话存储类型
spring.session.store-type=redis

编写Session配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;


@Configuration
public class GulimallSessionConfig {
    /**
     * 子域问题共享解决
     */
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULIMALLSESSION");
        return cookieSerializer;
    }

    /**
     * 使用json序列化方式来序列化对象数据到redis中
     */
    @Bean
    public RedisSerializer springSessionDefaultRedisSerializer() {
       return new GenericJackson2JsonRedisSerializer();
    }
} 
  

编写线程池配置 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
public class MyThreadPoolConfig {

    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties threadPoolConfigProperties){
       return new ThreadPoolExecutor(threadPoolConfigProperties.getCoreThreadSize(),
                threadPoolConfigProperties.getMaxThreadSize(),
                threadPoolConfigProperties.getKeepAliveTime(), TimeUnit.SECONDS,new LinkedBlockingQueue<>(100000),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
    }
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
    private Integer coreThreadSize;
    private Integer maxThreadSize;
    private Integer keepAliveTime;
}

 配置

gulimall.thread.core-thread-size=20
gulimall.thread.max-thread-size=200
gulimall.thread.keep-alive-time=10

使用@EnableRedisHttpSession让Session启作用 

谷粒商城-订单业务_第13张图片

3. 登录回显

我的订单路径跳转

用户名回显

商城业务-订单服务-订单基本概念

订单服务中的数据流向如下图所示:

谷粒商城-订单业务_第14张图片

谷粒商城-订单业务_第15张图片订单简易流程图

谷粒商城-订单业务_第16张图片

商城业务-订单服务-订单登录拦截

1.路由跳转,点击去结算跳转至订单确认页

谷粒商城-订单业务_第17张图片

谷粒商城-订单业务_第18张图片谷粒商城-订单业务_第19张图片2. 只要能结算就是登录状态,因此,需要编写一个拦截器 

拦截器的编写:

import com.atguigu.common.constant.SessionAttrKeyConstant;
import com.atguigu.common.vo.MemberRespVo;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;



@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    public static ThreadLocal loginUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(SessionAttrKeyConstant.LOGIN_USER);
        if (attribute != null){
            // 登录成功后,将用户信息存储至ThreadLocal中方便其它服务获取用户信息
            loginUser.set(attribute);
            return true;
        }else {
            // 未登录请先登录
            request.getSession().setAttribute("msg","请先进行登录");
            response.sendRedirect("http://auth.gulimall.com/login.html");
            return false;
        }
    }
}

需要配置以下配置类拦截器才会生效:

谷粒商城-订单业务_第20张图片

import com.atguigu.gulimall.order.interceptor.LoginUserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {

    @Autowired
    LoginUserInterceptor loginUserInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 配置CartInterceptor拦截器拦截所有请求
        registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
    }
}

未登录消息提醒 回显

商城业务-订单服务-订单确认页模型抽取

1.购物车计算价格存在小bug,未选中的商品不应该加入总价的计算

谷粒商城-订单业务_第21张图片

解决方案:

谷粒商城-订单业务_第22张图片2. 订单确认页的数据编写

①用户地址信息,数据来源:ums_member_receive_address

谷粒商城-订单业务_第23张图片

② 商品项信息,之前编写CartItem

谷粒商城-订单业务_第24张图片

③ 优惠券信息,使用京豆的形式增加用户的积分

④ 订单总额和应付总额信息

谷粒商城-订单业务_第25张图片

3. 编写Vo

谷粒商城-订单业务_第26张图片

谷粒商城-订单业务_第27张图片

谷粒商城-订单业务_第28张图片4. 编写业务代码

谷粒商城-订单业务_第29张图片

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

1.创建出返回会员地址列表的方法,方便后续的远程服务调用

谷粒商城-订单业务_第30张图片2.远程调用会员服务查询收货地址 

开启远程服务调用功能

谷粒商城-订单业务_第31张图片3. 查询购物车时,需要查询实时的商品价格,因此,编写通过skuId查询商品价格的接口

谷粒商城-订单业务_第32张图片

4. 远程调用商品服务,查询商品的实时价格

谷粒商城-订单业务_第33张图片

5. 查询购物车接口编写

注意细节:①需要过滤选中的商品②Redis中的购物车商品的价格可能是很久之前的需要实时查询商品的价格

谷粒商城-订单业务_第34张图片

6. 远程调用购物车服务,查询购物车中的商品列表

谷粒商城-订单业务_第35张图片

7. 价格获取方法编写

谷粒商城-订单业务_第36张图片

为了防止用户重复提交提订单,需要编写一个令牌(Token)

谷粒商城-订单业务_第37张图片

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

出现问题:远程调用购物车服务,购物车认为未登录

出现问题的原因:feign构建的新请求未把老请求头给带过来

谷粒商城-订单业务_第38张图片

解决方案:feign在创建RequestTemplate之前会调用很多RequestInterceptor,可以利用RequestInterceptor将老请求头给加上

谷粒商城-订单业务_第39张图片

配置类如下: 

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;

@Configuration
public class GuliFeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                // 1.通过RequestContextHolder拿到老请求
                ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)
                        RequestContextHolder.getRequestAttributes();
                HttpServletRequest oldRequest = servletRequestAttributes.getRequest();
                // 2.同步请求头数据
                String cookie = oldRequest.getHeader("Cookie");
                template.header("Cookie",cookie);
            }
        };
    }
}

商城业务-订单服务-Feign异步调用丢失上下文的问题

1.注入线程池

2.使用异步编排,各个任务彼此之间互不相关,但是需要等待各个任务处理完成

谷粒商城-订单业务_第40张图片

出现问题: 异步任务执行远程调用时会丢失请求上下文,oldRequest会为null

谷粒商城-订单业务_第41张图片

出现问题的原因: 当我们不使用异步编排的时候也就是单线程执行的时候,请求上下文持有器即:RequestContextHolder采用的是ThreadLocal存储请求对象。当我们采用异步编排时,而是多个线程去执行,新建的线程会丢失请求对象。

谷粒商城-订单业务_第42张图片

谷粒商城-订单业务_第43张图片

解决方案: 每个新建的线程都去添加之前请求的数据

谷粒商城-订单业务_第44张图片

商城业务-订单服务-bug修改

出现问题:

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [user/cart], template might not exist or might not be accessible by any of the configured Template Resolvers

解决方案: 

谷粒商城-订单业务_第45张图片

商城业务-订单服务-订单确认页渲染

1.收货人信息回显

2. 商品信息回显

谷粒商城-订单业务_第46张图片

谷粒商城-订单业务_第47张图片

3. 商品总件数、总金额、应付金额回显

总件数计算:

谷粒商城-订单业务_第48张图片

谷粒商城-订单业务_第49张图片

商城业务-订单服务-订单确认页库存查询

1.库存服务中查询库存的方法之前已经编写好了

谷粒商城-订单业务_第50张图片

2. 远程服务接口调用编写

谷粒商城-订单业务_第51张图片

3. Vo编写

谷粒商城-订单业务_第52张图片

4. 编写异步任务查询库存信息

编写Map用于封装库存信息

谷粒商城-订单业务_第53张图片

谷粒商城-订单业务_第54张图片库存信息回显 

谷粒商城-订单业务_第55张图片

出现空指针异常:无需共享数据就不用做以下操作了

谷粒商城-订单业务_第56张图片

商城业务-订单服务-订单确认页模拟运费效果

1.远程服务调用查询地址接口编写

谷粒商城-订单业务_第57张图片

2.编写获取邮费的接口

谷粒商城-订单业务_第58张图片

谷粒商城-订单业务_第59张图片3.  地址高亮显示

为div绑定class方便找到,自定义def属性存储默认地址值,默认地址为1,否则为0

空格代表子元素 

函数调用 

谷粒商城-订单业务_第60张图片

自定义属性存储地址Id

为运费定义一个id,用于运费的回显 

为应付总额定义一个id,用于计算应付总额的回显 

为p标签绑定单击事件 

谷粒商城-订单业务_第61张图片默认地址的邮费查询

谷粒商城-订单业务_第62张图片

商城业务-订单服务-订单确认页细节显示

查询运费时连同地址信息一起返回,也就是选中地址的地址信息回显

1.编写vo

谷粒商城-订单业务_第63张图片

2.改写实现类 

谷粒商城-订单业务_第64张图片

谷粒商城-订单业务_第65张图片

3. 信息回显

谷粒商城-订单业务_第66张图片

谷粒商城-订单业务_第67张图片

商城业务-订单服务-接口幂等性讨论

什么是接口幂等性?

假设网络很慢,用户多次点击提交订单,有可能会导致数据库中插入了多条订单记录,为了避免订单的重复提交,用专业的术语就称之为接口幂等性,通俗点讲就是用户提交一次和用户提交一百次的结果是一样的,数据库中只会有一条订单记录。

谷粒商城-订单业务_第68张图片

那些情况需要防止?

谷粒商城-订单业务_第69张图片

什么情况需要接口幂等性? 

谷粒商城-订单业务_第70张图片

幂等解决方案

1.Token机制

谷粒商城-订单业务_第71张图片使用Token机制存在的危险性 

谷粒商城-订单业务_第72张图片2. 各种锁机制

数据库悲观锁的使用场景:当我们查询库存信息时可以使用悲观锁锁住这条记录确保别人拿不到。

谷粒商城-订单业务_第73张图片

数据库乐观锁的使用场景:当我们减库存操作时,带上version=1执行成功此时version=2,但是由于网络原因没有返回执行成功标识,下一次请求过来还是带上的是version=1就无法对库存进行操作。 

谷粒商城-订单业务_第74张图片3.各种唯一约束

谷粒商城-订单业务_第75张图片

对订单号设置唯一约束 

右击表点击设计表

谷粒商城-订单业务_第76张图片谷粒商城-订单业务_第77张图片

 谷粒商城-订单业务_第78张图片

Redis set的防重场景:每个数据的MD5加密后的值唯一,网盘就可以根据上传的数据进行MD5加密,将加密后的数据存储至Redis的set里,下次你上传同样的东西时先会去set进行判断是否存在,存在就不处理。 

 防重表的应用场景:当我们去解库存的时候,先去防重表里插入一条数据,当请求再次过来的时候,先去防重表里插入数据,只有当插入成功才能进行下一步操作。

谷粒商城-订单业务_第79张图片

Nginx为每一个请求设置唯一id可以用作链路追踪,看这个请求请求了那些服务 

商城业务-订单服务-订单确认页完成

1.订单服务的执行流程如下图所示

谷粒商城-订单业务_第80张图片

2. 防重令牌的编写

①注入StringRedisTemplate

② 编写订单服务常量即防重令牌前缀,格式:order:token:userId

③ 防重令牌存储

谷粒商城-订单业务_第81张图片

3. 提交页面数据Vo的编写

选中一个商品,点击去结算

谷粒商城-订单业务_第82张图片

回到购物车,再选中另一个商品 

谷粒商城-订单业务_第83张图片

回到结算页,点击结算 

谷粒商城-订单业务_第84张图片

说明:京东的结算页中的商品信息是实时获取的,结算的时候会去购物车中再去获取一遍,因此,提交页面的数据Vo没必要提交商品信息  

谷粒商城-订单业务_第85张图片

谷粒商城-订单业务_第86张图片4. 前端页面提交表单编写

5.  为input框绑定数据

谷粒商城-订单业务_第87张图片

6.编写提交订单数据接口

谷粒商城-订单业务_第88张图片

商城业务-订单服务-原子验令牌

1.提交订单返回结果Vo编写 

谷粒商城-订单业务_第89张图片

2. 接口编写

谷粒商城-订单业务_第90张图片

验证令牌的核心:保证令牌的比较和删除的原子性 

解决方案:使用脚本

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end  

脚本执行的返回结果: 

0:代表令牌校验失败

1:代表令牌成功删除即成功

execute(arg1,arg2,arg3)参数解释:

arg1:用DefaultRedisScript的构造器封装脚本和返回值类型

arg2:数组,用于存放Redis中token的key

arg3:用于比较的token即浏览器存储的token

T:返回值的类型 

谷粒商城-订单业务_第91张图片

谷粒商城-订单业务_第92张图片

商城业务-订单服务-构造订单数据

1.订单创建To的编写

谷粒商城-订单业务_第93张图片

2. 创建订单方法编写

①订单状态枚举类的编写

谷粒商城-订单业务_第94张图片② IDWorker中的getTimeId()生成时间id,不重复,用于充当订单号

谷粒商城-订单业务_第95张图片③地址和运费信息Vo的编写

谷粒商城-订单业务_第96张图片

④ 远程服务调用获取地址和运费信息

谷粒商城-订单业务_第97张图片

⑤ 使用ThreadLocal,实现同一线程共享数据

⑥ 将构造订单数据的代码抽取成方法

谷粒商城-订单业务_第98张图片

谷粒商城-订单业务_第99张图片

商城业务-订单服务-构造订单项数据

1.远程服务调用,通过skuId获取Spu信息

谷粒商城-订单业务_第100张图片

谷粒商城-订单业务_第101张图片

谷粒商城-订单业务_第102张图片2. 设置订单购物项数据

谷粒商城-订单业务_第103张图片

谷粒商城-订单业务_第104张图片

商城业务-订单服务-订单验价

1.计算单个购物项的真实价格

谷粒商城-订单业务_第105张图片

2.设置订单的价格

谷粒商城-订单业务_第106张图片

谷粒商城-订单业务_第107张图片

谷粒商城-订单业务_第108张图片

3.订单其它信息设置

谷粒商城-订单业务_第109张图片

4. 验价

谷粒商城-订单业务_第110张图片

商城业务-订单服务-保存订单数据

1.保存订单和订单项数据

①保存订单和订单项以及锁库存操作处于事务当中,出现异常需要回滚

谷粒商城-订单业务_第111张图片

②注入orderItemService

③保存

谷粒商城-订单业务_第112张图片谷粒商城-订单业务_第113张图片

商城业务-订单服务-锁定库存

锁库存逻辑

谷粒商城-订单业务_第114张图片

远程服务调用锁定库存

1.锁库存Vo编写

谷粒商城-订单业务_第115张图片

将订单服务的OrderLockVo和OrderItemVo复制到库存服务中

谷粒商城-订单业务_第116张图片

2. 锁库存响应Vo编写

谷粒商城-订单业务_第117张图片

3. 锁库存异常类的编写

谷粒商城-订单业务_第118张图片

4. 库存不足异常状态码编写

谷粒商城-订单业务_第119张图片

5.为库存表的锁库存字段设置默认值:0

谷粒商城-订单业务_第120张图片

6. 查询库存接口编写

谷粒商城-订单业务_第121张图片

指定抛出此异常时一定要回滚,不指定也会回滚默认运行时异常都会回滚 

谷粒商城-订单业务_第122张图片

内部类保存商品在那些仓库有库存以及锁库存数量 

谷粒商城-订单业务_第123张图片

锁库存实现 

谷粒商城-订单业务_第124张图片

7. 远程服务调用

谷粒商城-订单业务_第125张图片

谷粒商城-订单业务_第126张图片

谷粒商城-订单业务_第127张图片

商城业务-订单服务-提交订单的问题

1.订单号显示

2.应付金额回显 

出现问题:orderSn长度过长

解决方案:数据库的表中的对应字段长度增大

3.提交订单消息回显

谷粒商城-订单业务_第128张图片

4.  为了确保锁库存失败后,订单和订单项也能回滚,需要抛出异常

谷粒商城-订单业务_第129张图片

出现问题:库存不足消息回显失败,一开始我以为是addFlashAttribute()方法失效了,因为,浏览器中的session里面并没有值

谷粒商城-订单业务_第130张图片

后面我才想起里,并不是这样的,我用了Spring- Session,所以获取到的session都是redis中存储的session,redis中的session是有addFlashAttribute添加属性的key的,因此,我查看log以及结合之前的代码发现很有可能存储的是set,直接存是存是存储不了的,答案确实是set。

谷粒商城-订单业务_第131张图片

解决方案: 以map的形式存储数据

谷粒商城-订单业务_第132张图片

你可能感兴趣的:(尚硅谷谷粒商城,谷粒商城订单服务)