谷粒商城总结下

15.分布式Session和SpringSession

15.1session的认识及分布式下的session问题的解决方案

什么是session

当访问服务器否个网页的时候,会在服务器端的内存里开辟一块内存,这块内存就叫做session,而这个内存是跟浏览器关联在一起的。这个浏览器指的是浏览器窗口,或者是浏览器的子窗口,意思就是,只允许当前这个session对应的浏览器访问,就算是在同一个机器上新启的浏览器也是啊·无法访问的。而另外一个浏览器也需要记录session的话,就会再启一个属于自己的session

开发中的两大session问题

  • 首先了解什么是一级域名 二级域名 父域名 子域名
    在这里插入图片描述

1.session域名跳转不共享问题

​ 我们在做登录时 auth.gulimall.com存储了用户的登录信息 保存了cookie中保存了sessionId 但是我们登录成功后重 定向到了gulimall.com这个域名下 所以sessionid不会保存 也就是说我们在登录完成跳到商城首页后 不能取出登录人的信息

2.负载均衡后的session不能保存问题

在分布式情况下 我们第一次负载均衡来到一号服务器 登录后一号服务器在内存中存了session信息 下一次我们负载均衡来到二号服务器 由于二号服务器没有存我们的session信息 即使我们的cookie中有sessionid信息 但是不同的服务器session不共享 导致我们的session无用 还得重新输入信息=

解决方案

  • 解决session问题原理

    1。session不共享问题的域名跳转解决原理:tomcat在内存中保存我们的session时 会设置域名的作用范围 默认就是我们当前的域名 所以我们可以自己设置session时改变作用范围

    2。负载均衡后的session不能保存问题: 可以把session都存储到redis中 这样不管负载均衡到哪一台服务器上都可以保存session 但存取到redis中是网络交互过程 会浪费时间

15.2SpringSession完美解决分布式Session问题

15.2.1SpringBoot整合SpringSession(已经整合完redis)解决session不能保存多台服务器问题

  • 导入依赖(本次使用Redis缓存Session 所以导入session-redis整合包)

            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
            </dependency>
    
  • 开启redis缓存session功能注解**@EnableRedisHttpSession**

@EnableRedisHttpSession //开启redis作为session的功能
@EnableFeignClients(basePackages = "com.atguigu.gulimall.auth.feign")
@SpringBootApplication
@EnableDiscoveryClient
public class GulimallAuthServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallAuthServerApplication.class, args);
    }
}
  • 配置文件
#保存session到redis中
spring.session.store-type=redis
#session过期时间
server.servlet.session.timeout=30m
#session的刷新策略
spring.session.redis.flush-mode=on_save
# session存储到redis中的前缀
spring.session.redis.namespace=spring:session

上面的操作完成之后 session就会在redis中存储 测试中先进入auth服务 登陆成功后修改cookie中sessionId的作用域为.gulimall.com之后 在商城首页得到了用户信息 还有两个问题

1.在对象传输到redis的过程中 如何使用JSON进行序列化

2.如何将auth服务的cookie里的session作用域放大

15.2.2解决session的作用域和使用JSON序列化问题

编写配置类(每个服务都编写一份)

@Configuration
public class GulimallSessionConfig {
    //设置session的作用域
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULISESSION");
        return cookieSerializer;
    }
    //设置session的存储Redis的序列化
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

经过测试 解决了session的跨域问题和序列化问题

Spring+Session整个流程

当浏览器去访问auth.gulimall.com发送请求 登陆成功后 tomcat会保存你的个人信息到内存中(session) 因为使用了springsession 所以原来存储在服务器内存中的个人信息会保存到redis中 然后给浏览器的cookie中存sessionID 有了sessionid就可以读取到session中的存的数据 但是服务器默认存sessionid的作用域为本域 所以我们修改了配置 使这个

session

16.SSO单点登录核心原理

参考web开发 seesion-cookie笔记

关键 在第一次访问资源无权限进行跳转时 在认证成功后 生成一个token 并使用token作为key 用户信息最为value存入redis 并手动生成一个cookie 值是token 这样作的好处就是在下个用户登录的时候 浏览器会带着cookie的信息 这样请求一过来 如果得到了cookie的信息 直接就返回原来页面并带着cookie的值 也就是token 然后经过redis查询得到用户的信息 并存入session中 这就实现了免登录的功能

17SpringMVC功能配置回顾

17.1实现WebMvcConfigurer接口的主要方法

17.1.1添加拦截器并拦截请求

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //前提是容器中必须有配置的拦截器  addPathPatterns() 配置拦截那些请求
        registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
    }

17.1.2只作为页面跳转的controller抽出来进行视图映射

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/reg.html").setViewName("reg");
    }

17.2拦截器HandlerInterceptor的使用()**

    //在方法请求处理之前拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      	//匹配的路径可以放行  
        String Uri =  request.getRequestURI();
        boolean match =	new AntPathMatcher().match("/xx/xx",uri);
        return ture;
      
        return true; //放行
    }
    //在方法请求处理之后 但是它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对控制器处理之后的ModelAndView对象进行操作
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
     
    //在整个方法全部执行之后并且页面渲染之后处理 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }

17.3ThreadLocal的使用

线程之间共享数据

找详细资料

18.仿京东构造临时用户购物车功能

18.1购物车合并及创建临时用户的核心原理

  • 未登录时 打开购物车时都会创建一个临时用户保存到cookie中 key是user-key value是一串字符串
  • 登录时 也会穿件一个临时user-key

所以通过拦截器来实现 在访问购物车服务时 都要先通过拦截器 先检查session中有无用户信息 有的话设置userId

没有的话一定要创建一个user-key

19.RabbitMQ

19.1主要名词概念及工作原理

在这里插入图片描述

19.2Docker安装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

在这里插入图片描述

19.3交换机Exchange类型

Exchange分发消息时根据类型的不同分发策略有区别,目前工四种类型 ,directfanouttopicheaders,headers匹配AMQP消息的header而不是路由键,headers交换机和direct交换机完全一致,但性能差很多,目前几乎用不到了,所以直接使用另外三种类型

19.3.1Direct Exchange

在这里插入图片描述

19.3.2Fanout Exchange

在这里插入图片描述

19.3.3Topic Exchange

在这里插入图片描述

19.4RabbitMQ整合SpringBoot

1.导入依赖(引入场景启动器)

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

2.配置文件

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

3.(不是必须)容器中注入JSON序列化机制

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

19.4.1使用AmqpAdmin创建Exchange Queue 并进行绑定binding

1.创建交换机

    @Test
    void contextLoads() {
        //创建一个direct类型的交换机
        DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
        amqpAdmin.declareExchange(directExchange);
    }

2.创建队列

    @Test
    void createQueue(){
        Queue queue = new Queue("TestJava",false,false,false);
        amqpAdmin.declareQueue(queue);
    }

3.创建绑定关系

    @Test
    void createBinding(){
        Binding binding = new Binding("TestJava",Binding.DestinationType.QUEUE,"hello-java-exchange","java",null);
        amqpAdmin.declareBinding(binding);
    }

19.4.2使用RabbitTemplate向交换机中发送消息

//可以发送字符串 数字 和对象    
public String send(){
        for (int i = 0; i <4 ; i++) {
            OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();
            orderReturnReasonEntity.setId(2L);
            orderReturnReasonEntity.setName(""+i);
            //第一个参数是交换机名字 第二个是route-key 第三个是发送的消息(对象)
            rabbitTemplate.convertAndSend("hello-java-exchange","java",orderReturnReasonEntity);
            OrderReturnApplyEntity orderReturnApplyEntity = new OrderReturnApplyEntity();
            orderReturnApplyEntity.setCompanyAddress("sd");
            rabbitTemplate.convertAndSend("hello-java-exchange","java",orderReturnApplyEntity);
        }

19.4.3使用@RabbitListener和@RabbitHandler两个注解进行监听队列并取出消息

只有一个消息完全处理完 方法运行结束 才可以接受到下一个消息

//@RabbitListener可以标注在接口、类、枚举和方法上  @RabbitHandler只能标志在方法上
//所以一般用法@RabbitListener标注在类上 指明要监听哪些队列 @RabbitHandler标注在方法上 可以取出不同的对象
@RabbitListener(queues = {"TestJava"})
public class A{
    //使用重载方法 取出队列中的两个不同的对象
    @RabbitHandler
    public void receive(OrderReturnReasonEntity entity, Message message){
        //可以取出消息头
        MessageProperties messageProperties = message.getMessageProperties();
        System.out.println("接受到了消息"+entity+messageProperties);
    }
    @RabbitHandler
    public void receive(OrderReturnApplyEntity entity1){
        System.out.println("接受到了消息2"+entity1);
    }

19.5RabbitMq消息确认机制-可靠抵达(发送端)在这里插入图片描述

19.5.1.confirmCallback机制 (消费者到broker服务器)

在这里插入图片描述

编写配置文件

#配置回调 发送端确认
spring.rabbitmq.publisher-confirm-type=correlated
@Configuration
public class RabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    //Bean初始化之前
    @PostConstruct
    public void initRabbitTemplate(){
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            //correlationData可以再发送信息的时候指定 
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
               System.out.println("confirm...correlationData"+correlationData+""+ack+cause);
            }
        });
    }
}

19.5.2returnCallback机制(broker服务器到queue队列)

在这里插入图片描述

#开启交换机中消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列 已异步发送优先回调retuen的确认
spring.rabbitmq.template.mandatory=true
@Configuration
public class RabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;
    @PostConstruct
    public void initRabbitTemplate() {
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /*
             * @description:交换机没有到达队列时的回调 投递成功不会触发
             * @param message 投递失败的消息详细信息
             * @param replyCode 回复的状态码
             * @param replyText  回复的文本内容
             * @param exchange  消息从哪个交换机中发出来
             * @param routingKey  消息携带的路由键
             * @return: void
             * @date: 2020/10/6 20:23
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                    System.out.println(message);
            }
        });
    }
}

19.6消息确认机制(接收端)

在这里插入图片描述

默认是自动确认的 只要消费者接受到了消息 就会自动删除这个消息

  • 问题:我们收到很多消息 自动回复给服务器ack 但只有一个消息处理成功 如果发生宕机 那么消息会丢失
#手动确认消息(消费者)
spring.rabbitmq.listener.simple.acknowledge-mode=manual

解决:手动确认 --只要我们没有明确告诉服务器我们收到了消息 没有ack 那么消息一直就是unacked状态 即使consumer宕机 消息也会保存到队里中 在ready中

  • 代码示例
    @RabbitHandler
    public void receive(OrderReturnReasonEntity entity, Message message, Channel channel){
        MessageProperties messageProperties = message.getMessageProperties();
        System.out.println("接受到了消息"+entity+messageProperties);
        //deliveryTag每个队列的标号 相当于每个消息的编号
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println(deliveryTag);
        try {
            //消息确认 第二个参数是是否批量确认
            channel.basicAck(deliveryTag,false);
            //消息拒接模式 第二个参数是否批量拒绝 第三个参数是否将消息重新入队 true重新入队 false
            直接丢弃
            channel.basicNack(deliveryTag,false,false);
        } catch (IOException e) {
            e.printStackTrace();
        }

只要没有调用ack确认方法 就和拒签并且重新入队为true是一样的效果 服务器就认为你在处理消息 消息就不会丢失

如果调用了Nack方法 并且重新入队设为false 那么消息就会直接丢弃 为true消息重新入队重新投放

最好把拒绝接受的消息 如果丢弃的话放入到死信队列中

参考:Google 书签帖子

19.7订单业务完整逻辑使用MQ

在这里插入图片描述

参考码云Gulimall

19.8RabbitMQ的优化及问题

  • 保证消息可靠性
    在这里插入图片描述

  • 消息重复性
    在这里插入图片描述

  • 消息积压

在这里插入图片描述

20.接口幂等性

20.1使用Redis原子令牌进行验证

如果要对提交订单的请求进行验证 避免因网络问题进行重复提交的问题 使用Redis UUID 生成字符串进行验证

在跳转到提交订单页面的时候 生成并传一个UUID的字符串传到提交订单页面 并保存到Redis中 设置过期时间 然后点击提交订单时提交这个数据 后端拿到数据中的令牌信息和Redis进行对比 然后进行删除令牌 注意这里要使用Lua脚本 保证对比令牌然后删除是原子性的

Lua脚本

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

使用RedisTemplate执行脚本

Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId()), orderToken);

返回1说明验证成功 0代表失败

21.事务与分布式事务

21.1本地事务@Transactional及分布式下出现的本地事务问题

//SpringBoot提供的本地事务控制注解 只能回滚本地的数据库 对于远程服务出异常无法回滚 因为操作的不是同一个数据库
@Transactional

分布式下远程调用使用@Transactional出现的问题

1.远程服务假失败

远程服务其实成功了 但是由于网络问题 读取超时抛出了异常 这是事务回滚 但是不能回滚远程服务 导致其他操作回滚 远 程无法回滚 造成业务出问题

2.远程服务成功 但是下面的其他方法出现异常 导致本地事务回滚 但是远程服务无法回滚

  • 参考谷粒商城 事务MD文档
  • 参考P284视频

21.2分布式事务

详情请看谷粒商城事务与分布式事务MD文档

21.3分布式事务控制——Seata的使用

官方文档: http://seata.io/zh-cn/docs/overview/what-is-seata.html

  • 导入依赖
       <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
  • 配置类 在容器中配置自定义的被Seata包装过的数据源
@Configuration
public class SeataConfig {

    @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);
    }
}
  • 修改Nacos的配置文件 注册中心改为Nacos 修改事务日志存储方式为db 或者file(省事) 粘贴 两个配置文件到resource目录下

    改成这个形式 分组自己制定 可以为default

seata的全局注解默认是AT模式 实现要搞各种锁在高并发下不推荐使用

高并发下使用MQ延时队列 下面是下单解锁库存延时队列逻辑
在这里插入图片描述

21.4MQ延时队列

实现原理

  • 设置队列过期时间实现延时队列

先有交换机发送消息给A队列 这个A队列不能有任何人监听 设置消息的存活时间 设置如果消息过期后的路由交换机

到了存活时间队列中的消息 直接放到死信交换机中 再由这个交换机进行路由到有人监听的队列中
在这里插入图片描述

  • 设置消息的过期时间实现延时队列

    这种会出现问题 RabbitMQ默认使用懒加载机制 如果第一个消息过期时间是5分钟 第二是消息过期时间是1分钟 那么第二个消息要等待第一个消息被丢弃之后才会被丢弃
    在这里插入图片描述

21.4.1模拟延时队列

原理:消费者先发送消息到交换机 交换机发布消息到死信队列 过了指定的时间之后 死信队列中的消息重新发到交换机中 交换机发布消息到有人监听的队列中

在这里插入图片描述

21.4.2使用Spring容器快速创建交换机 队列 和绑定关系(并设置参数)

import java.util.HashMap;


@Configuration
public class MyRabbitMQConfig {

    /* 容器中的Queue、Exchange、Binding 会自动创建(在RabbitMQ)不存在的情况下 */

    /**
     * 死信队列
     *
     * @return
     */@Bean
    public Queue orderDelayQueue() {
        /*
            Queue(String name,  队列名字
            boolean durable,  是否持久化
            boolean exclusive,  是否排他
            boolean autoDelete, 是否自动删除
            Map<String, Object> arguments) 属性
         */
        HashMap<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", "order-event-exchange");
        arguments.put("x-dead-letter-routing-key", "order.release.order");
        arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟
        Queue queue = new Queue("order.delay.queue", true, false, false, arguments);

        return queue;
    }
    /**
     * 普通队列
     *
     * @return
     */
    @Bean
    public Queue orderReleaseQueue() {

        Queue queue = new Queue("order.release.order.queue", true, false, false);

        return queue;
    }
    /**
     * TopicExchange
     *
     * @return
     */
    @Bean
    public Exchange orderEventExchange() {
        /*
         *   String name,
         *   boolean durable,
         *   boolean autoDelete,
         *   Map<String, Object> arguments
         * */
        return new TopicExchange("order-event-exchange", true, false);

    }

    @Bean
    public Binding orderCreateBinding() {
        /*
         * String destination, 目的地(队列名或者交换机名字)
         * DestinationType destinationType, 目的地类型(Queue、Exhcange)
         * String exchange,
         * String routingKey,
         * Map<String, Object> arguments
         * */
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order",
                null);
    }
    @Bean
    public Binding orderReleaseBinding() {

        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order",
                null);
    }

    /**
     * 订单释放直接和库存释放进行绑定
     * @return
     */
    @Bean
    public Binding orderReleaseOtherBinding() {

        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.other.#",
                null);
    }

    /**
     * 商品秒杀队列
     * @return
     */
    @Bean
    public Queue orderSecKillOrrderQueue() {
        Queue queue = new Queue("order.seckill.order.queue", true, false, false);
        return queue;
    }

    @Bean
    public Binding orderSecKillOrrderQueueBinding() {
        //String destination, DestinationType destinationType, String exchange, String routingKey,
        // 			Map<String, Object> arguments
        Binding binding = new Binding(
                "order.seckill.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.seckill.order",
                null);
        return binding;
    }
}

21.4.3业务中使用MQ

在这里插入图片描述

在这里插入图片描述

21.4.3.1订单到一定时间没有支付而过期被取消实现

当创建订单并提交时 先去发送消息(就是订单的订单号等等)到交换机 交换机把消息发送到死信队列 过了设定的时间 把消 息送到原交换机 不过这次路由到有消费者监听的队列中 取出消息中的消息 去查询订单的状态 如果显示未支付 或是已经被取消了 直接触发取消订单逻辑 当消息重新从私信队列发到交换机时 直接就去发送到解锁库存的队列中 去解锁库存

21.4.3.2下单业务远程调用库存业务中有方法出现异常 导致库存服务无法回滚

当远程调用锁定库存时 多保存两张表的信息 记录库存的 各种信息 锁几件 那个仓库 skuid等 以及锁定状态 并且使用延时队列发送消息 过了设定的时间 业务接收到了消息 就去查询库存的锁定状态 如果被锁定了 就解锁

22整合支付业务+内网穿透

支付流程
在这里插入图片描述

22.1配置沙箱环境 整合SpringBoot进行支付

  • 下载支付宝开放平台开发助手

​ https://opendocs.alipay.com/open/291/introduce

  • 使用开发助手点击生成秘钥 生成应用私钥和应用公钥
  • 登录支付宝 上传应用公钥支付宝生成支付宝公钥 和AppId

​ https://openhome.alipay.com/platform/appDaily.htm?tab=info

秘钥工作流程

在这里插入图片描述

  • 导入配置

    支付模板

@ConfigurationProperties(prefix = "alipay") //参数可以写到配置文件中
@Component
@Data
public class AlipayTemplate {

    //在支付宝创建的应用的id
    private   String app_id = "登录支付宝查看";

    // 商户私钥,您的PKCS8格式RSA2私钥
    private  String merchant_private_key = "商户自己的私钥(应用私钥)";
    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    private  String alipay_public_key = "上传商户公钥后支付宝生成的支付宝公钥";
    // 服务器[异步通知]页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    // 支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息 (支付宝给我们发消息 要内网穿透)
    private  String notify_url = "http://41gui1szia.52http.net/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";
    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    //同步通知,支付成功,跳转到成功页 (商城项目一般跳到已购买页面显示一些订单的状态)
    private  String return_url = "http://member.gulimall.com/success";
    // 签名方式
    private  String sign_type = "RSA2";

    // 字符编码格式
    private  String charset = "utf-8";

    // 支付宝网关; https://openapi.alipaydev.com/gateway.do
    private  String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";

    public  String pay(PayVo vo) throws AlipayApiException {

        //AlipayClient alipayClient = new DefaultAlipayClient(AlipayTemplate.gatewayUrl, AlipayTemplate.app_id, AlipayTemplate.merchant_private_key, "json", AlipayTemplate.charset, AlipayTemplate.alipay_public_key, AlipayTemplate.sign_type);
        //1、根据支付宝的配置生成一个支付客户端
        AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl,
                app_id, merchant_private_key, "json",
                charset, alipay_public_key, sign_type);

        //2、创建一个支付请求 //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(return_url);
        alipayRequest.setNotifyUrl(notify_url);

        //商户订单号,商户网站订单系统中唯一订单号,必填
        String out_trade_no = vo.getOut_trade_no();
        //付款金额,必填
        String total_amount = vo.getTotal_amount();
        //订单名称,必填
        String subject = vo.getSubject();
        //商品描述,可空
        String body = vo.getBody();

        alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
                + "\"total_amount\":\""+ total_amount +"\","
                + "\"subject\":\""+ subject +"\","
                + "\"body\":\""+ body +"\","
                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

        String result = alipayClient.pageExecute(alipayRequest).getBody();

        //会收到支付宝的响应,响应的是一个页面,只要浏览器显示这个页面,就会自动来到支付宝的收银台页面
        System.out.println("支付宝的响应:"+result);
        return result;
    }
}

支付信息的vo

@Data
public class PayVo {
    private String out_trade_no; // 商户订单号 必填
    private String subject; // 订单名称 必填
    private String total_amount;  // 付款金额 必填
    private String body; // 商品描述 可空
}
  • 调用支付接口
@Controller
public class PayWebController {

    @Autowired
    AlipayTemplate alipayTemplate;

    @ResponseBody
    @GetMapping(value = "/payOrder",produces = "text/html")
    public String payOrder() throws AlipayApiException {
	//第一步 先构造订单的详情PayVo 这些数据都是传过来的
       
        PayVo payVo = new PayVo();
        //伪代码 orderService.get() 使用service查出订单的支付信息 payVo中的数据都是查出来的
        payVo.setBody("iPhone XS Max "); //订单备注
        payVo.setOut_trade_no("22022020"); //订单号
        payVo.setSubject("HGWD"); //订单主题
        payVo.setTotal_amount("203132"); //订单总价
    //第二步 使用这个支付模板直接调用pay方法传入payVo 响应一段字符串 就是HTML代码 
    //把这段代码发给前端   就能构造出支付所需要的二维码等  支付成功后会根据我们模板中配置的 异步回调 或者同步回调来工作 
        String pay = alipayTemplate.pay(payVo);
        return pay;
    }
}

22.2内网穿透+Nginx+SpringCloud-Gateway 进行支付成功异步回调

整个流程

支付宝访问http://41gui1szia.52http.net/payed/notify 我们进行了域名映射 order.gulimall.com:80 所以就相当于访问

http://order.gulimall.com:80/payed/notify 而我们的本机做了域名映射 所有order.gulimall.com的域名请求映射为Nginx的ip地址

所以我们会访问Nginx http://xxx:80/payed/notify(80是nginx的端口 ) 而nginx又做了反向代理 监听了浏览器的 41gui1szia.52http.net域名 然后路径地址做了映射 一旦匹配 就会转到我们配置的反向代理的URl (nginx反向代理)

监听到了41gui1szia.52http.net这个域名 然后路径进行匹配 路径匹配正确 就转发到了 gateway 注意这里要带上请求头

自己设置 请求头 为order.gulimall.com 可以让网关进行识别 转到我们的订单服务

在这里插入图片描述

在这里插入图片描述

支付宝异步回调+验签

//支付宝的异步回调会给我们返回一些数据 封装成PayAsyncVo

@Controller
public class OrderPayedListener {

    @Autowired
    OrderService orderService;

    @Autowired
    AlipayTemplate alipayTemplate;

    /*
     * @description:
     * @param request
     * @return: java.lang.String
     * @date: 2020/10/13 20:36
     */
    @ResponseBody
    @GetMapping("/payed/notify")
    public String handleAliPayed(PayAsyncVo vo, HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
        //获取支付宝POST过来反馈信息  //验签
        Map<String, String> params = new HashMap<String, String>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用
            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
		//验签成功 执行业务逻辑 给支付宝返回success
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayTemplate.getAlipay_public_key(), alipayTemplate.getCharset(), alipayTemplate.getSign_type()); //调用SDK验证签名
        if (signVerified) {
            System.out.println("签名验证成功");
            String result = orderService.handlePayResult(vo);
            return result;
        } else {
            return "error";
        }
    }
}

同步调用

流程 订单支付完成后 插入数据库 直接调用查询所有订单就ok

22.3关单功能

在这里插入图片描述

支付模板里面配置

23.秒杀+定时任务整合SpringBoot

23.1定时任务+异步任务

/*
* 1.@EnableScheduling开启定时任务
*   @Scheduled(cron = "* * * * * ?") 定时任务方法
*
* 2.@EnableAsync开启异步任务功能
*   @Async 异步任务方法
*
* 3.定时任务默认是阻塞的 但不应该让其阻塞
*  解决方案
*  1)可以让业务运行以异步的方式 自己提交到线程池
*    CompletableFuture.runAsync(()->{
*        xxxx业务方法
*        },executor);
*   2)定时任务线程池 TaskSchedulingproperties
*
*   3) 让定时任务异步执行
*   异步任务 :
*
*   解决: 使用异步+定时任务 完成定时任务不阻塞的功能
*
*
* */
//开启定时任务注解
//@EnableAsync
//@EnableScheduling
@Slf4j
@Component
public class Helloscheduled {
    @Async
    @Scheduled(cron = "* * * * * ?")
    public void hello() throws InterruptedException {
        log.info("hello...");
        Thread.sleep(3000);
    }
}

业务实战

  • 配置类
@Configuration
@EnableAsync //开启异步
@EnableScheduling  //开启定时任务 
public class ScheduledConfig {

}

  • ​ 业务
@Service
public class SeckillSkuScheduled {

    @Autowired
    SeckillService seckillService;
    
    //定时上架最近三天需要秒杀的商品
    @Scheduled(cron = "0 0 3 * * ?")
    public void uploadSeckillSkuLatest3Days() {
        //  seckillService.uploadSeckilSku();
        System.out.println("商品在凌晨三点已上架");
    }
}

23.2分布式下定时任务+解决重复上架的问题(幂等性)

在这里插入图片描述

23.3高并发关注问题

在这里插入图片描述

在这里插入图片描述

高并发秒杀流程

在这里插入图片描述

24.Sentinel高并发理论

24.1引入Sentinel

1.导入依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

2.下载sentinel的DashBoard(根据sentinel版本)

https://github.com/alibaba/Sentinel/releases

3.导入SpringBoot的流控中心依赖

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

4.配置文件暴露所有端点

#结合Sentinel 打造流控页面统计信息
management.endpoints.web.exposure.include=*

#配置sentinel 控制台连接端口
spring.cloud.sentinel.transport.dashboard=localhost:8080
#配置sentinel 
spring.cloud.sentinel.transport.port=8719

5.测试请求

启动服务+DashBoard并登录Sentinel

访问请求并后台监控 进行简单的流控 参考周阳SpringCloud

24.2服务熔断降级

具体参考周阳SpringCloud 和 https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

24.3网关流控

25.sleuth+Zipkin服务链路追踪

引入Sleuth

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>

整合zipkin

引入zipkin依赖

    <!--引入了zipkin之后无需引入sleuth 包含-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

配置文件

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shstart7

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

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

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

打赏作者

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

抵扣说明:

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

余额充值