Finder ssl jackson mockito requirejs NEJ vue最新版本 vue绑定事件 vue响应式布局 河南网络推广 郑州普通话考试 oracle连接字符串 matlab网页版 完全去vm去虚拟化工具 java微服务架构 python使用教程 python配置 python写脚本 python排序 java8特性 java结束线程 linux系统命令大全 运行时错误1004 java分布式开发 零基础学python h370主板 英雄联盟崩溃 深渊碎片 python爬虫代码 ip切换软件 ps制作表格 pr视频加速 英雄联盟设置 hyqihei wegame更新失败 子节点 突袭时间表 批处理for 日志软件 趣学python编程
当前位置: 首页 > 学习教程  > 编程语言

谷粒商城-个人笔记(高级篇四)

2020/12/5 9:57:02 文章标签:

本文重点记录老师讲的话 和 一些配置流程,笔记中有的内容尽量少记录。 官方笔记-基础篇:https://blog.csdn.net/hancoder/article/details/107612619 官方笔记-高级篇:https://blog.csdn.net/hancoder/article/details/107612746 官方笔记-…

本文重点记录老师讲的话 和 一些配置流程,笔记中有的内容尽量少记录。

  • 官方笔记-基础篇:https://blog.csdn.net/hancoder/article/details/107612619

  • 官方笔记-高级篇:https://blog.csdn.net/hancoder/article/details/107612746

  • 官方笔记-集群篇:https://blog.csdn.net/hancoder/article/details/107612802

  • 官方笔记下载地址:https://download.csdn.net/download/hancoder/1273468

  • 接口文档:https://easydoc.xyz/doc/75716633/ZUqEdvA4/LnjzZHPj

目录

九、消息队列

1、MQ简介

2、RabbitMQ概念

3、docker安装RabbitMQ

4、SpringBoot整合RabbitMQ

5、AmqpAdmin使用

6、RabbitTemplate使用

7、RabbitListener&RabbitHandler接收消息

8、可靠投递-发送端确认

9、可靠投递-消费端确认

十、订单服务

1、页面环境搭建

2、整合SpringSession

3、订单登录拦截

1)、订单流程

4、订单确认页数据获取

5、Feign远程调用丢失请求头问题

6、Feign异步调用丢失请求头问题

7、页面调整

8、订单确认页库存查询

9、订单确认页模拟运费效果

10、接口幂等性讨论

11、添加防重令牌

12、订单提交

 13、分布式事务

14、分布式事务常见解决方案

15、Seata

1)、概念

2)、Seata术语

3)、SEATA 的分布式交易解决方案

4)、seata控制分布式事务需要

5)、每个微服务数据库加上undo_log(回滚日志表)

因为linux出现问题,时间紧迫没处理,现在各个数据服务器连接不起来 ,只是跟着老师敲,没法展示

6)、环境搭建

16、RabbitMQ延时队列(实现定时任务)

17、库存自动解锁

1)、库存锁定

2)、监听队列

3)、库存解锁

18、定时关单

1)、提交订单

2)、监听队列

 3)、关闭订单

4)、解锁库存

19、消息丢失、积压、重复等解决方案


 

九、消息队列

1、MQ简介

  • 异步任务
  • 应用解耦
  • 流量控制

概述:

 

2、RabbitMQ概念

3、docker安装RabbitMQ

不下载镜像,直接安装。默认会帮你下载

docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management

修改只要启动docker自动重启rabbitMQ

docker update rabbitmq --restart=always

账号:guest

密码:guest

创建一个交换机

创建队列

交换机绑定队列

删除交换机,先双击点击要删除的交换机,接着

4、SpringBoot整合RabbitMQ

RabbitMQ的使用

1、引入amqp;RabbitAutoConfiguration就会自动生效

2、给容器中自动配置了RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate

             所有的属性都是

            @ConfigurationProperties(prefix = "spring.rabbitmq")

            public class RabbitProperties

3、给配置文件中配置 spring.rabbitmq 信息

4、@EnableRabbit 开启功能

1)、导入amqp依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2)、添加配置(@ConfigurationProperties(prefix = "spring.rabbitmq"))注意配置前缀一定是spring.rabbitmq

spring.rabbitmq.host=172.20.10.3
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/

3)、主启动类添加@EnableRabbit注解()

@EnableRabbit
@SpringBootApplication
public class GulimallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallOrderApplication.class, args);
    }

}

5、AmqpAdmin使用

@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {

    @Autowired
    AmqpAdmin amqpAdmin;

    /**
     * 1、如何创建Exchange[hello-java-exchange]、Queue、Binding
     *      1)、使用AmqpAdmin进行创建
     * 2、如何收发消息
     */
    @Test
    void contextLoads() {
        DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
        amqpAdmin.declareExchange(directExchange);
        log.info("Exchange[{}]创建成功","hello-java-exchange");
    }

    @Test
    public void createQueue(){
        Queue queue = new Queue("hello-java-queue",true,false,false);
        amqpAdmin.declareQueue(queue);
        log.info("Queue[{}]创建成功","hello-java-queue");
    }

    @Test
    public void createBinding(){
        Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE,"hello-java-exchange","hello.java",null);
        amqpAdmin.declareBinding(binding);
        log.info("Binding[{}]创建成功","hello-java-binding");
    }
}

6、RabbitTemplate使用

@Slf4j
@SpringBootTest
class GulimallOrderApplicationTests {

    @Autowired
    AmqpAdmin amqpAdmin;

    @Autowired
    RabbitTemplate rabbitTemplate;


    /**
     *  发送消息
     */
    @Test
    public void sendMessageTest(){
        OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
        orderReturnApplyEntity.setId(1L);
        orderReturnApplyEntity.setCreateTime(new Date());
        orderReturnApplyEntity.setReturnName("哈哈哈");
        //1、发送消息,如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable
        String msg = "hello word";

        //2、配置MyRabbitConfig,让发送的对象类型的消息,可以是一个json
        rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnApplyEntity);
        log.info("消息发送完成{}",orderReturnApplyEntity);
    }
}    

注意:

配置MyRabbitConfig,让发送的对象类型的消息,可以是一个json

添加“com.atguigu.gulimall.order.config.MyRabbitConfig”类,代码如下:

@Configuration
public class MyRabbitConfig {
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

7、RabbitListener&RabbitHandler接收消息

监听消息:使用@RabbitListener; 主启动类必须有@EnableRabbit

        @RabbitListener: 类+方法上(监听哪些队列即可)

        @RabbitHandler: 标在方法上(重载区分不同的消息)

/**
 * queues:声明需要监听的所有队列
 *
 * org.springframework.amqp.core.Message
 * @param message
 *
 * 参数可以写以下内容
 * 1、Message message:原生消息详细信息。头+体
 * 2、T<发送的消息类型> OrderReturnApplyEntity content
 * 3、Channel channel 当前传输数据的通道
 *
 * Queue:可以很多人都来监听。只要收到消息,队列删除消息,而且只能有一个收到此消息
 * 场景:
 *       1)、订单服务启动多个;同一个消息,只能有一个客户端收到
 *       2)、只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
 *
 */

添加“com.atguigu.gulimall.order.controller.RabbitController”类,代码如下:

@RestController
public class RabbitController {

    @Autowired
    RabbitTemplate rabbitTemplate;
    @GetMapping("/sendMq")
    public String sendMq(@RequestParam(value = "num",defaultValue = "10") Integer num){
        for (int i = 0; i < num; i++){
            if (i%2==0){
                OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
                orderReturnApplyEntity.setId(1L);
                orderReturnApplyEntity.setCreateTime(new Date());
                orderReturnApplyEntity.setReturnName("哈哈哈");
                //配置MyRabbitConfig,让发送的对象类型的消息,可以是一个json
                rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnApplyEntity, new CorrelationData(UUID.randomUUID().toString()));
            }else {
                OrderEntity entity = new OrderEntity();
                entity.setOrderSn(UUID.randomUUID().toString());
                rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",entity, new CorrelationData(UUID.randomUUID().toString()));
            }
        }
        return "OK";
    }

}

 修改“com.atguigu.gulimall.order.service.impl.OrderItemServiceImpl”类,代码如下:

package com.atguigu.gulimall.order.service.impl;

import com.atguigu.gulimall.order.entity.OrderEntity;
import com.atguigu.gulimall.order.entity.OrderReturnApplyEntity;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.order.dao.OrderItemDao;
import com.atguigu.gulimall.order.entity.OrderItemEntity;
import com.atguigu.gulimall.order.service.OrderItemService;

import javax.swing.*;

@RabbitListener(queues = {"hello-java-queue"})
@Service("orderItemService")
public class OrderItemServiceImpl extends ServiceImpl<OrderItemDao, OrderItemEntity> implements OrderItemService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderItemEntity> page = this.page(
                new Query<OrderItemEntity>().getPage(params),
                new QueryWrapper<OrderItemEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * queues:声明需要监听的所有队列
     *
     * org.springframework.amqp.core.Message
     * @param message
     *
     * 参数可以写以下内容
     * 1、Message message:原生消息详细信息。头+体
     * 2、T<发送的消息类型> OrderReturnApplyEntity content
     * 3、Channel channel 当前传输数据的通道
     *
     * Queue:可以很多人都来监听。只要收到消息,队列删除消息,而且只能有一个收到此消息
     * 场景:
     *       1)、订单服务启动多个;同一个消息,只能有一个客户端收到
     *       2)、只有一个消息完全处理完,方法运行结束,我们就可以接收到下一个消息
     *
     */
    //@RabbitListener(queues = {"hello-java-queue"})
    @RabbitHandler
    public void receiverMessage(Message message,OrderReturnApplyEntity content,
                                Channel channel) throws InterruptedException {
        //消息体
        byte[] body = message.getBody();
        //消息头属性信息
        MessageProperties properties = message.getMessageProperties();
        System.out.println("接收到消息...内容:" + content);
//        Thread.sleep(3000);
        System.out.println("消息处理完成=》"+content.getReturnName());
    }

    @RabbitHandler
    public void receiverMessage(OrderEntity orderEntity){
        System.out.println("接收到消息...内容:" + orderEntity);

    }
}

8、可靠投递-发送端确认

 定制RabbitTemplate

服务器收到消息就回调

1、开启发送端确认

        1、spring.rabbitmq.publisher-confirms=true

        2、设置确认回调

2、消息抵达队列就回调

        1、#开启发送端抵达队列确认

         spring.rabbitmq.publisher-returns=true

        #只要抵达队列,以异步发送优先回调我们这个returnConfirm

         spring.rabbitmq.template.mandatory=true

       2、设置确认回调ReturnCallback

3、消费端确认(保证每个消息被正确消费,此时才可以保证broker删除这个消息)

修改application.properties

#开启发送端确认
spring.rabbitmq.publisher-confirms=true
#开启发送端抵达队列确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调我们这个returnConfirm
spring.rabbitmq.template.mandatory=true

添加“com.atguigu.gulimall.order.config.MyRabbitConfig”类,代码如下:

package com.atguigu.gulimall.order.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

/**
 * @Description: MyRabbitConfig
 * @Author: WangTianShun
 * @Date: 2020/11/23 21:58
 * @Version 1.0
 */
@Configuration
public class MyRabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 定制RabbitTemplate
     * 服务器收到消息就回调
     * 1、开启发送端确认
     *      1、spring.rabbitmq.publisher-confirms=true
     *      2、设置确认回调
     * 2、消息抵达队列就回调
     *      1、#开启发送端抵达队列确认
     *         spring.rabbitmq.publisher-returns=true
     *         #只要抵达队列,以异步发送优先回调我们这个returnConfirm
     *         spring.rabbitmq.template.mandatory=true
     *      2、设置确认回调ReturnCallback
     *  3、消费端确认(保证每个消息被正确消费,此时才可以保证broker删除这个消息)
     */
    @PostConstruct   //MyRabbitConfig对象创建完以后,执行这个方法
    public void initRabbitTemplate(){
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 只要消息抵达Broker就b = true
             * @param correlationData 当前消息的唯一关联数据(这个消息的唯一id)
             * @param b  消息是否成功收到
             * @param s 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("confirm...correlationData["+correlationData+"]==>b["+b+"]s==>["+s+"]");
            }
        });

        //设置消息抵达队列的确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定的队列,就触发这个失败回调
             * @param message 投递失败的消息详细信息
             * @param i 回复的状态码
             * @param s 回复的文本内容
             * @param s1 当时这个消息发给哪个交换机
             * @param s2 当时这个消息用哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
                System.out.println("Fail Message["+message+"]==>i["+i+"]==>s["+s+"]==>s1["+s1+"]==>s2["+s2+"]");
            }
        });
    }
}

9、可靠投递-消费端确认

消费端确认(保证每个消息被正确消费,此时才可以保证broker删除这个消息)

1、默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息

问题:

    我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了。发生消息丢失

    手动确认模式。只要我们没有明确告诉MQ,货物被签收,没有ACK,消息就一直unacked状态,

    即使Consumer宕机。消息不会丢失,会重新变为Ready,下一次有新的Consumer连接进来就发给他

2、

      1)、#手动确认收货(ack)

      spring.rabbitmq.listener.simple.acknowledge-mode=manual

     2)、channel.basicAck(deliveryTag,false);签收;业务成功完成就应该签收

     channel.basicNack(deliveryTag,false,true);拒签:业务失败,拒签

添加application.properties

#手动确认收货(ack)
spring.rabbitmq.listener.simple.acknowledge-mode=manual
    @RabbitHandler
    public void receiverMessage(Message message,OrderReturnApplyEntity content,
                                Channel channel) throws InterruptedException {
        //消息体
        byte[] body = message.getBody();
        //消息头属性信息
        MessageProperties properties = message.getMessageProperties();
        System.out.println("接收到消息...内容:" + content);
//        Thread.sleep(3000);
        System.out.println("消息处理完成=》"+content.getReturnName());
        //channel内按顺序自增的
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag:"+deliveryTag);
        //签收货物,非批量模式
        try{
            if (deliveryTag % 2 == 0){
                //收货
                channel.basicAck(deliveryTag,false);
                System.out.println("签收了货物。。。"+deliveryTag);
            }else {
                //退货requeue=false 丢弃  requeue=true发挥服务器,服务器重新入队。
                channel.basicNack(deliveryTag,false,true);
                System.out.println("没有签收货物..."+deliveryTag);
            }

        }catch (Exception e){
            //网络中断
        }

    }

十、订单服务


1、页面环境搭建

1)、把静态资源放到虚拟机的nginx里,在/mydata/nginx/html/static/目录先创建order文件夹,在创建detail文件夹,并把静态资源上传到这个文件夹

把index.html放到gulimall-order服务,改名为detail.html

2)、在/mydata/nginx/html/static/order目录下创建list文件夹,并把静态资源上传到这个文件夹

把index.html放到gulimall-order服务,改名为list.html

3)、在/mydata/nginx/html/static/order目录下创建confirm文件夹,并把静态资源上传到这个文件夹

把index.html放到gulimall-order服务,改名为confirm.html

4)、在/mydata/nginx/html/static/order目录下创建pay文件夹,并把静态资源上传到这个文件夹

把index.html放到gulimall-order服务,改名为pay.html

5)、在C:\Windows\System32\drivers\etc\hosts文件里添加域名(把属性只读模式去掉,用记事本打开)

6)、在gulimal-gateway添加路由

        - id: gulimall_order_route
          uri: lb://gulimall-order
          predicates:
            - Host=order.gulimall.com

7)、修改每个html的资源访问路径

confirm.html

加上thymeleaf模板空间

<!DOCTYPE html>
<html  lang="en" xmlns:th="http://www.thymeleaf.org">

接下来几个html以此类推进行修改

8)、引入thymeleaf模板引擎

        <!--模板引擎 thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

关闭thymeleaf缓存

spring:
  thymeleaf:
    cache: false

测试

创建“com.atguigu.gulimall.order.web.HelloController”类,代码如下

@RestController
public class HelloController {

    @GetMapping("{page}.html")
    public String listPage(@PathVariable("page") String page){

        return page;
    }
}

启动gulimall-order和guliall-gateway

问题:访问http://order.gulimall.com/confirm.html访问失败,报503

原因:gulimall-order服务没有加入到注册中心

解决:1)、pom文件已经导入gulimall-common依赖,说明gulimall-order服务包含注册中心nacos的依赖

          2)、在主启动类添加@EnableDiscoveryClient注解

          3)、配置应用名和注册中心地址(如果不配置应用名,注册到注册中心不会成功)

spring:
  application:
    name: gulimall-order
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

confirm.html页面报错,搜素/*把它去掉即可

http://order.gulimall.com/confirm.html

 

2、整合SpringSession

添加依赖

        <!--属性配置的提示工具-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--整合SpringSession完成session共享问题-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <!--引入redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

修改application.properties

#SpringSession的存储类型
spring.session.store-type=redis
#线程池属性的配置
gulimall.thread.core= 20
gulimall.thread.max-size= 200
gulimall.thread.keep-alive-time= 10
#reidis地址
spring.redis.host=172.20.10.3

添加SpringSession的配置,添加“com.atguigu.gulimall.order.config.GulimallSessionConfig”类,代码如下

@Configuration
public class GulimallSessionConfig {

    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULISESSION");
        return cookieSerializer;
    }

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
        return new GenericJackson2JsonRedisSerializer();
    }
}

添加线程池的配置,添加“com.atguigu.gulimall.order.config.MyThreadConfig”类,代码如下

@Configuration
public class MyThreadConfig {
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){
        return new ThreadPoolExecutor(pool.getCore(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }
}

线程池配置需要的属性

添加“com.atguigu.gulimall.order.config.ThreadPoolConfigProperties”类,代码如下

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

主启动类是上添加SpingSession自动启动的注解

页面调整

修改商城首页我的订单地链接地址

获取用户信息

3、订单登录拦截

1)、订单流程

订单生成 -> 支付订单 -> 卖家发货 -> 确认收货 -> 交易成功

修改“去结算”的链接地址

添加“com.atguigu.gulimall.order.web.OrderWebController”类,代码如下:

@Controller
public class OrderWebController {

    @GetMapping("/toTrade")
    public String toTrade(){
        return "confirm";
    }
}

添加登录拦截器类“com.atguigu.gulimall.order.interceptor.LoginUserInterceptor”,代码如下:

@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    public static ThreadLocal<MemberResponseVO> loginUser = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MemberResponseVO attribute = (MemberResponseVO) 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;
        }
    }
}

添加拦截器的配置“com.atguigu.gulimall.order.config.OrderWebConfiguration”类,代码如下:

@Configuration
public class OrderWebConfiguration implements WebMvcConfigurer {
    @Autowired
    LoginUserInterceptor loginUserInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
    }
}

修改gulimall-auth-server的login.html页面

4、订单确认页数据获取

修改“com.atguigu.gulimall.order.web.OrderWebController”类,代码如下:

@Controller
public class OrderWebController {

    @Autowired
    OrderService orderService;


    @GetMapping("/toTrade")
    public String toTrade(Model model){
        OrderConfirmVo confirmVo = orderService.confirmOrder();
        //展示订单确认的数据
        model.addAttribute("orderConfirmData",confirmVo);
        return "confirm";
    }
    
}

添加“com.atguigu.gulimall.order.vo.OrderConfirmVo”类,代码如下:

//订单确认页需要用的数据
public class OrderConfirmVo {

    //收获地址,ums_member_receive_address表
    List<MemberAddressVo> address;

    //所有选中的购物项
    List<OrderItemVo> items;

    //发票。。。

    //优惠券信息。。。
    //积分
    Integer integration;

    //订单总额
    BigDecimal total;

    //应付价格
    BigDecimal payPrice;

    //防重令牌
    String orderToken;

    //总件数
    public Integer getCount(){
        Integer i = 0;
        if (items != null){
            for (OrderItemVo item : items) {
                i += item.getCount();
            }
        }
        return i;
    }

    public List<MemberAddressVo> getAddress() {
        return address;
    }

    public void setAddress(List<MemberAddressVo> address) {
        this.address = address;
    }

    public List<OrderItemVo> getItems() {
        return items;
    }

    public void setItems(List<OrderItemVo> items) {
        this.items = items;
    }

    public Integer getIntegration() {
        return integration;
    }

    public void setIntegration(Integer integration) {
        this.integration = integration;
    }

    public BigDecimal getTotal() {
        BigDecimal total = new BigDecimal("0");
        if (items != null){
            for (OrderItemVo item : items) {
                BigDecimal multiply = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
                total = total.add(multiply);
            }
        }

        return total;
    }

    public BigDecimal getPayPrice() {
        return getTotal();
    }

    public String getOrderToken() {
        return orderToken;
    }

    public void setOrderToken(String orderToken) {
        this.orderToken = orderToken;
    }
}

修改“com.atguigu.gulimall.order.service.OrderService”类,代码如下:

    /**
     * 订单确认页返回需要用到的数据
     * @return
     */
    OrderConfirmVo confirmOrder();

修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下:

    @Override
    public OrderConfirmVo confirmOrder() {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        MemberResponseVO memberResponseVO = LoginUserInterceptor.loginUser.get();

        //1、远程查询所有的收货地址列表
        List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVO.getId());
        confirmVo.setAddress(address);

        //2、远程查询购物车所有选中的购物项
        List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
        confirmVo.setItems(items);

        //3、查询用户积分
        Integer integration = memberResponseVO.getIntegration();
        confirmVo.setIntegration(integration);

        //4、其他数据自动计算
        return confirmVo;
    }

添加“com.atguigu.gulimall.order.vo.MemberAddressVo”类,代码如下

@Data
public class MemberAddressVo {
    private Long id;
    /**
     * member_id
     */
    private Long memberId;
    /**
     * 收货人姓名
     */
    private String name;
    /**
     * 电话
     */
    private String phone;
    /**
     * 邮政编码
     */
    private String postCode;
    /**
     * 省份/直辖市
     */
    private String province;
    /**
     * 城市
     */
    private String city;
    /**
     * 区
     */
    private String region;
    /**
     * 详细地址(街道)
     */
    private String detailAddress;
    /**
     * 省市区代码
     */
    private String areacode;
    /**
     * 是否默认
     */
    private Integer defaultStatus;
  
}

添加“com.atguigu.gulimall.order.vo.OrderItemVo”类,代码如下

@Data
public class OrderItemVo {
    private Long skuId;

    //标题
    private String title;

    //图片
    private String image;

    //商品套餐属性
    private List<String> skuAttr;

    //价格
    private BigDecimal price;

    //数量
    private Integer count;

    //总价
    private BigDecimal totalPrice;

    //TODO 查询库存状态
    //是否有货
    private boolean hasStock;
    
    //重量
    private BigDecimal weight;

}

远程调用要开启fegin客户端

//1、远程查询所有的收货地址列表

添加“com.atguigu.gulimall.order.feign.MemberFeignService”类,代码如下:

@FeignClient("gulimall-member")
public interface MemberFeignService {
    @GetMapping("/member/memberreceiveaddress/{memberId}/addresses")
    List<MemberAddressVo> getAddress(@PathVariable("memberId") Long memberId);
}

gulimall-member

修改“com.atguigu.gulimall.member.controller.MemberReceiveAddressController”类,代码如下

    @GetMapping("/{memberId}/addresses")
    public List<MemberReceiveAddressEntity> getAddress(@PathVariable("memberId") Long memberId){
        return memberReceiveAddressService.getAddress(memberId);
    }

修改“com.atguigu.gulimall.member.service.MemberReceiveAddressService”类,代码如下:

List<MemberReceiveAddressEntity> getAddress(Long memberId);

修改“com.atguigu.gulimall.member.service.impl.MemberReceiveAddressServiceImpl”类,代码如下:

    @Override
    public List<MemberReceiveAddressEntity> getAddress(Long memberId) {
        List<MemberReceiveAddressEntity> memberAddress = this.list(new QueryWrapper<MemberReceiveAddressEntity>().eq("member_id", memberId));
        return memberAddress;
    }

//2、远程查询购物车所有选中的购物项

添加“com.atguigu.gulimall.order.feign.CartFeignService”类,代码如下:

@FeignClient("gulimall-cart")
public interface CartFeignService {
    @GetMapping("/currentUserCartItems")
    List<OrderItemVo> getCurrentUserCartItems();
}

gulimall-cart

修改“com.atguigu.gulimall.cart.controller.CartController”类,代码如下

    @GetMapping("/currentUserCartItems")
    @ResponseBody
    public List<CartItem> getCurrentUserCartItems(){
        return cartService.getUserCartItems();
    }

修改“com.atguigu.gulimall.cart.service.CartService”类,代码如下:

    /**
     * 获取用户购物车里购物项的所有数据
     * @return
     */
    List<CartItem> getUserCartItems();

修改“com.atguigu.gulimall.cart.service.impl.CartServiceImpl”类,代码如下

    @Override
    public List<CartItem> getUserCartItems() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        if (userInfoTo.getUserId() == null){
            return null;
        }else {
            String cartKey = CART_PREFIX + userInfoTo.getUserId();
            List<CartItem> cartItems = getCartItems(cartKey);
            //获取所有被选中的购物项
            List<CartItem> collect = cartItems.stream().filter(item -> item.getCheck())
                    .map(item->{
                        R price = productFeignService.getPrice(item.getSkuId());
                        //更新为最新价格
                        String data = (String) price.get("data");
                        item.setPrice(new BigDecimal(data));
                        return item;})
                    .collect(Collectors.toList());
            return collect;
        }

    }

添加“com.atguigu.gulimall.cart.feign.ProductFeignService”类,代码如下:

    @GetMapping("/product/skuinfo/{skuId}/price")
    R getPrice(@PathVariable("skuId") Long skuId);

gulimall-product

修改“com.atguigu.gulimall.product.app.SkuInfoController”类,代码如下:

    @GetMapping("/{skuId}/price")
    public R getPrice(@PathVariable("skuId") Long skuId){
        SkuInfoEntity byId = skuInfoService.getById(skuId);
        return R.ok().setData(byId.getPrice().toString());
    }

5、Feign远程调用丢失请求头问题

  • feign远程调用的请求头中没有含有JSESSIONIDcookie,所以也就不能得到服务端的session数据,cart认为没登录,获取不了用户信息
  • 但是在feign的调用过程中,会使用容器中的RequestInterceptorRequestTemplate进行处理,因此我们可以通过向容器中导入定制的RequestInterceptor为请求加上cookie
  • RequestContextHolder为SpingMVC中共享request数据的上下文,底层由ThreadLocal实现。经过RequestInterceptor处理后的请求如下,已经加上了请求头的Cookie信息

添加“com.atguigu.gulimall.order.config.GuliFeignConfig”类,代码如下:

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;

/**
 * @Description: 请求拦截器
 * @Author: WangTianShun
 * @Date: 2020/11/26 9:10
 * @Version 1.0
 */
@Configuration
public class GuliFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //1、RequestContextHolder拿到刚进来的请求
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = attributes.getRequest();//老请求
                //同步请求头数据。Cookie
                String cookie = request.getHeader("Cookie");
                //给新请求同步了老请求的cookie
                requestTemplate.header("Cookie",cookie);
                System.out.println("feign远程之前先执行RequestInterceptor.apply()");
            }
        };
    }
}

6、Feign异步调用丢失请求头问题

  • 查询购物项、库存和收货地址都要调用远程服务,串行会浪费大量时间,因此我们使用CompletableFuture进行异步编排
  • 由于RequestContextHolder使用ThreadLocal共享数据,所以在开启异步时获取不到老请求的信息,自然也就无法共享cookie了。在这种情况下,我们需要在开启异步的时候将老请求的RequestContextHolder的数据设置进去

修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        MemberResponseVO memberResponseVO = LoginUserInterceptor.loginUser.get();
        System.out.println("主线程..."+Thread.currentThread().getId());
        //获取之前的请求
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //异步任务编排
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //1、远程查询所有的收货地址列表
            System.out.println("member线程..."+Thread.currentThread().getId());
            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVO.getId());
            confirmVo.setAddress(address);
        }, executor);

        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            //2、远程查询购物车所有选中的购物项
            System.out.println("cart线程..."+Thread.currentThread().getId());
            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
            confirmVo.setItems(items);
            //feign在远程调用之前要构造请求,调用很多拦截器RequestInterceptor interceptor: requestInterceptors
        }, executor);


        //3、查询用户积分
        Integer integration = memberResponseVO.getIntegration();
        confirmVo.setIntegration(integration);

        //4、其他数据自动计算

        //5、TODO 防重令牌想·
        CompletableFuture.allOf(getAddressFuture,cartFuture).get();
        return confirmVo;
    }

7、页面调整

		<!--主体部分-->
		<p class="p1">填写并核对订单信息</p>
		<div class="section">
			<!--收货人信息-->
			<div class="top-2">
				<span>收货人信息</span>
				<span>新增收货地址</span>
			</div>

			<!--地址-->
			<div class="top-3" th:each="addr:${orderConfirmData.address}">
				<p>[[${addr.name}]]</p><span>[[${addr.name}]] [[${addr.province}]]  [[${addr.city}]] [[${addr.detailAddress}]] [[${addr.phone}]]</span>
			</div>
			<p class="p2">更多地址︾</p>
			<div class="hh1"/></div>
		<!--********************************************************************************************-->
		<!--谷粒学院自提-->
		<div class="top-4">
			<p>谷粒学院自提</p>
			<p>省运费·无续重·随时取</p>
			<p class="xiang">详情</p>
		</div>

		<!--地址-->


		<!--支付方式-->
		<h4 class="h4">支付方式</h4>

		<div class="top-6">
			<p>货到付款</p>
			<p><span>惠</span>在线支付</p>
		</div>
		<div class="hh1"></div>
		<!--送货清单-->
		<h4 class="h4" style="margin-top: 5px;">送货清单</h4>
		<div class="top_1">
			<div class="to_left">
				<h5><span class="peisong">配送方式</span><span class="dui"><img src="/static/order/confirm/img/i_03.png"/> 对应商品</span></h5>
				<div class="box">
					谷粒学院快递
				</div>
				<p class="biao">
					<span class="til">标 准 达 :</span>
					<span class="con">预计 12月16日[今天] 15:00-19:00 送达</span>
					<a href="/static/order/confirm/#">修改</a>
				</p>
				<div class="updata-1">
					<img src="/static/order/confirm/img/im_06.png" />
					<span>京准达 标准达</span>
					<span style="color: black;"> 配送服务全面升级</span>
				</div>
				<div class="hh1"></div>
				<p class="tui">
					<span class="til">退换无忧:</span>
					<span class="con">
							<input type="checkbox" />
							自签收后7天内退货,15天内换<span style="font-size: 12px;margin-left: 5px"> ¥ 0.50</span><br />
						<span class="nul">货,</span>可享1次上门取件服务 ﹀
						</span>

				<div class="updata-2">
					<img src="/static/order/confirm/img/im_11.png" />
					<span>京准达运费大促(限自营中小件)</span>
				</div>

				</p>
				<p class="kg" style="color:#666666;margin-top: 13px;font-size: 12px">总重量 :<span style="color:#999999;font-size: 12px">0.095kg</span></p>
			</div>
			<div class="to_right">
				<h5>商家:谷粒学院自营</h5>
				<div><button>换购</button><span>已购满20.00元,再加49.90元,可返回购物车领取赠品</span></div>
				<!--图片-->
				<div class="yun1" th:each="item:${orderConfirmData.items}">
					<img style="width: 150px;height: 100px;" th:src="${item.image}" class="yun"/>
					<div class="mi">
						<p>[[${item.title}]] <span style="color: red;"> ¥[[${#numbers.formatDecimal(item.price, 1, 2)}]]</span> <span> x[[${item.count}]]</span> <span>[[${item.hasStock?"有货":"无货"}]]</span></p>
						<p><span>0.095kg</span></p>
						<p class="tui-1"><img src="/static/order/confirm/img/i_07.png" />支持7天无理由退货</p>
					</div>
				</div>

				<div class="hh1"></div>
				<p>退换无忧 <span class="money">¥ 0.00</span></p>
			</div>
		</div>
		<div class="bto">
			<div class="hh2"></div>
			<h4 class="float">发票信息</h4>
			<div class="note float"><img src="/static/order/confirm/img/i_11.png" /> <span>开企业抬头发票须填写纳税人识别号,以免影响报销</span></div>
			<ul style="clear: both;">
				<li>电子普通发票 <img src="/static/order/confirm/img/i_14.png" /></li>
				<li>个人</li>
				<li>商品明细</li>
				<li>
					<a href="/static/order/confirm/">修改</a>
				</li>
			</ul>
			<div class="hh3"></div>
			<h4 class="clear">使用优惠/礼品卡/抵用 ^</h4>
			<ul>
				<li class="red">优惠卡</li>
				<li>礼品卡</li>
				<li>京豆</li>
				<li>余额</li>
				<li>领奖码</li>
			</ul>
			<div class="tuijian clear">
				<input type="checkbox" />
				<span>优惠组合推荐</span>
			</div>
		</div>
		<div class="xia">
			<div class="qian">
				<p class="qian_y">
					<span>[[${orderConfirmData.count}]]</span>
					<span>件商品,总商品金额:</span>
					<span class="rmb">¥[[${#numbers.formatDecimal(orderConfirmData.total, 1, 2)}]]</span>
				</p>
				<p class="qian_y">
					<span>返现:</span>
					<span class="rmb">  -¥0.00</span>
				</p>
				<p class="qian_y">
					<span>运费: </span>
					<span class="rmb"> &nbsp ¥0.00</span>
				</p>
				<p class="qian_y">
					<span>服务费: </span>
					<span class="rmb"> &nbsp ¥0.00</span>
				</p>
				<p class="qian_y">
					<span>退换无忧: </span>
					<span class="rmb"> &nbsp ¥0.00</span>
				</p>

			</div>

			<div class="yfze">
				<p class="yfze_a"><span class="z">应付总额:</span><span class="hq">¥[[${#numbers.formatDecimal(orderConfirmData.payPrice, 1, 2)}]]</span></p>
				<p class="yfze_b">寄送至: 北京 朝阳区 三环到四环之间 朝阳北路复兴国际大厦23层麦田房产 IT-中心研发二部 收货人:赵存权 188****5052</p>
			</div>
			<button class="tijiao">提交订单</button>
		</div>
		</div>

http://order.gulimall.com/toTrade

8、订单确认页库存查询

修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下:

 @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        MemberResponseVO memberResponseVO = LoginUserInterceptor.loginUser.get();
        System.out.println("主线程..."+Thread.currentThread().getId());
        //获取之前的请求
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //异步任务编排
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //1、远程查询所有的收货地址列表
            System.out.println("member线程..."+Thread.currentThread().getId());
            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVO.getId());
            confirmVo.setAddress(address);
        }, executor);

        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            //2、远程查询购物车所有选中的购物项
            System.out.println("cart线程..."+Thread.currentThread().getId());
            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
            confirmVo.setItems(items);
            //feign在远程调用之前要构造请求,调用很多拦截器RequestInterceptor interceptor: requestInterceptors
        }, executor).thenRunAsync(()->{
            //查询库存信息
            List<OrderItemVo> items = confirmVo.getItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());

            R hasStock = wmsFeignService.getSkusHasStock(collect);
            List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
            });
            if (data != null){
                Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(map);
            }

        },executor);

添加“com.atguigu.gulimall.order.vo.SkuStockVo” 类,代码如下:

@Data
public class SkuStockVo {
    private Long skuId;
    private Boolean hasStock;
}

修改“com.atguigu.gulimall.order.vo.OrderConfirmVo”类,添加stocks参数,并添加getter,setter方法

修改“com.atguigu.gulimall.order.vo.OrderItemVo”类,去掉hasStock属性

//远程调用库存,查询是否有库存gulimall-ware

添加“com.atguigu.gulimall.order.feign.WmsFeignService”类,代码如下

@FeignClient("gulimall-ware")
public interface WmsFeignService {

    //查询sku是否有库存
    @PostMapping("/ware/waresku/hasStock")
    public R getSkusHasStock(@RequestBody List<Long> skuIds);
}

修改confirm.html页面

<!--图片-->
				<div class="yun1" th:each="item:${orderConfirmData.items}">
					<img style="width: 150px;height: 100px;" th:src="${item.image}" class="yun"/>
					<div class="mi">
						<p>[[${item.title}]] <span style="color: red;"> ¥[[${#numbers.formatDecimal(item.price, 1, 2)}]]</span> <span> x[[${item.count}]]</span> <span>[[${orderConfirmData.stocks[item.skuId]?"有货":"无货"}]]</span></p>
						<p><span>0.095kg</span></p>
						<p class="tui-1"><img src="/static/order/confirm/img/i_07.png" />支持7天无理由退货</p>
					</div>
				</div>

9、订单确认页模拟运费效果

添加“com.atguigu.gulimall.ware.vo.FareVo”类,代码如下

@Data
public class FareVo {
    //收货人地址信息
    private MemberAddressVo address;
    //费用
    private BigDecimal fare;
}

修改“com.atguigu.gulimall.ware.controller.WareInfoController”类,代码如下:

    @GetMapping("/fare")
    public R getFare(@RequestParam("addrId") Long addrId){
        FareVo fare = wareInfoService.getFare(addrId);
        return R.ok().setData(fare);
    }

修改“com.atguigu.gulimall.ware.service.WareInfoService”类,代码如下

     /**
     * 根据用户的收获地址计算运费
     * @param attrId
     * @return
     */
    FareVo getFare(Long attrId);

修改“com.atguigu.gulimall.ware.service.impl.WareInfoServiceImpl”类,代码如下:

    @Override
    public FareVo getFare(Long attrId) {

        FareVo fareVo = new FareVo();
        R r = memberFeignService.addrInfo(attrId);
        MemberAddressVo data = r.getData("memberReceiveAddress",new TypeReference<MemberAddressVo>() {
        });
        if (data != null){
            //模拟计算运费
            String phone = data.getPhone();
            String substring = phone.substring(phone.length() - 1, phone.length());
            BigDecimal bigDecimal = new BigDecimal(substring);
            fareVo.setAddress(data);
            fareVo.setFare(bigDecimal);

            return fareVo;
        }
        return null;
    }

添加“com.atguigu.gulimall.ware.vo.MemberAddressVo”类,代码如下:

@Data
public class MemberAddressVo {
    private Long id;
    /**
     * member_id
     */
    private Long memberId;
    /**
     * 收货人姓名
     */
    private String name;
    /**
     * 电话
     */
    private String phone;
    /**
     * 邮政编码
     */
    private String postCode;
    /**
     * 省份/直辖市
     */
    private String province;
    /**
     * 城市
     */
    private String city;
    /**
     * 区
     */
    private String region;
    /**
     * 详细地址(街道)
     */
    private String detailAddress;
    /**
     * 省市区代码
     */
    private String areacode;
    /**
     * 是否默认
     */
    private Integer defaultStatus;
}

添加“com.atguigu.gulimall.ware.feign.MemberFeignService”类,代码如下:

@FeignClient("gulimall-member")
public interface MemberFeignService {

    @RequestMapping("/member/memberreceiveaddress/info/{id}")
    R addrInfo(@PathVariable("id") Long id);
}

10、接口幂等性讨论

1)、什么是接口幂等性

2)、哪些情况需要防止

3)、幂等性解决方案

1、token机制

2、各种锁机制

3、各种唯一性约束

4、防重表

5、全球请求唯一id

11、添加防重令牌

修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下:

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        MemberResponseVO memberResponseVO = LoginUserInterceptor.loginUser.get();
        System.out.println("主线程..."+Thread.currentThread().getId());
        //获取之前的请求
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //异步任务编排
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //1、远程查询所有的收货地址列表
            System.out.println("member线程..."+Thread.currentThread().getId());
            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVO.getId());
            confirmVo.setAddress(address);
        }, executor);

        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            //2、远程查询购物车所有选中的购物项
            System.out.println("cart线程..."+Thread.currentThread().getId());
            //每一个线程都来共享之前的请求数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
            confirmVo.setItems(items);
            //feign在远程调用之前要构造请求,调用很多拦截器RequestInterceptor interceptor: requestInterceptors
        }, executor).thenRunAsync(()->{
            //查询库存信息
            List<OrderItemVo> items = confirmVo.getItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());

            R hasStock = wmsFeignService.getSkusHasStock(collect);
            List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
            });
            if (data != null){
                Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(map);
            }

        },executor);


        //3、查询用户积分
        Integer integration = memberResponseVO.getIntegration();
        confirmVo.setIntegration(integration);

        //4、其他数据自动计算

        //5、TODO 防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberResponseVO.getId(),token,30, TimeUnit.MINUTES);
        confirmVo.setOrderToken(token);
        CompletableFuture.allOf(getAddressFuture,cartFuture).get();
        return confirmVo;
    }

12、订单提交

confirm.html

页面提交数据  添加“com.atguigu.gulimall.order.vo.OrderSubmitVo”类,代码如下:

/**
 * @Description: 封装订单提交的数据
 * @Author: WangTianShun
 * @Date: 2020/11/26 22:27
 * @Version 1.0
 */
@Data
public class OrderSubmitVo {
    // 收获地址的id
    private Long addrId;

    // 支付方式
    private Integer payType;

    //无需提交需要购买的商品,去购物车在获取一遍

    // 防重令牌
    private String orderToken;

    // 应付价格 验价
    private BigDecimal payPrice;

    // 订单备注
    private String note;

}
  • 提交订单成功,则携带返回数据转发至支付页面
  • 提交订单失败,则携带错误信息重定向至确认页

添加“com.atguigu.gulimall.order.web.OrderWebController”类,代码如下:

    /**
     * 下单功能
     * @param submitVo
     * @param model
     * @param redirectAttributes
     * @return
     */
    @PostMapping("/submitOrder")
    public String submitOrder(OrderSubmitVo submitVo, Model model, RedirectAttributes redirectAttributes) {
        // 下单 去创建订单 验证令牌 核算价格 锁定库存
        try {
            SubmitOrderResponseVo responseVo = orderService.submitOrder(submitVo);
            if (responseVo.getCode() == 0) {
                // 下单成功到选择支付方式页面
                model.addAttribute("submitOrderResp", responseVo);
                return "pay";
            } else {
                // 订单失败返回到订单确认页面
                String msg = "下订单失败: ";
                switch (responseVo.getCode()) {
                    case 1 : msg += "订单信息过期, 请刷新后再次提交."; break;
                    case 2 : msg += "订单中的商品价格发生变化, 请刷新后再次提交."; break;
                    case 3 : msg += "库存锁定失败, 商品库存不足."; break;
                }
                redirectAttributes.addFlashAttribute("msg", msg);
                return "redirect:http://order.gulimall.com/toTrade";
            }
        } catch (Exception e) {
            if (e instanceof NoStockException) {
                String message = e.getMessage();
                redirectAttributes.addFlashAttribute("msg", message);
            }
            return "redirect:http://order.gulimall.com/toTrade";
        }
    }

添加“com.atguigu.gulimall.order.service.OrderService”类,代码如下:

    /**
     * 下单
     * @param submitVo
     * @return
     */
    SubmitOrderResponseVo submitOrder(OrderSubmitVo submitVo);
添加“com.atguigu.gulimall.order.vo.SubmitOrderResponseVo”类,代码如下:
@Data
public class SubmitOrderResponseVo {

    private OrderEntity order;

    private Integer code;

}

 

1、验证令牌【令牌的对比和删除必须保证原子性】
2、令牌验证成功 下单 去创建订单 验证令牌 核算价格 锁定库存

添加“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下:

@Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo submitVo) {
        confirmVoThreadLocal.set(submitVo);
        SubmitOrderResponseVo response = new SubmitOrderResponseVo();
        MemberResponseVO memberResponseVO = LoginUserInterceptor.loginUser.get();
        response.setCode(0);
        // 1、验证令牌【令牌的对比和删除必须保证原子性】
        // 0令牌失败 -1删除成功
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String orderToken = submitVo.getOrderToken();
        // 原子验证令牌和删除令牌
        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVO.getId()), orderToken);
        if (result == 0L) {
            // 令牌验证失败
            response.setCode(1);
            return response;
        } else {
            // 令牌验证成功 下单 去创建订单 验证令牌 核算价格 锁定库存
            // 1、创建订单,订单项等信息
            OrderCreateTo order = createOrder();
            // 2、验价
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = submitVo.getPayPrice();
            if (Math.abs(payAmount.subtract(payPrice).doubleValue()) <0.01){
                // 金额对比成功
                // 3、保持订单
                saveOrder(order);
                // 4、库存锁定,只要有异常回滚订单数据。订单号,订单项信息(skuId,skuName,num)
                WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
                wareSkuLockVo.setOrderSn(order.getOrder().getOrderSn());
                List<OrderItemVo> orderItemVos = order.getOrderItems().stream().map(item -> {
                    OrderItemVo orderItemVo = new OrderItemVo();
                    orderItemVo.setSkuId(item.getSkuId());
                    orderItemVo.setCount(item.getSkuQuantity());
                    orderItemVo.setTitle(item.getSkuName());
                    return orderItemVo;
                }).collect(Collectors.toList());
                wareSkuLockVo.setLocks(orderItemVos);
                // TODO 远程锁库存
                R r = wmsFeignService.orderLockStock(wareSkuLockVo);
                if (r.getCode() == 0){
                    //锁成功了
                    response.setOrder(order.getOrder());
                    return response;
                }else {
                    //锁定失败
                    throw new NoStockException((String) r.get("msg"));
                }
            }else {
                response.setCode(2);
                return response;
            }
        }
//        String redisToken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVO.getId());
//        if (orderToken != null && orderToken.equals(redisToken)){
//            //令牌验证通过
//            redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVO.getId());
//        }else {
//            //不通过
//        }
    }

订单返回参数     添加“com.atguigu.gulimall.order.to.OrderCreateTo”类,代码如下:

@Data
public class OrderCreateTo {

    // 订单
    private OrderEntity order;

    // 订单项
    private List<OrderItemEntity> orderItems;

    // 订单应付的价格
    private BigDecimal payPrice;

    //运费
    private BigDecimal fare;
}

1、生成一个订单

    /**
     * 生成一个订单
     * @return
     */
    public OrderCreateTo createOrder(){
        OrderCreateTo createTo = new OrderCreateTo();
        // 1、生成一个订单号
        String orderSn = IdWorker.getTimeId();
        // 创建订单
        OrderEntity orderEntity = buildOrder(orderSn);
        createTo.setOrder(orderEntity);
        // 2、获取所有的订单项
        List<OrderItemEntity> itemEntities = buildOrderItems(orderSn);
        createTo.setOrderItems(itemEntities);
        // 3、计算价格、积分等相关
        computePrice(orderEntity,itemEntities);
        return createTo;
    }

 1.1、创建订单

    /**
     * 创建订单
     * @param orderSn
     * @return
     */
    private OrderEntity buildOrder(String orderSn) {
        MemberResponseVO memberResponseVo = LoginUserInterceptor.loginUser.get();
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderSn);
        orderEntity.setMemberId(memberResponseVo.getId());
        OrderSubmitVo orderSubmitVo = confirmVoThreadLocal.get();
        // 获取收获地址信息
        R r = wmsFeignService.getFare(orderSubmitVo.getAddrId());
        FareVo fareResp = r.getData(new TypeReference<FareVo>() {
        });
        // 设置运费信息
        orderEntity.setFreightAmount(fareResp.getFare());
        // 设置收货人信息
        orderEntity.setReceiverCity(fareResp.getAddress().getCity());
        orderEntity.setReceiverDetailAddress(fareResp.getAddress().getDetailAddress());
        orderEntity.setReceiverName(fareResp.getAddress().getName());
        orderEntity.setReceiverPhone(fareResp.getAddress().getPhone());
        orderEntity.setReceiverPostCode(fareResp.getAddress().getPostCode());
        orderEntity.setReceiverRegion(fareResp.getAddress().getRegion());
        // 设置订单的相关状态信息
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        orderEntity.setAutoConfirmDay(7);
        return orderEntity;
    }

//远程获取收获地址信息(gulimall-ware)

修改“com.atguigu.gulimall.order.feign.WmsFeignService”类,代码如下:

    @GetMapping("/ware/wareinfo/fare")
    public R getFare(@RequestParam("addrId") Long addrId);

封装远程返回的数据

添加“com.atguigu.gulimall.order.vo.FareVo”类,代码如下:

@Data
public class FareVo {
    //收货人地址信息
    private MemberAddressVo address;
    //费用
    private BigDecimal fare;
}

 1.2、获取所有的订单项

    /**
     * 构建所有订单项数据
     * @return
     */
    private List<OrderItemEntity> buildOrderItems(String orderSn) {
        // 最后确定每个购物项的价格
        List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
        if (currentUserCartItems != null && currentUserCartItems.size()>0){
            List<OrderItemEntity> itemEntities = currentUserCartItems.stream().map(cartItem -> {
                OrderItemEntity itemEntity = buildOrderItem(cartItem);
                itemEntity.setOrderSn(orderSn);
                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }
        return null;
    }

1.2.1、构建某一个订单项

    /**
     * 构建某一个订单项
     * @param cartItem
     * @return
     */
    private OrderItemEntity buildOrderItem(OrderItemVo cartItem) {
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        // 1 订单信息 订单号
        // 2 SPU信息
        Long skuId = cartItem.getSkuId();
        R r = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo data = r.getData(new TypeReference<SpuInfoVo>(){});
        orderItemEntity.setSpuId(data.getId());
        orderItemEntity.setSpuBrand(data.getBrandId().toString());
        orderItemEntity.setSpuName(data.getSpuName());
        orderItemEntity.setCategoryId(data.getCatalogId());
        // 3 SKU信息
        orderItemEntity.setSkuId(cartItem.getSkuId());
        orderItemEntity.setSkuName(cartItem.getTitle());
        orderItemEntity.setSkuPic(cartItem.getImage());
        orderItemEntity.setSkuPrice(cartItem.getPrice());
        String skuAttrs = StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";"); //将集合转换成字符串
        orderItemEntity.setSkuAttrsVals(skuAttrs);
        orderItemEntity.setSkuQuantity(cartItem.getCount());
        // 4 优惠信息 [不做]

        // 5 积分信息
        orderItemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        orderItemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        // 6 订单项的价格信息
        orderItemEntity.setPromotionAmount(new BigDecimal("0"));
        orderItemEntity.setIntegrationAmount(new BigDecimal("0"));
        orderItemEntity.setCouponAmount(new BigDecimal("0"));
        // 当前订单项的实际金额
        BigDecimal origin = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
        // 总额减去各种优惠后的价格
        BigDecimal subtract = origin.subtract(orderItemEntity.getCouponAmount()).subtract(orderItemEntity.getIntegrationAmount()).subtract(orderItemEntity.getPromotionAmount());
        orderItemEntity.setRealAmount(subtract);
        return orderItemEntity;
    }

//远程获取spu信息

封装远程spu的返回信息   添加“com.atguigu.gulimall.order.vo.SpuInfoVo”类,代码如下

@Data
public class SpuInfoVo {
    /**
     * 商品id
     */
    private Long id;
    /**
     * 商品名称
     */
    private String spuName;
    /**
     * 商品描述
     */
    private String spuDescription;
    /**
     * 所属分类id
     */
    private Long catalogId;
    /**
     * 品牌id
     */
    private Long brandId;
    /**
     *
     */
    private BigDecimal weight;
    /**
     * 上架状态[0 - 下架,1 - 上架]
     */
    private Integer publishStatus;
    /**
     *
     */
    private Date createTime;
    /**
     *
     */
    private Date updateTime;
}

添加“com.atguigu.gulimall.order.feign.ProductFeignService”类,代码如下“:

@FeignClient("gulimall-product")
public interface ProductFeignService {
    @GetMapping("/product/spuinfo/skuId/{id}")
    R getSpuInfoBySkuId(@PathVariable("id") Long skuId);
}

gulimall-product

修改“com.atguigu.gulimall.product.app.SpuInfoController”类,代码如下:

    @GetMapping("skuId/{id}")
    public R getSpuInfoBySkuId(@PathVariable("id") Long skuId){
        SpuInfoEntity entity = spuInfoService.getSpuInfoBySkuId(skuId);
        return R.ok().setData(entity);
    }

添加“com.atguigu.gulimall.product.service.SpuInfoService”类,代码如下:

 SpuInfoEntity getSpuInfoBySkuId(Long skuId);

修改“com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl”类,代码如下:

    @Override
    public SpuInfoEntity getSpuInfoBySkuId(Long skuId) {
        SkuInfoEntity byId = skuInfoService.getById(skuId);
        Long spuId = byId.getSpuId();
        SpuInfoEntity spuInfoEntity = getById(spuId);

        return spuInfoEntity;
    }

1.3、计算价格、积分等相关

 

    /**
     * 计算价格相关
     * @param orderEntity
     * @param itemEntities
     */
    private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> itemEntities) {
        BigDecimal total = new BigDecimal("0.0");
        BigDecimal coupon = new BigDecimal("0.0");
        BigDecimal integration = new BigDecimal("0.0");
        BigDecimal promotion = new BigDecimal("0.0");
        BigDecimal gift = new BigDecimal("0.0");
        BigDecimal growth = new BigDecimal("0.0");
        // 订单的总额,叠加每一个订单项的总额信息。
        for (OrderItemEntity entity : itemEntities) {
            coupon = coupon.add(entity.getCouponAmount());
            integration = integration.add(entity.getIntegrationAmount());
            promotion = promotion.add(entity.getPromotionAmount());
            total = total.add(entity.getRealAmount());
            gift = gift.add(new BigDecimal(entity.getGiftIntegration().toString()));
            growth = growth.add(new BigDecimal(entity.getGiftGrowth().toString()));
        }
        // 订单价格相关
        orderEntity.setTotalAmount(total);
        // 应付金额
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        orderEntity.setPromotionAmount(promotion);
        orderEntity.setIntegrationAmount(integration);
        orderEntity.setCouponAmount(coupon);
        // 设置积分信息
        orderEntity.setIntegration(gift.intValue());
        orderEntity.setGrowth(growth.intValue());
        // 设置删除状态 0未删除
        orderEntity.setDeleteStatus(0);
    }

 2、保存订单

    /**
     * 保存订单数据
     * @param order
     */
    private void saveOrder(OrderCreateTo order) {
        OrderEntity orderEntity = order.getOrder();
        orderEntity.setModifyTime(new Date());
        this.save(orderEntity);
        List<OrderItemEntity> orderItems = order.getOrderItems();
        orderItemService.saveBatch(orderItems);
    }

3、库存锁定,只要有异常回滚订单数据。订单号,订单项信息(skuId,skuName,num)

  • 找出所有库存大于商品数的仓库
  • 遍历所有满足条件的仓库,逐个尝试锁库存,若锁库存成功则退出遍历

修改“com.atguigu.gulimall.order.feign.WmsFeignService”类,代码如下:

    @PostMapping("/ware/waresku/lock/order")
    public R orderLockStock(@RequestBody WareSkuLockVo vo);

gulimall-ware

修改“com.atguigu.gulimall.ware.controller.WareSkuController”类,代码如下:

    @PostMapping("/lock/order")
    public R orderLockStock(@RequestBody WareSkuLockVo vo){
        try{
            Boolean stock = wareSkuService.orderLockStock(vo);
            return R.ok();
        }catch (NoStockException e){
            return R.error(BizCodeEnume.NO_STOCK_EXCEPTION.getCode(),BizCodeEnume.NO_STOCK_EXCEPTION.getMsg());
        }
    }

 添加“com.atguigu.common.exception.BizCodeEnume”类,代码如下:

修改“com.atguigu.gulimall.ware.service.WareSkuService”类,代码如下:

Boolean orderLockStock(WareSkuLockVo vo);

 添加“com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl”类,代码如下:

    /**
     * 为某个订单锁定库存
     * (rollbackFor = NoStockException.class)
     * 默认只要都是运行异常都会回滚
     * @param vo
     * @return
     */
    @Transactional
    @Override
    public Boolean orderLockStock(WareSkuLockVo vo) {
        // 1、按照下单的收货地址,找到一个就近仓库,锁定库存

        // 1、找到每个商品在哪个仓库都有库存
        List<OrderItemVo> locks = vo.getLocks();
        List<SkuWareHasStock> collect = locks.stream().map(item -> {
            SkuWareHasStock stock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            stock.setSkuId(skuId);
            stock.setNum(item.getCount());
            //查询这个商品在哪个仓库有库存
            List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);
            stock.setWareId(wareIds);
            return stock;
        }).collect(Collectors.toList());
        // 2、锁定库存
        for (SkuWareHasStock hasStock : collect) {
            Boolean skuStocked = false;
            Long skuId = hasStock.getSkuId();
            List<Long> wareIds = hasStock.getWareId();
            if (wareIds == null || wareIds.size() == 0){
                //没有任何库存有这个商品的库存
                throw new NoStockException(skuId);
            }
            for (Long wareId : wareIds) {
                //成功返回1;否则就是0
                Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum());
                if (count == 1){
                    skuStocked = true;
                    break;
                    //当仓库锁失败,重试下一个仓库
                }
            }
            if (skuStocked == false){
                //当前商品所有仓库都没有锁住
                throw new NoStockException(skuId);
            }
        }
        // 3、肯定全部都是锁定成功的
        return true;
    }

    @Data
    class SkuWareHasStock{

        private Long skuId;

        private Integer num;

        private List<Long> wareId;
    }

}

这里通过异常机制控制事务回滚,如果在锁定库存失败则抛出NoStockExceptions,订单服务和库存服务都会回滚。 

修改“com.atguigu.gulimall.ware.dao.WareSkuDao”类,代码如下:

    List<Long> listWareIdHasSkuStock(@Param("skuId") Long skuId);

    Long lockSkuStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("num") Integer num);
    <select id="listWareIdHasSkuStock" resultType="java.lang.Long">
        select ware_id from wms_ware_sku where sku_id =#{skuId} and stock - stock_locked > 0
    </select>

    <update id="lockSkuStock">
        update wms_ware_sku set stock_locked = stock_locked + #{num}
        where sku_id = #{skuId} and ware_id = #{wareId} and stock - stock_locked >= #{num}
    </update>

 13、分布式事务

本地事务

分布式事务

分布式情况下,可能出现一些服务事务不一致的情况

  • 远程服务假失败
  • 远程服务执行完成后,下面其他方法出现异常

分布式CAP&BASE理论

1)、cap定理

14、分布式事务常见解决方案

 

15、Seata

1)、概念

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

2)、Seata术语

  • TC (Transaction Coordinator) - 事务协调者

      维护全局和分支事务的状态,驱动全局事务提交或回滚。

  • TM (Transaction Manager) - 事务管理器

       定义全局事务的范围:开始全局事务、提交或回滚全局事务。

  • RM (Resource Manager) - 资源管理器

      管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

3)、SEATA 的分布式交易解决方案

我们只需要使用一个 @GlobalTransactional 注解在业务方法上:


    @GlobalTransactional
    public void purchase(String userId, String commodityCode, int orderCount) {
        ......
    }

4)、seata控制分布式事务需要

* Seata控制分布式事务
* 1)、每一个微服务先必须创建undo_logo;
* 2)、安装事务协调器;seata-server: https://github.com/seata/seata/releases
* 3)、整合
*      1、导入依赖  spring-cloud-starter-alibaba-seata  seata-all-1.0.0.jar
*      2、解压并启动seata-server
*          registry.conf注册中心相关的配置,修改registry type=nacos
*          file.conf
*      3、所有想要用到分布式事务的微服务使用seata DatasourceProxy代理自己的数据源
*      4、每个微服务,都必须导入registry.cof
*      file.conf  vgroup_mapping.{application.name}-fescar-service-group = "default"
*      5、启动测试
*      6、给分布式大事务的路口标注@GlobalTransactional
*      7、每一个远程的小事务用 @Transactional

5)、每个微服务数据库加上undo_log(回滚日志表)

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

因为linux出现问题,时间紧迫没处理,现在各个数据服务器连接不起来 ,只是跟着老师敲,没法展示

6)、环境搭建

从 https://github.com/seata/seata/releases,下载服务器软件包,将其解压缩。

下载senta-server-1.0.0并修改register.conf,使用nacos作为注册中心(这里根据自己maven版本下载)

修改registry.conf,把nacos作为seata的注册中

添加“com.atguigu.gulimall.order.config.MySeataConfig”类,代码如下:

@Configuration
public class MySeataConfig {
    @Autowired
    DataSourceProperties dataSourceProperties;

    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties){
        //得到数据源
        HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        if (StringUtils.hasText(dataSourceProperties.getName())){
            dataSource.setPoolName(dataSourceProperties.getName());
        }
        return new DataSourceProxy(dataSource);
    }
}

gulimall-ware

修改“com.atguigu.gulimall.ware.config.WareMybatisConfig”类,代码如下:

    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties){
        //得到数据源
        HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        if (StringUtils.hasText(dataSourceProperties.getName())){
            dataSource.setPoolName(dataSourceProperties.getName());
        }
        return new DataSourceProxy(dataSource);
    }

分别给gulimall-order和gulimall-ware加上file.conf和registry.conf这两个配置,并修改file.conf

给分布式大事务的路口标注@GlobalTransactional;    每一个远程的小事务用 @Transactional

 @GlobalTransactional
    @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo submitVo) 

16、RabbitMQ延时队列(实现定时任务)

添加“com.atguigu.gulimall.order.config.MyMQConfig”类,代码如下:

@Configuration
public class MyRabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 使用JSON序列化机制,进行消息转换
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

//    @RabbitListener(queues = "stock.release.stock.queue")
//    public void handle(Message message){
//
//    }

    @Bean
    public Exchange stockEventExchange() {
        return new TopicExchange("stock-event-exchange", true, false);
    }

    @Bean
    public Queue stockReleaseStockQueue() {
        return new Queue("stock.release.stock.queue", true, false, false);
    }

    @Bean
    public Queue stockDelayQueue() {
        // String name, boolean durable, boolean exclusive, boolean autoDelete,
        //			@Nullable Map<String, Object> arguments
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", "stock-event-exchange");
        arguments.put("x-dead-letter-routing-key", "stock.release");
        arguments.put("x-message-ttl", 120000);
        return new Queue("stock.delay.queue", true, false, false, arguments);
    }

    @Bean
    public Binding stockReleaseStockBinding() {
        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,
                "stock-event-exchange",
                "stock.release.#",
                new HashMap<>());
    }

    @Bean
    public Binding stockLockedBinding() {
        return new Binding("stock.delay.queue",
                Binding.DestinationType.QUEUE,
                "stock-event-exchange",
                "stock.locked",
                new HashMap<>());
    }
}

修改“com.atguigu.gulimall.order.web.HelloController”类代码如下:

    @ResponseBody
    @GetMapping("/test/createOrder")
    public String createOrderTest(){
        //订单下单成功
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(UUID.randomUUID().toString());
        orderEntity.setModifyTime(new Date());
        //给MQ发送消息
        rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",orderEntity);
        return "ok";
    }

17、库存自动解锁

gulimall-ware  服务添加RabbitMQ

导入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

添加配置

spring.rabbitmq.host=172.20.10.9
spring.rabbitmq.virtual-host=/

主启动类添加注解

@EnableRabbit
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallWareApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallWareApplication.class, args);
    }

}

添加“com.atguigu.gulimall.ware.config.MyRabbitConfig”类,代码如下:

@Configuration
public class MyRabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 使用JSON序列化机制,进行消息转换
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    @RabbitListener(queues = "stock.release.stock.queue")
    public void handle(Message message){

    }

    @Bean
    public Exchange stockEventExchange() {
        return new TopicExchange("stock-event-exchange", true, false);
    }

    @Bean
    public Queue stockReleaseStockQueue() {
        return new Queue("stock.release.stock.queue", true, false, false);
    }

    @Bean
    public Queue stockDelayQueue() {
        // String name, boolean durable, boolean exclusive, boolean autoDelete,
        //			@Nullable Map<String, Object> arguments
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", "stock-event-exchange");
        arguments.put("x-dead-letter-routing-key", "order.release");
        arguments.put("x-message-ttl", 120000);
        return new Queue("stock.delay.queue", true, false, false, arguments);
    }

    @Bean
    public Binding stockReleaseStockBinding() {
        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,
                "stock-event-exchange",
                "stock.release.#",
                new HashMap<>());
    }

    @Bean
    public Binding orderLockedBinding() {
        return new Binding("stock.delay.queue",
                Binding.DestinationType.QUEUE,
                "stock-event-exchange",
                "stock.locked",
                new HashMap<>());
    }
}

修改表wms_ware_order_task_detail

修改“com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity”类,代码 如下:

@AllArgsConstructor
@NoArgsConstructor
@Data
@TableName("wms_ware_order_task_detail")
public class WareOrderTaskDetailEntity implements Serializable {

	/**
	 * id
	 */
	@TableId
	private Long id;
	/**
	 * sku_id
	 */
	private Long skuId;
	/**
	 * sku_name
	 */
	private String skuName;
	/**
	 * 购买个数
	 */
	private Integer skuNum;
	/**
	 * 工作单id
	 */
	private Long taskId;
	/**
	 * 仓库id
	 */
	private long wareId;
	/**
	 * 锁定状态
	 */
	private Integer lockStatus;
}

修改WareOrderTaskDetailDao.xml

<resultMap type="com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity" id="wareOrderTaskDetailMap">
        <result property="id" column="id"/>
        <result property="skuId" column="sku_id"/>
        <result property="skuName" column="sku_name"/>
        <result property="skuNum" column="sku_num"/>
        <result property="taskId" column="task_id"/>
        <result property="wareId" column="ware_id"/>
        <result property="lockStatus" column="lock_status"/>
    </resultMap>

 

1)、库存锁定

在库存锁定是添加以下逻辑

  • 由于可能订单回滚的情况,所以为了能够得到库存锁定的信息,在锁定时需要记录库存工作单,其中包括订单信息和锁定库存时的信息(仓库id,商品id,锁了几件...)
  • 在锁定成功后,向延迟队列发消息,带上库存锁定的相关信息

修改“com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl”类,代码如下:

   @Transactional
@Override
public Boolean orderLockStock(WareSkuLockVo wareSkuLockVo) {
    //因为可能出现订单回滚后,库存锁定不回滚的情况,但订单已经回滚,得不到库存锁定信息,因此要有库存工作单
    WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
    taskEntity.setOrderSn(wareSkuLockVo.getOrderSn());
    taskEntity.setCreateTime(new Date());
    wareOrderTaskService.save(taskEntity);

    List<OrderItemVo> itemVos = wareSkuLockVo.getLocks();
    List<SkuLockVo> lockVos = itemVos.stream().map((item) -> {
        SkuLockVo skuLockVo = new SkuLockVo();
        skuLockVo.setSkuId(item.getSkuId());
        skuLockVo.setNum(item.getCount());
        List<Long> wareIds = baseMapper.listWareIdsHasStock(item.getSkuId(), item.getCount());
        skuLockVo.setWareIds(wareIds);
        return skuLockVo;
    }).collect(Collectors.toList());

    for (SkuLockVo lockVo : lockVos) {
        boolean lock = true;
        Long skuId = lockVo.getSkuId();
        List<Long> wareIds = lockVo.getWareIds();
        if (wareIds == null || wareIds.size() == 0) {
            throw new NoStockException(skuId);
        }else {
            for (Long wareId : wareIds) {
                Long count=baseMapper.lockWareSku(skuId, lockVo.getNum(), wareId);
                if (count==0){
                    lock=false;
                }else {
                    //锁定成功,保存工作单详情
                    WareOrderTaskDetailEntity detailEntity = WareOrderTaskDetailEntity.builder()
                            .skuId(skuId)
                            .skuName("")
                            .skuNum(lockVo.getNum())
                            .taskId(taskEntity.getId())
                            .wareId(wareId)
                            .lockStatus(1).build();
                    wareOrderTaskDetailService.save(detailEntity);
                    //发送库存锁定消息至延迟队列
                    StockLockedTo lockedTo = new StockLockedTo();
                    lockedTo.setId(taskEntity.getId());
                    StockDetailTo detailTo = new StockDetailTo();
                    BeanUtils.copyProperties(detailEntity,detailTo);
                    lockedTo.setDetailTo(detailTo);
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);

                    lock = true;
                    break;
                }
            }
        }
        if (!lock) throw new NoStockException(skuId);
    }
    return true;
}

 

2)、监听队列

  • 延迟队列会将过期的消息路由至"stock.release.stock.queue",通过监听该队列实现库存的解锁
  • 为保证消息的可靠到达,我们使用手动确认消息的模式,在解锁成功后确认消息,若出现异常则重新归队 
@Component
@RabbitListener(queues = {"stock.release.stock.queue"})
public class StockReleaseListener {

    @Autowired
    private WareSkuService wareSkuService;

    @RabbitHandler
    public void handleStockLockedRelease(StockLockedTo stockLockedTo, Message message, Channel channel) throws IOException {
        log.info("************************收到库存解锁的消息********************************");
        try {
            wareSkuService.unlock(stockLockedTo);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }
}

3)、库存解锁

  • 如果工作单详情不为空,说明该库存锁定成功
    • 查询最新的订单状态,如果订单不存在,说明订单提交出现异常回滚,或者订单处于已取消的状态,我们都对已锁定的库存进行解锁
  • 如果工作单详情为空,说明库存未锁定,自然无需解锁
  • 为保证幂等性,我们分别对订单的状态和工作单的状态都进行了判断,只有当订单过期且工作单显示当前库存处于锁定的状态时,才进行库存的解锁
@Override
    public void unlock(StockLockedTo stockLockedTo) {
        StockDetailTo detailTo = stockLockedTo.getDetailTo();
        WareOrderTaskDetailEntity detailEntity = wareOrderTaskDetailService.getById(detailTo.getId());
        //1.如果工作单详情不为空,说明该库存锁定成功
        if (detailEntity != null) {
            WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(stockLockedTo.getId());
            R r = orderFeignService.infoByOrderSn(taskEntity.getOrderSn());
            if (r.getCode() == 0) {
                OrderTo order = r.getData("order", new TypeReference<OrderTo>() {
                });
                //没有这个订单||订单状态已经取消 解锁库存
                if (order == null||order.getStatus()== OrderStatusEnum.CANCLED.getCode()) {
                    //为保证幂等性,只有当工作单详情处于被锁定的情况下才进行解锁
                    if (detailEntity.getLockStatus()== WareTaskStatusEnum.Locked.getCode()){
                        unlockStock(detailTo.getSkuId(), detailTo.getSkuNum(), detailTo.getWareId(), detailEntity.getId());
                    }
                }
            }else {
                throw new RuntimeException("远程调用订单服务失败");
            }
        }else {
            //无需解锁
        }
    }

远程查询订单状态  gulimall-order

添加“com.atguigu.gulimall.ware.feign.OrderFeignService”类,代码如下:

@FeignClient("gulimall-order")
public interface OrderFeignService {
    @GetMapping("/order/order/status/{orderSn}")
    R getOrderStatus(@PathVariable("orderSn") String orderSn);
}

添加“com.atguigu.gulimall.order.controller.OrderController”类,代码如下:

    @GetMapping("/status/{orderSn}")
    public R getOrderStatus(@PathVariable("orderSn") String orderSn){
        OrderEntity orderEntity = orderService.getOrderByOrderSn(orderSn);
        return R.ok().setData(orderEntity);
    }

修改“com.atguigu.gulimall.order.service.OrderService”类,代码如下:

   OrderEntity getOrderByOrderSn(String orderSn);

修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下:

    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {
        OrderEntity order_sn = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
        return order_sn;
    }

修改“com.atguigu.gulimall.ware.dao.WareSkuDao”类,代码如下:

void unlockStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("num") Integer num, @Param("taskDetailId") Long taskDetailId);
     <update id="unlockStock">
        update wms_ware_sku set stock_locked = stock_locked - #{num}
        where sku_id = #{skuId} and ware_id = #{wareId}
    </update>

由于gulimall-order添加了拦截器,只要使用该服务必须登录才行。因为这边需要远程调用订单,但不需要登录,所以给这个路径放行

@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    public static ThreadLocal<MemberResponseVO> loginUser = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        boolean match = new AntPathMatcher().match("/order/order/status/**", uri);
        if (match){
            return true;
        }


        MemberResponseVO attribute = (MemberResponseVO) 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;
        }
    }
}

18、定时关单

1)、提交订单

添加“com.atguigu.gulimall.order.web.OrderWebController”类,代码如下:

/**
     * 下单功能
     * @param submitVo
     * @param model
     * @param redirectAttributes
     * @return
     */
    @PostMapping("/submitOrder")
    public String submitOrder(OrderSubmitVo submitVo, Model model, RedirectAttributes redirectAttributes) {
        // 下单 去创建订单 验证令牌 核算价格 锁定库存
        try {
            SubmitOrderResponseVo responseVo = orderService.submitOrder(submitVo);
            System.out.println("============================="+responseVo.getCode());
            if (responseVo.getCode() == 0) {
                // 下单成功到选择支付方式页面
                model.addAttribute("submitOrderResp", responseVo);
                return "pay";
            } else {
                // 订单失败返回到订单确认页面
                String msg = "下订单失败: ";
                switch (responseVo.getCode()) {
                    case 1 : msg += "订单信息过期, 请刷新后再次提交."; break;
                    case 2 : msg += "订单中的商品价格发生变化, 请刷新后再次提交."; break;
                    case 3 : msg += "库存锁定失败, 商品库存不足."; break;
                }
                redirectAttributes.addFlashAttribute("msg", msg);
                return "redirect:http://order.gulimall.com/toTrade";
            }
        } catch (Exception e) {
            if (e instanceof NoStockException) {
                String message = e.getMessage();
                redirectAttributes.addFlashAttribute("msg", message);
            }
            return "redirect:http://order.gulimall.com/toTrade";
        }

2)、监听队列

创建订单的消息会进入延迟队列,最终发送至队列order.release.order.queue,因此我们对该队列进行监听,进行订单的关闭

@Service
@RabbitListener(queues = "order.release.order.queue")
public class OrderCloseListener {

    @Autowired
    private OrderService orderService;

    @RabbitHandler
    public void listener(OrderEntity entity, Channel channel, Message message) throws IOException {
        try {
            orderService.closeOrder(entity);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            // 修改失败 拒绝消息 使消息重新入队
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }

}

 3)、关闭订单

  • 由于要保证幂等性,因此要查询最新的订单状态判断是否需要关单
  • 关闭订单后也需要解锁库存,因此发送消息进行库存、会员服务对应的解锁

 添加“com.atguigu.gulimall.order.service.OrderService”类,代码如下:

void closeOrder(OrderEntity entity);

 修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”,代码如下:

@Override
    public void closeOrder(OrderEntity entity) {
        //查询当前这个订单地最新状态
        OrderEntity orderEntity = this.getById(entity.getId());
        if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){
            //关单
            OrderEntity update = new OrderEntity();
            update.setId(entity.getId());
            update.setStatus(OrderStatusEnum.CANCLED.getCode());
            this.updateById(update);
            OrderTo orderTo = new OrderTo();
            BeanUtils.copyProperties(orderEntity,orderTo);
            //发给MQ一个
            rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",orderTo);
        }
    }

添加“com.atguigu.common.to.mq.OrderTo”类,代码如下:

@Data
public class OrderTo {
    private Long id;
    /**
     * member_id
     */
    private Long memberId;
    /**
     * 订单号
     */
    private String orderSn;
    /**
     * 使用的优惠券
     */
    private Long couponId;
    /**
     * create_time
     */
    private Date createTime;
    /**
     * 用户名
     */
    private String memberUsername;
    /**
     * 订单总额
     */
    private BigDecimal totalAmount;
    /**
     * 应付总额
     */
    private BigDecimal payAmount;
    /**
     * 运费金额
     */
    private BigDecimal freightAmount;
    /**
     * 促销优化金额(促销价、满减、阶梯价)
     */
    private BigDecimal promotionAmount;
    /**
     * 积分抵扣金额
     */
    private BigDecimal integrationAmount;
    /**
     * 优惠券抵扣金额
     */
    private BigDecimal couponAmount;
    /**
     * 后台调整订单使用的折扣金额
     */
    private BigDecimal discountAmount;
    /**
     * 支付方式【1->支付宝;2->微信;3->银联; 4->货到付款;】
     */
    private Integer payType;
    /**
     * 订单来源[0->PC订单;1->app订单]
     */
    private Integer sourceType;
    /**
     * 订单状态【0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单】
     */
    private Integer status;
    /**
     * 物流公司(配送方式)
     */
    private String deliveryCompany;
    /**
     * 物流单号
     */
    private String deliverySn;
    /**
     * 自动确认时间(天)
     */
    private Integer autoConfirmDay;
    /**
     * 可以获得的积分
     */
    private Integer integration;
    /**
     * 可以获得的成长值
     */
    private Integer growth;
    /**
     * 发票类型[0->不开发票;1->电子发票;2->纸质发票]
     */
    private Integer billType;
    /**
     * 发票抬头
     */
    private String billHeader;
    /**
     * 发票内容
     */
    private String billContent;
    /**
     * 收票人电话
     */
    private String billReceiverPhone;
    /**
     * 收票人邮箱
     */
    private String billReceiverEmail;
    /**
     * 收货人姓名
     */
    private String receiverName;
    /**
     * 收货人电话
     */
    private String receiverPhone;
    /**
     * 收货人邮编
     */
    private String receiverPostCode;
    /**
     * 省份/直辖市
     */
    private String receiverProvince;
    /**
     * 城市
     */
    private String receiverCity;
    /**
     * 区
     */
    private String receiverRegion;
    /**
     * 详细地址
     */
    private String receiverDetailAddress;
    /**
     * 订单备注
     */
    private String note;
    /**
     * 确认收货状态[0->未确认;1->已确认]
     */
    private Integer confirmStatus;
    /**
     * 删除状态【0->未删除;1->已删除】
     */
    private Integer deleteStatus;
    /**
     * 下单时使用的积分
     */
    private Integer useIntegration;
    /**
     * 支付时间
     */
    private Date paymentTime;
    /**
     * 发货时间
     */
    private Date deliveryTime;
    /**
     * 确认收货时间
     */
    private Date receiveTime;
    /**
     * 评价时间
     */
    private Date commentTime;
    /**
     * 修改时间
     */
    private Date modifyTime;
}

4)、解锁库存

修改“com.atguigu.gulimall.ware.listener.StockReleaseListener”类,代码如下:

@Slf4j
@Service
@RabbitListener(queues = "stock.release.stock.queue")
public class StockReleaseListener {

    @Autowired
    WareSkuService wareSkuService;

    /**
     * 1、库存自动解锁
     * 库存解锁的场景
     *     1)、下订单成功,库存锁定成功,接下来的业务调用失效,导致订单回滚。之前锁定的库存就要自动回滚
     *     2)、订单失败
     *     锁库存失败
     *
     * 只要解锁库存的消息失败。一定澳告诉服务解锁失败。
     *
     */
    @RabbitHandler
    public void handleStockLockedRelease(StockLockedTO to, Message message, Channel channel) throws IOException {
        log.info("************************收到库存解锁的消息********************************");
        try {
            wareSkuService.unLockStock(to);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }

    @RabbitHandler
    public void handleOrderCloseRelease(OrderTo to, Message message, Channel channel) throws IOException {
        log.info("************************订单关闭准备解锁库存********************************");
        try {
            wareSkuService.unLockStockForOrder(to);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }
}

修改“com.atguigu.gulimall.ware.service.WareSkuService”类,代码如下:

void unLockStockForOrder(OrderTo to);

修改“com.atguigu.gulimall.order.service.impl.OrderServiceImpl”类,代码如下:

     /**
     * 防止订单服务卡顿,导致订单状态一直改变不了,库存消息优先到期,查订单状态新建状态,什么都不做就走了
     * 导致卡顿的订单,永远不能解锁库存
     * @param to
     */
    @Transactional
    @Override
    public void unLockStockForOrder(OrderTo to) {
        String orderSn = to.getOrderSn();
        //查一下最新的库存解锁状态,防止重复解锁库存
        R r = orderFeignService.getOrderStatus(orderSn);
        WareOrderTaskEntity task = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
        Long id = task.getId();
        //按照工作单找到所有 没有解锁的库存,进行解锁
        List<WareOrderTaskDetailEntity> entities = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", id).eq("lock_status", 1));
        for (WareOrderTaskDetailEntity entity : entities) {
            unLockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());
            
        }
    }

修改“com.atguigu.gulimall.ware.service.WareOrderTaskService”类,代码如下:

WareOrderTaskEntity getOrderTaskByOrderSn(String orderSn);

修改“com.atguigu.gulimall.ware.service.impl.WareOrderTaskServiceImpl”类,代码如下:

    @Override
    public WareOrderTaskEntity getOrderTaskByOrderSn(String orderSn) {
        WareOrderTaskEntity orderTaskEntity = this.getOne(new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", orderSn));
        return orderTaskEntity;
    }

总结:

19、消息丢失、积压、重复等解决方案

修改“com.atguigu.gulimall.order.config.MyRabbitConfig”类,代码如下:

@Configuration
public class MyRabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 使用JSON序列化机制,进行消息转换
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    
    @PostConstruct   //MyRabbitConfig对象创建完以后,执行这个方法
    public void initRabbitTemplate(){
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 只要消息抵达Broker就b = true
             * @param correlationData 当前消息的唯一关联数据(这个消息的唯一id)
             * @param ack  消息是否成功收到
             * @param s 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String s) {
                /**
                 *1、做好消息确认机制(publisher,consumer【手动ack】)
                 * 2、每一个发送的消息都在数据库做好记录。定期将失效的消息再次发送
                 */
                //服务器收到了
                System.out.println("confirm...correlationData["+correlationData+"]==>ack["+ack+"]s==>["+s+"]");
            }
        });

        //设置消息抵达队列的确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定的队列,就触发这个失败回调
             * @param message 投递失败的消息详细信息
             * @param i 回复的状态码
             * @param s 回复的文本内容
             * @param s1 当时这个消息发给哪个交换机
             * @param s2 当时这个消息用哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
                //报错误了。修改数据库当前消息的错误状态-》错误
                System.out.println("Fail Message["+message+"]==>i["+i+"]==>s["+s+"]==>s1["+s1+"]==>s2["+s2+"]");
            }
        });
    }
}

 

 

 

 

 


本文链接: http://www.dtmao.cc/news_show_450119.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?