第四阶段微服务

本文详细介绍了Spring Cloud Alibaba微服务架构,包括核心组件Nacos的注册与服务发现、服务调用分析、Feign远程服务调用、配置中心、限流降级策略、Sentinel的使用等。讲解了服务注册、服务调用、负载均衡、服务降级、熔断、Feign的声明式服务调用、配置管理以及Sentinel的限流和降级规则,涉及Spring Cloud Gateway的路由设计和过滤器。最后讨论了单点登录系统的实现,包括JWT令牌的颁发与解析、权限校验等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是微服务:

微服务架构(MSA)是将单个应用程序开发为一组独立服务,在自己的进程中运行,独立开发和部署

在这里插入图片描述

 每个服务都是一个功能,都是独立运行,更新,部署和扩展不会影响到别的服务,

总结:

1.每个服务都有自己独立空间.开发,更新,部署和扩展不会影响的别的服务

2.就是将各个服务之间的共性进行抽取,形成独立的服务.

3.微服务就是一种架构方式,架构中的每个服务(service)都是按照一定的业务设计的,并且都是用来解决问题的.

在这里插入图片描述

 Alibaba 解决方案(掌握)

降级从专业的维度讲,就是熔断,就是当负荷比较大时,关闭一些服务.比方说电流太大,跳闸了,

核心组件分析
Spring Cloud Alibaba 默认提供了如下核心功能(先了解):

服务限流降级:
默认支持 WebServlet、OpenFeign、RestTemplate、Spring Cloud Gateway, RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:
基于Spring Cloud 服务注册与发现标准,借助Nacos进行实现,默认还集成了 Ribbon 的支持。
分布式配置管理:
基于Nacos支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:
基于Spring Cloud Stream 为微服务应用构建消息驱动能力。
分布式事务:
使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。。
分布式任务调度:
提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker上执行。

Nacos注册中心简介

就是如何在不同的服务之间进行通信?如何更好更方便的管理应用中的每一个服务,如何建立各个服务之间联系的纽带,由此注册中心诞生(例如淘宝网卖家提供服务,买家调用服务)。

我们如何进行选型呢?我们主要从社区活跃度,稳定性,功能,性能等方面进行考虑即可

官网:https://nacos.io/zh-cn/docs/quick-start.html

服务注册与调用入门(重点)

1.创建两个项目分别为服务提供者和服务消费者,两者都要注册到NacosServer中,

这个server本质上就是一个web服务,端口默认为8848),然后服务提供者可以为服务消费者提供远端调用服务(例如支付服务为服务提供方,订单服务为服务消费方.

在这里插入图片描述

打包:

1.deploy:在本地库存一份,上传远程仓库.

2.package:生成jar文件.

3.install:生成Jsr包,在本地库存一份

 

解决方案架构设计

基于Spring Cloud Alibaba实现的微服务,

在这里插入图片描述

项目结构分析:(聚合工程)

在这里插入图片描述

总结:

1.父工程主要提供版本和依赖.

2.服务方和提供方是具体业务需求.

3.订单服务消耗商品服务

4.父工程管理所有子工程,消费方通过购买服务来调用提供方,Nacos可以看到每一条信息.

5.每一个单独的服务,都是一组小型程序,通过调用来获取服务

 服务调用分析

在这里插入图片描述

总结:

1.不同的服务有不同的IP地址和端口号

2.前面还有网关还没有讲.统一访问的路口

3.现在是服务带服务要明白.(是内部服务的调用),

4.外部直接访问

负载均衡方式的调用:

方式一:性能好,代码多

   第一步: 服务调用 RestTemplate
    @Bean 
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

        第二步:调用配置文件名字必须保持一致.
        @Value("${spring.application.name}")
        private String appName;

     第三步:   
    @GetMapping("/consumer/doRestEcho2")
        public String doRestEcho2(){
            基于loadBalancerClient方式获取服务实例
            String serviceId="sca-provider";//这个名字要在nacos的服务列表中
            ServiceInstance choose = loadBalancerClient.choose(serviceId);
            String ip=choose.getHost();
            int port=choose.getPort();
            String url=String.format("http://%s:%s/provider/echo/%s",ip,port,appName);
            return restTemplate.getForObject(url,String.class);
        }

@LoadBalanced:

  • @LoadBalanced的作用是什么?(描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略。)

方式二:性能差,代码少

    @Bean
    @LoadBalanced //这个注解描述RestTemplate对象时,系统底层会对RestTemplate对象的请求进行拦截
    public RestTemplate loadBalanceRestTemplate(){
        return new RestTemplate();
    }

        @Autowired
        private RestTemplate loadBalanceRestTemplate;
        //负载均衡客户端对象(基于此对象可以从nacos中获取服务列表,并且可以基于一定的算法
        //从列表中获取一个服务实例)

        //负载均衡应用方式2:@LoadBalance
        @GetMapping("/consumer/doRestEcho3")
        public String doRestEcho3(){
            String serviceId="sca-provider";
            String url=String.format("http://%s/provider/echo/%s",serviceId,appName);
            return loadBalanceRestTemplate.getForObject(url,String.class);
        }

方式三:动态接收

 @GetMapping("/consumer/doRestEcho1")
        public String doRestEcho1(){
//            String url = "http://localhost:8081/provider/echo/"+appName;
//            return restTemplate.getForObject(url,String.class);
            String serverId="sca-provider";
            ServiceInstance choose = loadBalancerClient.choose(serverId);
            String ip=choose.getHost();
            int port = choose.getPort();
           // String url = "http://"+ip+":"+port+"/provider/echo/"+appName;
            String url = String.format("http://%s:%s/provider/echo/%s",ip,port,appName);
            return restTemplate.getForObject(url,String.class);

了解:手动随机负载均衡:

思考题:

创建RestTemplate对象,然后基于此对象进行远程服务调用
 @Bean 注解用于描述方法,用于告诉spring框架这个方法的返回值交给spring管理,
 spring默认会这个方法返回对象起一个bean的名字(key),默认为方法名.当然也可以
 对一个bean的名字进行自定义,例如@Bean("自己定义的名字").
 思考:spring框架中为什么会给出这样的注解,用于描述方法,在方法中构建对象?
 第一:Spring是一个资源整合框架.
 第二:何为资源?(内存中对象)
 第三:所有对象是否都有类型?
 第四:所有对象的类型都是你自己定义的吗?不一定(有自己,有第三方)
 第五:第三方的类,我们能在类上直接添加注解描述吗(例如@Component)?不可以
@Autowired注解描述属性时,会告诉spring框架,要优先按属性类型进行对象的查找和注入,假如
此类型的对象存在多个,此时还会按照属性名进行查找和比对,有相同的则直接注入(DI),没有相同
的则出错,当然也可以在属性上添加@Qualifier("bean的名字")注解,指定要注入的具体对象

注意:以后工作中可能注解中加("msg")是因为兼容JDK1.8之前的版本

 消费方调用提供方的两种方式:一种不带参数, 一种带参数

 总结:

1.写法不友好,不够灵活,太死了.

2.不支持负载均衡

底层:

1.用到的组件HttpClient对象

2.OKHTTP

3.这种服务远程调用的过程进行了一种封装

@Bean

public RestTemplate restTemplate()

{ return new RestTemplate(); }


@Autowired

private RestTemplate restTemplate;

并发运行:

 小节面试分析(Nacos)

1.为什么要将服务注册到nacos?  (因为查找服务方便)

2.在Nacos中服务提供者是如何向Nacos注册中心(Registry)续约的?(5秒心跳)

任务调度.

3.对于Nacos服务来讲它是如何判定服务实例的状态?(检测心跳包,15,30)

3.服务启动时如何找到服务启 动注册配置类?(NacosNamingService)

底层(从这里看)

4.服务消费方是如何调用服务提供方的服务的?(RestTemplate)

小节面试分析

  • @Bean注解的作用?(一般用于配置类内部,描述相关方法,用于告诉spring此方法的返回值要交给spring管理,bean的名字默认为方法名,假如需要指定名字可以@Bean(“bean的名字”),最多的应用场景是整合第三方的资源-对象)
  • @Autowired注解的作用?(此注解用于描述属性,构造方法,set方法等,用于告诉spring框架,按找一定的规则为属性进行DI操作,默认按属性,方法参数类型查找对应的对象,假如只找到一个,则直接注入,类型多个时还会按照属性名或方法参数名进行值的注入,假如名字也不同,就出报错.)
  • Nacos中的负责均衡底层是如何实现的?(通过Ribbon实现,Ribbon中定义了一些负载均衡算法,然后基于这些算法从服务实例中获取一个实例为消费方法提供服务)
  • Ribbon 是什么?(Netflix公司提供的负载均衡客户端,一般应用于服务的消费方法)
  • Ribbon 可以解决什么问题? (基于负载均衡策略进行服务调用, 所有策略都会实现IRule接口)
  • Ribbon 内置的负载策略都有哪些?(8种,可以通过查看IRule接口的实现类进行分析)
  • @LoadBalanced的作用是什么?(描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略。)
  • 我们可以自己定义负载均衡策略吗?(可以,基于IRule接口进行策略定义,也可以参考NacosRule进行实现)

基于Feign的远程服务调用

服务消费方基于rest方式请求服务提供方的服务时,一种直接的方式就是自己拼接url,拼接参数然后实现服务调用,但每次服务调用都需要这样拼接,代码量复杂且不易维护,此时Feign诞生

Feign是什么(一套组件)

Feign 是一种声明式Web服务客户端,底层封装了对Rest技术的应用,通过Feign可以简化服务消费方对远程服务提供方法的调用实现

在这里插入图片描述

 Feign应用实践

1.在消费方,添加项目依赖

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

2.启动类上添加@EnableFeignClients注解,用于告诉springboot在启动时,扫描启动类所在包或子包中的类,假如接口上有@FeignClient注解描述,则对这样的接口创建其实现类,在实现类内部帮我们进行远程服务调用

3.定义Http请求API,基于此API借助OpenFeign访问远端服务,

说明:假如@FeignClient注解中添加了contextId属性,则这个属性值默认
     会作为当前bean对象的名字,此时name的值仅仅作为要调用的服务名对待,一般
     推荐contextId的值默认为@FeignClient注解描述的接口的名字(首字母小写)

@FeignClient(name="sca-provider" ,
             contextId = "remoteProvideService")//sca-provider为服务提供者名称
interface RemoteProviderService{
    //@GetMapping表示以get请求方式调用远端服务
    //"/provider/echo/{msg}"为远程调用服务的url
    @GetMapping("/provider/echo/{string}")//前提是远端需要有这个服务
    public String echoMessage(@PathVariable("string") String string);
}
3.1)@FeignClient(name="sca-provider")//sca-provider为服务提供者名称,

3.2)用于描述远程调用接口.这个接口不需要你写实现类,你只需要定义访问规则即可(例如请求方式,请求url,请求参数).

网络不好使配置 

当我们在进行远程服务调用时,假如调用的服务突然不可用了或者调用过程超时了.

第一步:定义FallbackFactory接口的实现

 第二步:在Feign访问接口中应用FallbackFactory对象,

第三步:在配置文件application.yml中添加如下配置,启动feign方式调用时的服务中断处理机制.

Feign 调用过程分析(了解)

Feign应用过程分析(底层逻辑先了解):
1)通过 @EnableFeignCleints 注解告诉springcloud,启动 Feign Starter 组件。
2) Feign Starter 在项目启动过程中注册全局配置,扫描包下所由@FeignClient注解描述的接口,然后由系统底层创建接口实现类(JDK代理类),并构建类的对象,然后交给spring管理(注册 IOC 容器)。
3) 接口被调用时被动态代理类逻辑拦截,将 @FeignClient 请求信息通过编码器生成 Request对象,基于此对象进行远程过程调用。
4) 请求对象经Ribbon进行负载均衡,挑选出一个健康的 Server 实例(instance)。
5) 通过 Client 携带 Request 调用远端服务返回请求响应。
6) 通过解码器生成 Response 返回客户端,将信息流解析成为接口返回数据。

小节面试分析

  • 为什么使用feign?(基于Feign可以更加友好的实现服务调用,简化服务消费方对服务提供方方法的调用)。
  • @FeignClient注解的作用是什么?(告诉Feign Starter,在项目启动时,为此注解描述的接口创建实现类-代理类)
  • Feign方式的调用,底层负载均衡是如何实现的?(Ribbon)
  • @EnableFeignCleints 注解的作用是什么?(描述配置类,例如启动类)

图解服务调用方案

在这里插入图片描述

这张图描述了远程服务调用的几中方式:
第一种:服务比较少,例如就两个服务,一个服务消费,一个服务提供,就不需要注册中心,不需要负载均衡.
第二种:并发比较大,服务服务比较多,我们需要管理服务,就需要注册中心,我们还需要服务间的负载均衡.但代码编写的复杂多相对高一些,我们需要自己获取ip,获取端口,拼接字符串等.
第三种:我们要基于第二种进行代码简化,底层提供了一种拦截器,把基于服务名获取服务实例的过程在拦截器中做了封装,简化了代码的开发.但是加了拦截器多少会在性能少有一点损耗.
第四种方式主要是从代码结构上做一个挑战,我们前面三种基于RestTemplate进行服务调用,本身属于一种远程服务调用业务,能够将这种业务写到一个业务对象中,Feign方式就诞生了,它主要对代码结构的一种优化.

FAQ分析

  • Nacos是什么,提供了什么特性(服务的注册、发现、配置)?
  • 你为什么会选择Nacos?(活跃度、稳定、性能、学习成本)
  • Nacos的官网?(nacos.io)
  • Nacos在github的源码?(github.com/alibaba/nacos)
  • Nacos在windows环境下安装?(解压即可使用)
  • Nacos在windows中的的初步配置?(application.properties访问数据库的数据源)
  • Nacos服务注册的基本过程?(服务启动时发送web请求)
  • Nacos服务消费的基本过程?(服务启动时获取服务实例,然后调用服务)
  • Nacos服务负载均衡逻辑及设计实现?(Ribbon)
  • 注册中心的核心数据是什么?(服务的名字和它对应的网络地址)
  • 注册中心中心核心数据的存取为什么会采用读写锁?(底层安全和性能)
  • Nacos健康检查的方式?(基于心跳包机制进行实现)
  • Nacos是如何保证高可用的?(重试,本地缓存、集群)
  • Feign是什么,它的应用是怎样的,feign应用过程中的代理对象是如何创建的(JDK)?
  • Feign方式的调用过程,其负载均衡是如何实现?(Ribbon)

 门面设计模式+简单工厂模式:

简单工厂模式:通过类名调用静态方法,通过静态方法返回日志对象.

    创建java中的日志对象(SLF4J是java中的日志规范,是日志对外的窗口,是门面)
    目前市场上对SLF4J规范的实现主要有两种:log4j,logback
    private static final Logger log=
    LoggerFactory.getLogger(ProviderController.class);

配置中心简介:

  • 什么是配置中心?(存储项目配置信息的一个服务)
  • 为什么要使用配置中心?(集中管理配置信息,动态发布配置信息)
  • 市场上有哪些主流的配置中心?(Apollo,nacos,……)

日志级别trace<debug<info<warn<error

           
            log.trace("==log.trace==");//跟踪
            log.debug("==log.debug==");//调试
            log.info("==log.info==");//常规信息
            log.warn("==log.warn==");//警告
            log.error("==log.error==");//错误信息

Nacos配置:

第一步:添加依赖

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

修改配置文件:

将项目中的application.yml的名字修改为bootstrap.yml配置文件(启动优先级最高)

在这里插入图片描述

这个注解描述类时,当配置中心数据发生变化会对属性进行重新初始化

@RefreshScope

小节面试分析:

1.配置中心一般都会配置什么内容?(可能会经常变化的配置信息,例如连接池,日志、线程池、限流熔断规则)
2.什么信息一般不会写到配置中心?(服务端口,服务名,服务的注册地址,配置中心)
3.项目中为什么要定义bootstrap.yml文件?(此文件被读取的优先级比较高,可以在服务启动时读取配置中心的数据)
4.Nacos配置中心宕机了,我们的服务还可以读取到配置信息吗?(可以从内存,客户端获取了配置中心的配置信息以后,会将配置信息在本地内存中存储一份.)
5.微服务应用中我们的客户端如何获取配置中心的信息?(我们的服务一般首先会从内存读取配置信息,同时我们的微服务还可以定时向nacos配置中心发请求拉取(pull)更新的配置信息)

在1.4.2之前这种拉取是长轮循

段轮循:拉取去没有就回来,

长轮循:拉取去等29.5秒,没有就回来,因为30秒在拉取
在这里插入图片描述

6.微服务应用中客户端如何感知配置中心数据变化?(当数据发生变化时,nacos找到它维护的客户端,然后通知客户端去获取更新的数据,客户端获取数据以后更新本地内存,并在下次访问资源时,刷新@Value注解描述的属性值,但是需要借助@RefreshScope注解对属性所在的类进行描述)
服务启动后没有从配置中心获取我们的配置数据是什么原因?(依赖,配置文件名字bootstrap.yml,配置中心的dataId名字是否正确,分组是否正确,配置的名字是否正确,缩进关系是否正确,假如是动态发布,类上是否有@RefreshScope注解)
7.你项目中使用的日志规范是什么?(SLF4J)
8.你了解项目中的日志级别吗?(debug,info,error,…,可以基于日志级别控制日志的输出)
 

Nacos配置管理模型

在这里插入图片描述

在这里插入图片描述

  • Namespace:命名空间,对不同的环境进⾏隔离,⽐如隔离开发环境和⽣产环境。
  • Group:分组,将若⼲个服务或者若⼲个配置集归为⼀组。
  • Service/DataId:某⼀个服务或配置集,一般对应一个配置文件。
 config:    #配置中心的配置
        server-addr: localhost:8848
        file-extension: yml
        访问不一样的命名空间/组/ID
        namespace: 594e1efc-cd0a-43cf-9d35-2b6567d8b122

Nacos配置中心模型

在这里插入图片描述

        享元模式:(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能

        1.池也是减少对象的创建,让对象重性好,都会用这个模式.

Tomcat 请求处理分析

在这里插入图片描述

 分组设计及实现

当我们在指定命名空间下,按环境或服务做好了配置以后,有时还需要基于服务做分组配置,例如,一个服务在不同时间节点(节假日,活动等)切换不同的配置,可以在新建配置时指定分组名称

在这里插入图片描述

 配置发布以后,修改boostrap.yml配置类

 在类中添加属性和方法用于获取和输出DEV_GROUP_51配置中设置的线程数

        @Value("${server.tomcat.threads.max:200}")
        private Integer maxThread;

        @RequestMapping("/provider/doGetMaxThread")
        public String doGetMaxThread(){
            return "server.threads.max is  "+maxThread;
        }

共享配置设计及读取

当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,哪个微服务需要,就在服务的配置中设置读取即可

第一步:在nacos中创建一个共享配置文件

 第二步:在指定的微服务配置文件(bootstrap.yml)中设置对共享配置文件的读取

  第三步:在指定的业务类中读取和应用共享配置即可              

小节面试:

  • Nacos配置管理模型的背景?(环境不同配置不同)
  • Nacos配置中的管理模型是怎样的?(namespace,group,service/data-id)
  • Nacos客户端(微服务)是否可以读取共享配置?(可以)

重难点分析:

  • 配置中心的选型。(市场活跃度、稳定性、性能、易用)
  • Nacos配置中心基本应用。(新建,修改、删除配置以后,在Nacos客户端应用配置)
  • 配置管理模型应用。(namespace,group,service/dataId)
  • Nacos配置变更的动态感知。(底层原理分析)

FAQ分析:

为什么需要配置中心?(动态管理发布配置,无需重启服务,更好保证服务的可用)
配置中一般要配置什么内容?(经常变化的配置数据-日志级别,线程池、连接池、…)
市面上有哪些主流配置中心?(Nacos, ….)
配置中心选型时要重点考虑哪些因素?(市场活跃度、稳定性、性能、易用)
Nacos客户端(微服务业务)如何动态感知配置中心数据变化的?(nacos2.0之前nacos客户端采用长轮询机制每隔30秒拉取nacos配置信息.)
Nacos配置管理模型是怎样的?(命名空间-namespace,分组-group,服务实例-dataId)

线程池任务执行过程

在这里插入图片描述

线程拒绝执行异常

在这里插入图片描述

Sentinel简介

在我们日常生活中,经常会在淘宝、天猫、京东、拼多多等平台上参与商品的秒杀、抢购以及一些优惠活动,我们如何在这些业务流量变化无常的情况下,保证各种业务安全运营,系统在任何情况下都不会崩溃呢?我们可以在系统负载过高时,采用限流、降级和熔断,三种措施来保护系统,由此一些流量控制中间件诞生。例如Sentinel。

Sentinel概述

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。

Sentinel核心分为两个部分:

  • 核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。

安装Sentinel服务

Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能,

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

Sentinel限流

概述

我们系统中的数据库连接池,线程池,nginx的瞬时并发,MQ消息等在使用时都会跟定一个限定的值,这本身就是一种限流的设计。限流的目的防止恶意请求流量、恶意攻击,或者防止流量超过系统峰值。

第一步:Sentinel 应用于服务消费方(Consumer),在消费方添加依赖,(在哪添加依赖就对谁进行限流)

        <!--限流依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--Spring Boot中的监控依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

第二步:打开服务消费方配置文件application.yml,添加sentinel配置

spring:
  cloud:
    sentinel:
      transport:
      #  port: 8099 #跟sentinel控制台交流的端口,随意指定一个未使用的端口即可,可以不写,默认8719
         dashboard: localhost:8180 # 指定sentinel控制台地址。

回顾Spring MVC 请求处理

Spring MVC 是spring框架中基于MVC设计思想实现的一个WEB模块,这个模块下的请求响应处理流程如下:

在这里插入图片描述

 其中:
第一:客户端向web服务(例如tomcat)发起请求。
第二:tomcat会调用Filter对请求进行预处理(例如请求编码处理,请求认证分析等)。
第三:请求经过滤器Filter处理过后会交给DispatcherServlet对象(负责资源调度,前端控制器),此对象基于url找到对应的请求处理链对象(HandlerExecutionChain)。
第四:DispatcherServlet获取了请求执行链之后,会首先调用请求执行链中拦截器(HandlerInterceptor)对象(这个对象会在@RestController之前执行).
第五:拦截器对象获取请求以后可以对请求先进行分析,例如记录请求次数,请求时间,然后控制对后端Controller的调用。
第六:拦截器允许请求去传递到Controller时,Controller对象会对请求进行处理,然后将处理结果还会交给MVC 拦截器。
第七:拦截器拿到响应结果以后对其进行分析处理(例如记录Controller方法执行结束的时间)
第八:拦截器将响应结果传递到DispatcherServlet对象。
第九:DispatcherServlet拿到响应结果以后,会基于响应数据的类型,调用相关处理器(Processer)进行处理。
第十:响应结果处理器对象对响应数据处理以后,会将其结果交给DispatcherServlet对象。
第十一:DispatcherServlet对象拿到响应数据的处理结果时,会将结果基于ServletResponse对象响应到客户端。
 

Sentinel 请求拦截分析

Sentinel对请求进行限流的原理分析

在这里插入图片描述

 当我们在服务中添加了Sentinel依赖以后,Sentinel会为我们的服务提供一个SpringMVC拦截器,这个拦截器会对请求进行拦截,然后基于请求url获取sentinel控制台中设置好的流控规则,然后采用一定的算法对请求url要访问的资源进行流量限制。

Sentinel流控规则

阈值类型分析

  • QPS(Queries Per Second):当调用相关url对应的资源时,QPS达到单机阈值时,就会限流。

  • 线程数:当调用相关url对应的资源时,线程数达到单机阈值时,就会限流。

限流模式

核心思想:保证核心业务的执行,当核心业务负载大时,把非核心业务限制掉.

Sentinel的流控模式代表的流控的方式,默认【直接】,还有关联,链路。

流控处理就是【直接->快速失败】。

关联模式

当关联的资源达到阈值,就限流自己。例如设置了关联资源为/ur2时,假如关联资源/url2的qps阀值超过1时,就限流/url1接口(是不是感觉很霸道,关联资源达到阀值,是本资源接口被限流了)订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。那就可以利用关联模式;在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求
在这里插入图片描述

链路模式

链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的.

    @Service
    public class ConsumerService{
    @SentinelResource("doGetResource")
    public String doGetResource(){
        return "doGetResource";
    }
    }

接下来我们在/consumer/doRestEcho1对应的方法中对ConsumerService中的doGetResource方法进行调用(应用consumerService对象之前,要先在doRestEcho01方法所在的类中进行consumerService值的注入)

consumerService.doGetResource();

路由规则配置:

在这里插入图片描述

在这里插入图片描述

 这就是被限流:

需要在application.yml添加如下语句来关闭URL PATH聚合

 取消链路聚合:取消链路聚合,否则sentinel1.7.2以后会默认将所有链路聚合到一个入口上

    sentinel:
     web-context-unify: false

 小节面试

Sentinel是什么?(阿里推出一个流量控制平台,防卫兵)
类似Sentinel的产品你知道有什么?(hystrix-一代微服务产品)
你了解哪些限流算法?(计数器、令牌桶、漏斗算法,滑动窗口算法,…)
Sentinel 默认的限流算法是什么?(滑动窗口算法)
你了解sentinel中的阈值应用类型吗?(两种-QPS,线程数)
Sentinel 限流规则中默认有哪些限流模式?(直连,关联,链路)
Sentinel的限流效果有哪些?(快速失败,预热,排队)
Sentinel 为什么可以对我们的业务进行限流,原理是什么?
我们在访问web应用时,在web应用内部会有一个拦截器,这个拦截器会对请求的url进行拦截,拦截到请求以后,读取sentinel 控制台推送到web应用的流控规则,基于流控规则对流量进行限流操作。


Sentinel降级

概述

熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

 第一步:服务启动后,选择要降级的链路

 在这里插入图片描述

第二步:选择要降级的链路 

在这里插入图片描述

这里表示熔断策略为慢调用比例,表示链路请求数超过3时,假如平均响应时间假如超过200毫秒的有50%,则对请求进行熔断,熔断时长为10秒钟,10秒以后恢复正常。 

第三步:对指定链路进行刷新,多次访问测试,假如出现了降级熔断,会出现如下结果:

在这里插入图片描述

Sentinel 异常处理

系统提供了默认的异常处理机制,假如默认处理机制不满足我们需求,我们可以自己进行定义。定义方式上可以直接或间接实现BlockExceptionHandler接口,并将对象交给spring管理。

在这里插入图片描述

    @Component
    public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse             
    response,BlockException e) throws Exception {
         //response.setStatus(601);
         //设置响应数据的编码
         response.setCharacterEncoding("utf-8");
         //告诉客户端要响应的数据类型以及客户端以什么编码呈现数据
         response.setContentType("text/html;charset=utf-8");
         PrintWriter pw=response.getWriter();
         Map<String,Object> map=new HashMap<>();
         if(e instanceof DegradeException){//降级、熔断
             map.put("status",601);
             map.put("message", "服务被熔断了!");
         }else if(e instanceof FlowException){
             map.put("status",602);
             map.put("message", "服务被限流了!");
         }else{
             map.put("status",603);
             map.put("message", "Blocked by Sentinel (flow limiting)");
         }
         //将map对象转换为json格式字符串
         String jsonStr=new ObjectMapper().writeValueAsString(map);
         pw.println(jsonStr);
         pw.flush();
    }
}

小节面试分析


何为降级熔断?(让外部应用停止对服务的访问,生活中跳闸,路障设置-此路不通)
为什么要进行熔断呢?(平均响应速度越来越慢或经常出现异常,这样可能会导致调用链堆积,最终系统崩溃)
Sentinel中限流,降级的异常父类是谁?(BlockException)
Sentinel 出现降级熔断时,系统底层抛出的异常是谁?(DegradeException)
Sentinel中异常处理接口是谁?(BlockExceptionHandler)
Sentinel中异常处理接口下默认的实现类为? (DefaultBlockExceptionHandler)
假如Sentinel中默认的异常处理规则不满足我们的需求怎么办?(自己定义)
我们如何自己定义Sentinel中异常处理呢?(直接或间接实现BlockExceptionHandler )

Sentinel降级策略分析:


Sentinel 降级熔断策略有哪些?(慢调用,异常比例,异常数)
Sentinel 熔断处理逻辑中的有哪些状态?(Open,HalfOpen,Closed)
Sentinel 对服务调用进行熔断以后处于什么状态?(熔断打开状态-Open)
Sentinel 设置的熔断时长到期以后,Sentinel的熔断会处于什么状态?(探测-HalfOpen,假如再次访问时依旧响应时间比较长或依旧有异常,则继续熔断)
Sentinel 中的熔断逻辑恢复正常调用以后,会出现什么状态?(熔断关闭-closed)

Idea中启动Nacos

在这里插入图片描述

 在这里插入图片描述

 在这里插入图片描述

Sentinel热点规则分析

 何为热点?热点即经常访问的数据。

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。
  • 其中,Sentinel会利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

定义热点业务代码,

        @GetMapping("/consumer/findById")
        @SentinelResource("res")
        public String doFindById(@RequestParam Integer id){
            return "select by" + id;
        }

    @RequestParam注解作用:
    获取URL中携带的请求参数的值既URL中“?”后携带的参数,传递参数的格式是:key=value
    如: https://localhost/requestParam/test?key1=value1&key2=value2...

    @PathVariable注解作用:
    用于获取URL中路径的参数值,参数名由RequestMapping注解请求路径时指定,常用于restful风格的api            
    中,传递参数格式:直接在url后添加需要传递的值即可
    如: https://localhost/pathVariable/test/value1/value2...

服务启动后,选择要限流的热点链路

在这里插入图片描述

 设置要限流的热点

在这里插入图片描述

 热点规则的限流模式只有QPS模式(这才叫热点)。参数索引为@SentinelResource注解的方法参数下标,0代表第一个参数,1代表第二个参数。单机阈值以及统计窗口时长表示在此窗口时间超过阈值就限流。

 在后台出现如下异常表示限流成功。

小节面试:

如何理解热点数据?(访问频度比较高的数据,某些商品、谋篇文章、某个视频)
热点数据的限流规则是怎样的?(主要是针对参数进行限流设计)
热点数据中的特殊参数如何理解?(热点限流中的某个参数值的阈值设计)
对于热点数据的访问出现限流以后底层异常是什么?(ParamFlowException)


Sentinel系统规则

系统在生产环境运行过程中,我们经常需要监控服务器的状态,看服务器CPU、内存、IO等的使用率;主要目的就是保证服务器正常的运行,不能被某些应用搞崩溃了;而且在保证稳定的前提下,保持系统的最大吞吐量。
长期以来,系统自适应保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。
 

入门

Sentinel的系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、线程数和CPU使用率五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性

在这里插入图片描述

Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
CPU使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务。
 

小节面试分析

如何理解sentinel中的系统规则?(是对所有链路的控制规则,是一种系统保护策略)
Sentinel的常用系统规则有哪些?(RT,QPS,CPU,线程,Load-linux,unix)
Sentinel系统保护规则被触发以后底层会抛出什么异常?(SystemBlockException)
 

Sentinel授权规则(重要)

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。例如微信中的黑名单。
在这里插入图片描述

黑白名单规则(AuthorityRule):

  • 资源名:即限流规则的作用对象
  • 流控应用:对应的黑名单/白名单中设置的规则值,多个值用逗号隔开.
  • 授权类型:白名单,黑名单(不允许访问).

定义请求解析器,用于对请求进行解析,并返回解析结果,sentinel底层 在拦截到用户请求以后,会对请求数据基于此对象进行解析,判定是否符合黑白名单规则

        @Component
        public class DefaultRequestOriginParser
            implements RequestOriginParser {
    这个方法会在资源访问时,由底层进行调用,获取请求中数据,再与我们指定的授权规则进行比对
    @Override
    public String parseOrigin(HttpServletRequest request) {
       return request.getParameter("origin");
    }
}

定义流控规则,

在这里插入图片描述

:执行资源访问,检测授权规则应用,当我们配置的流控应用值为黑名单

在这里插入图片描述

设计过程分析

在这里插入图片描述

 小节面试

如何理解Sentinel中的授权规则?(对指定资源的访问给出的一种简易的授权策略)
Sentinel的授权规则是如何设计的?(白名单和黑名单)
如何理解Sentinel中的白名单?(允许访问的资源名单)
如何理解Sentinel中的黑名单?(不允许访问的资源名单)、
Sentinel如何识别白名单和黑名单?(在拦截器中通过调用RequestOriginParser对象的方法检测具体的规则)
授权规则中RequestOriginParser类的做用是什么?(对流控应用值进行解析,检查服务访问时传入的值是否与RequestOriginParser的parseOrigin方法返回值是否相同。)

总结(Summary)

总之,Sentinel可为秒杀、抢购、抢票、拉票等高并发应用,提供API接口层面的流量限制,让突然暴涨而来的流量用户访问受到统一的管控,使用合理的流量放行规则使得用户都能正常得到服务。

重难点分析
Sentinel诞生的背景?(计算机的数量是否有限,处理能力是否有限,并发比较大或突发流量比较大)
服务中Sentinel环境的集成,初始化?(添加依赖-两个,sentinel配置)
Sentinel 的限流规则?(阈值类型-QPS&线程数,限流模式-直接,关联,链路)
Sentinel 的降级(熔断)策略?(慢调用,异常比例,异常数)
Sentinel 的热点规则设计(掌握)?
Sentinel 系统规则设计?(了解,全局规则定义,针对所有请求有效)
Sentinel 授权规则设计?(掌握,黑白名单)


FAQ分析
为什么要限流?
你了解的那些限流框架?(sentinel)
常用的限流算法有那些?(计数,令牌桶-电影票,漏桶-漏斗,滑动窗口)
Sentinel有哪些限流规则?(QPS,线程数)
Sentinel有哪些限流模式?(直接,关联-创建订单和查询订单,链路限流-北京六环外不限号,但是五环就限号)
Sentinel 的降级(熔断)策略有哪些?(慢调用-响应时长,异常比例-异常占比,异常数)
Sentinel 的热点规则中的热点数据?(热卖商品,微博大咖,新上映的电影)
如何理解Sentinel 授权规则中的黑白名单?
Bug分析
依赖下载失败 (maven-本地库,网络,镜像仓库)
单词错误(拼写错误)
 

网关Gateway:

一个大型系统在设计时,经常会被拆分为很多个微服务。客户端可以直接向微服务发送请求,每个微服务都有一个公开的URL,该URL可以直接映射到具体的微服务,如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。会存在着诸多的问题,例如,客户端请求不同的微服务可能会增加客户端代码或配置的复杂性。还有就是每个服务,在调用时都需要独立认证。并且存在跨域请求,也在一定程度上提高了代码的复杂度。基于微服务架构中的设计及实现上的问题,为了在项目中简化前端的调用逻辑,同时也简化内部服务之间互相调用的复杂度,更好保护内部服务,提出了网关的概念。

网关概述

网关本质上要提供一个各种服务访问的入口,并提供服务接收并转发所有内外部的客户端调用,还有就是权限认证,限流控制等等。Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 等技术开发的一个网关组件,它旨在为微服务架构提供一种简单有效的统一的 API入口,负责服务请求路由、组合及协议转换,并且基于 Filter 链的方式提供了权限认证,监控、限流等功能。
 

Spring Cloud Gateway优缺点分析:

优点:
性能强劲:是第一代网关Zuul的1.6倍。
功能强大:内置了很多实用的功能,例如转发、监控、限流等
设计优雅,容易扩展。
缺点:
依赖Netty与WebFlux(Spring5.0),不是传统的Servlet编程模型(Spring MVC就是基于此模型实现),学习成本高。
需要Spring Boot 2.0及以上的版本,才支持

通过网关作为服务访问入口,对系统中的服务进行访问,

创建sca-gateway模块,其pom.xml文件

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

添加相关配置

server:
  port: 9777
spring:
  application:
    name: sca-gateway
  cloud:
    gateway:
      routes: #网关路由配置
         - id: router01
           uri: http://localhost:8081
           predicates: #定义请求规则(请求需要按照此规则设计)
             - Path=/nacos/provider/echo/** #请求路径设计
           filters:
             - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos

其中:路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:

id,路由标识符,区别于其他 Route。
uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
predicate,断言(谓词)的作用是进行条件判断,只有断言都返回真,才会执行路由。
filter,过滤器用于修改请求和响应信息。
 

小节面试分析?


什么是网关?服务访问(流量)的一个入口,类似生活中的“海关“
为什么使用网关?(服务安全,统一服务入口管理,负载均衡,限流,鉴权)
Spring Cloud Gateway 应用的初始构建过程(添加依赖,配置)
Gateway 服务的启动底层是通过谁去实现的?(Netty网络编程框架-ServerSocket)
Gateway 服务做请求转发时一定要在注册中心进行注册吗?(不一定,可以直接通过远端url进行服务访问)

负载均衡设计
网关才是服务访问的入口,所有服务都会在网关层面进行底层映射,所以在访问服务时,要基于服务serivce id(服务名)去查找对应的服务,让请求从网关层进行均衡转发,以平衡服务实例的处理能力。














为什么负载均衡

网关才是服务访问的入口,所有服务都会在网关层面进行底层映射,所以在访问服务时,要基于服务serivce id(服务名)去查找对应的服务,让请求从网关层进行均衡转发,以平衡服务实例的处理能力。

Gateway中负载均衡实现

项目中添加服务发现依赖

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

修改配置文件:

server:
  port: 9000
spring:
  application:
    name: sca-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true  #开启基于服务名获取服务实例的功能(基于服务名创建路由)
      routes:
        - id: route01
          ##uri: http://localhost:8081/ 
          uri: lb://sca-provider # lb为服务前缀,不能随意写
          predicates: ###匹配规则
              - Path=/nacos/provider/echo/**
          filters:
              - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos

网关负载均衡调用流程分析

在这里插入图片描述执行流程分析

根据官方的说明,其Gateway具体工作流程

在这里插入图片描述

 客户端向Spring Cloud Gateway发出请求。 如果Gateway Handler Mapping 通过断言predicates(predicates)的集合确定请求与路由(Routers)匹配,则将其发送到Gateway Web Handler。 Gateway Web Handler 通过确定的路由中所配置的过滤器集合链式调用过滤器(也就是所谓的责任链模式)。 Filter由虚线分隔的原因是, Filter可以在发送代理请求之前和之后运行逻辑。处理的逻辑是 在处理请求时 排在前面的过滤器先执行,而处理返回相应的时候,排在后面的过滤器先执行。


小节面试分析
网关层面是如何实现负载均衡的?(通过服务名去查找具体的服务实例)
网关层面是如何通过服务名查找服务实例的?(Ribbon)
你了解Ribbon中的哪些负载均衡算法?(轮询,权重,hash,……可通过IRule接口进行查看分析)
网关进行请求转发的流程是怎样,有哪些关键对象?(XxxHandlerMapping,Handler,。。。)
网关层面服务的映射方式怎样的?(谓词-path,…,服务名/服务实例)
网关层如何记录服务的映射?(通过map,并要考虑读写锁的应用)

断言(Predicate)增强分析

Predicate(断言)又称谓词,用于条件判断,只有断言结果都为真,才会真正的执行路由。断言其本质就是定义路由转发的条件。

Predicate 内置工厂

SpringCloud Gateway包括一些内置的断言工厂,所有这些断言都与http请求的不同属性相匹配

基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

1) AfterRoutePredicateFactory:判断请求日期是否晚于指定日期
2) BeforeRoutePredicateFactory:判断请求日期是否早于指定日期
3) BetweenRoutePredicateFactory:判断请求日期是否在指定时间段内

-After=2020-12-31T23:59:59.789+08:00[Asia/Shanghai]

当且仅当请求时的时间After配置的时间时,才转发该请求,若请求时的时间不是After配置的时间时,则会返回404 not found。时间值可通过ZonedDateTime.now()获取。

基于header的断言工厂HeaderRoutePredicateFactory

判断请求Header是否具有给定名称且值与正则表达式匹配。

-Header=X-Request-Id, \d+

基于Method请求方法的断言工厂,

MethodRoutePredicateFactory接收一个参数,判断请求类型是否跟指定的类型匹配。例如:

-Method=GET

基于Query请求参数的断言工厂,QueryRoutePredicateFactory :

接收两个参数,请求param和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。例如:

-Query=pageSize,\d+

小节面试分析

  • 何为谓词?(网关中封装了判断逻辑的一个对象)
  • 谓词逻辑的设计是怎样的?(谓词判断逻辑返回值为true则进行请求转发)
  • 你了解哪些谓词逻辑?(path,请求参数,ip,请求方式,cookie,请求头,….)
  • 我们可以自己定义谓词工厂对象吗?(可以的)

添加gateway依赖:假如依赖下载不下来:

1)保证网络没有问题(手机网络,公司网络)

2)保证maven配置正确(maven软件,远程仓库)

3)本地库(本地库中尽量不要存在同一个资源的多个版本)

Postman请求携带请求头

在这里插入图片描述

过滤器(Filter)增强分析

概述
过滤器(Filter)就是在请求传递过程中,对请求和响应做一个处理。Gateway 的Filter从作用范围可分为两种:GatewayFilter与GlobalFilter。其中:

GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
 

基于AddRequestHeaderGatewayFilterFactory,为原始请求添加Header。

为原始请求添加名为 X-Request-Foo

- AddRequestHeader=X-Request-Foo, Bar

基于AddRequestParameterGatewayFilterFactory,为原始请求添加请求参数及值,

为原始请求添加名为foo,值为bar的参数,即:foo=bar。

- AddRequestParameter=foo, bar

基于PrefixPathGatewayFilterFactory,为原始的请求路径添加一个前缀路径

该配置使访问${GATEWAY_URL}/hello 会转发到uri/mypath/hello

  - PrefixPath=/mypath

基于RequestSizeGatewayFilterFactory,设置允许接收最大请求包的大小

  predicates:
        - Path=/upload
      filters:
        - name: RequestSize
          args:
            # 单位为字节
            maxSize: 5000000
如果请求包大小超过设置的值,则会返回 413 Payload Too Large以及一个errorMessage

全局过滤器设计及实现

第一步 配置中心
#自己定义白名单路径
white:
  prefix: /nacos
第二步 自定义的全局过滤器,作用域所有路由
 @Value("${white.prefix}")
    private String whitePrefix;
第三步  //例如判定请求的path是否在我们允许的范围之内
  System.out.println("whitePrefix="+whitePrefix);
        if (!path.startsWith(whitePrefix)){
            //获取响应对象
            ServerHttpResponse response = exchange.getResponse();
            //设置响应状态码,这里的值为502
            response.setStatusCode(HttpStatus.BAD_GATEWAY);
            //结束当前请求
            //return exchange.getResponse().setComplete();直接结束.
            String content = "request failure";
            byte[] bytes = content.getBytes();
            DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(dataBuffer));
        }
        //4.返回响应结果
        return chain.filter(exchange);//将请求交给下一个过滤器进行处理
    }

依赖:

<!--拓展:基于如下依赖中的API可以实现对象与json字符串之间转换-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

输出JS串

 if (!path.startsWith(whitePrefix)){
            //获取响应对象
            ServerHttpResponse response = exchange.getResponse();
            //设置响应状态码,这里的值为502
            response.setStatusCode(HttpStatus.BAD_GATEWAY);
            //结束当前请求
            //return exchange.getResponse().setComplete();直接结束.
            //假如希望响应一些具体内容到客户端
            //String content = "request failure";
            //byte[] bytes = content.getBytes();
            Map<String,Object> map = new HashMap<String, Object>();
            map.put("message","request failure");
            map.put("status",502);
            //将map对象转换为json字符串?
            //String jsonStr="{\"message\":\"request failure\",\"status\":502}";
            Gson gson = new Gson();
            String jsonStr = gson.toJson(map);//将map对象转换json格式字符串.
            byte[] bytes = jsonStr.getBytes();
            DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(dataBuffer));
        }
        //4.返回响应结果
        return chain.filter(exchange);//将请求交给下一个过滤器进行处理

回家练习:将map对象转换为json字符串?

            Map<String,Object> map = new HashMap<String, Object>();
            map.put("message","request failure");
            map.put("status",502);
            //将map对象转换为json字符串?
            //String jsonStr="{\"message\":\"request failure\",\"status\":502}";
            Gson gson = new Gson();
            String jsonStr = gson.toJson(map);//将map对象转换json格式字符串.
            byte[] bytes = jsonStr.getBytes();

小节面试分析

网关过滤器的作用是什么?(对请求和响应数据做一个预处理)
网关过滤器的类型有哪些?(局部过滤器,全局过滤器)
如何理解局部过滤器?(针对具体链路的应用的过滤器,需要进行配置)
你了解哪些局部过滤器?
如何理解全局过滤器?(作用于所有请求链路)
如何自己定义全局过滤器?(直接或间接实现GlobalFilter接口)
假如现在让你进行平台的网关自研设计,你可以吗?(可以)

Debug调试

在这里插入图片描述

http Client 应用

在这里插入图片描述

在Springwbe模块中注册一个过滤器对象: 

@Bean
public FilterRegistrationBean<CorsFilter>
        filterFilterRegistrationBean()

跨域配置:

/**
 * 跨域配置(基于过滤器方式进行配置,并且将过滤优先级设置高一些)
 */
@Configuration
public class CorsFilterConfig {
    @Bean
    public FilterRegistrationBean<CorsFilter>
            filterFilterRegistrationBean(){
        //1.对此过滤器进行配置(跨域设置-url,method)
        UrlBasedCorsConfigurationSource configSource=
                new UrlBasedCorsConfigurationSource();
        //2.构建url请求规则配置
        CorsConfiguration config = new CorsConfiguration();
        //2.1//允许所有请求头跨域
        config.addAllowedHeader("*");
        //2.2允许哪种方法类型跨域 get post delete put
        config.addAllowedMethod("*");
        //2.3允许哪些请求源(ip:port)跨域
        config.addAllowedOrigin("*");
        //2.4是否允许携带cookie跨域
        config.setAllowCredentials(true);
        //2.5将这个跨越配置应用到具体的url
        configSource.registerCorsConfiguration("/**",config);
        //3注册过滤器
        FilterRegistrationBean fBean=
                new FilterRegistrationBean(new CorsFilter(configSource));
        //设置其优先级
        fBean.setOrder(Integer.MIN_VALUE);
        return null;
    }

跨域配置:配置文件bootstrap.yml

server:
  port: 9000
spring:
  application:
    name: sca-resource-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: router01
          uri: lb://sca-resource
          predicates:
            - Path=/sca/resource/upload/**
          filters:
            - StripPrefix=1
      globalcors: #跨域配置
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
            allowCredentials: true

文件上传基本架构

在这里插入图片描述

 限流设计及实现

限流简述

网关是所有请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们采用Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进行限流。参考网址如下:

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

第一步:添加依赖

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

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

第二步:添加sentinel及路由规则

routes:
  - id: route01
    uri: lb://nacos-provider
    predicates: ###匹配规则
      - Path=/provider/echo/**
sentinel:
  transport:
    dashboard: localhost:8180 #Sentinel 控制台地址
    port: 8719 #客户端监控API的端口
  eager: true  #取消Sentinel控制台懒加载,即项目启动即连接

第三步:启动网关项目,检测sentinel控制台的网关菜单。
启动时,添加sentinel的jvm参数,通过此菜单可以让网关服务在sentinel控制台显示不一样的菜单,

-Dcsp.sentinel.app.type=1

在这里插入图片描述

Sentinel 控制台启动以后, 

在这里插入图片描述

 第四步:在sentinel面板中设置限流策略

在这里插入图片描述

 第五步:通过url进行访问检测是否实现了限流操作

在这里插入图片描述

基于请求属性限流

 定义指定routeId的基于属性的限流策略

在这里插入图片描述

 通过postman进行测试分析

在这里插入图片描述

自定义API维度限流(重点)

自定义API分组,是一种更细粒度的限流规则定义,它允许我们利用sentinel提供的API,将请求路径进行分组,然后在组上设置限流规则即可。

第一步:新建API分组

在这里插入图片描述

 第二步:新建分组流控规则

在这里插入图片描述

定制流控网关返回值

@Configuration
public class GatewayConfig {
    public GatewayConfig(){
        //自定义限流结果
        GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(
                    ServerWebExchange serverWebExchange,
                    Throwable throwable) {
                //构建响应数据
                Map<String,Object> map = new HashMap<>();
                map.put("message","request failure");
                map.put("status",429);
                Gson gson = new Gson();
                String jsonStr = gson.toJson(map);
                //创建Mono对象,将结果响应到客户端
                return ServerResponse.ok().body(Mono.just(jsonStr),
                        String.class);//String.class表示响应数据类型
            }
        });

小节面试分析:

  • 网关层面结合sentinel实现限流,其限流的类型有几种?(两种-route id,api)
  • 网关层面可以自定义限流后的异常处理结果吗?(可以)
  • 你知道Sentinel底层限流的算法有哪些?(滑动窗口,令牌桶,漏斗,。。。)

总结(Summay)

























重难点分析

网关(Gateway)诞生的背景?(第一:统一微服务访问的入口,第二:对系统服务进行保护,第三进行统一的认证,授权,限流)
网关的选型?(Netifix Zuul,Spring Cloud Gateway,…)
Spring Cloud Gateway的入门实现(添加依赖,路由配置,启动类)
Spring Cloud Gateway中的负载均衡?(网关服务注册,服务的发现,基于uri:lb://服务id方式访问具体服务实例)
Spring Cloud Gateway中的断言配置?(掌握常用几个就可,用时可以通过搜索引擎去查)
Spring Cloud Gateway中的过滤器配置?(掌握过滤器中的两大类型-局部和全局)
Spring Cloud Gateway中的限流设计?(Sentinel)
 

FAQ 分析

Gateway在互联网架构中的位置?(nginx->gateway–>微服务–>微服务)
Gateway底层负载均衡的实现?(Ribbon)
Gateway应用过程中设计的主要概念?(路由id,路由uri,断言,过滤器)
Gateway中你做过哪些断言配置?(after,header,path,cookie,…)
Gateway中你用的过滤器有哪些?(添加前缀,去掉前缀,添加请求头,…,负载均衡,…)
 

BUG分析

  • 503 异常?(服务不可用,检查你调用的服务是否启动ok,路由uri写的是否正确)
  • 启动时解析.yml配置文件异常(格式没有对齐,单词写错)

看见BodyBuilder就想建造模式:

1.用来创建一些复杂对象时候,

2.对象内容可能经常变化的时候,

网关执行流程

在这里插入图片描述

自定义执行链设计

在这里插入图片描述

代码如下:

/**拦截器*/
interface Interceptor{
    boolean doPre();
    void doPost();
}
/**处理器*/
interface Handler{
    void doHandle();
}
/**
 * 执行链设计(所有项目中的执行(Execution)链(Chain)都是预先设计好的)
 */
class ExecutionChain{
    //拦截器是边缘业务
    private List<Interceptor> interceptors;
    //处理器是核心业务
    private Handler handler;
    public ExecutionChain(List<Interceptor> interceptors, Handler handler) {
        this.interceptors = interceptors;
        this.handler = handler;
    }

    //执行业务逻辑
    public void execute(){//proceed()
        //1.执行拦截器(Interceptor)中的doPre方法
        for(int i=0;i<interceptors.size();i++)
            interceptors.get(i).doPre();
        //2.执行处理器(Handler)中的doHandle方法
        handler.doHandle();
        //3.执行拦截器(Interceptor)中的doPost方法
        for(int i=interceptors.size()-1;i>=0;i--)
            interceptors.get(i).doPost();
    }

}
public class ExecutionChainTests {
    public static void main(String[] args) {
        List<Interceptor> interceptors=new ArrayList<>();
        interceptors.add(new Interceptor() {
            @Override
            public boolean doPre() {
                System.out.println("doPre1");
                return false;
            }

            @Override
            public void doPost() {
                System.out.println("doPost1");
            }
        });
        interceptors.add(new Interceptor() {
            @Override
            public boolean doPre() {
                System.out.println("doPre2");
                return false;
            }

            @Override
            public void doPost() {
                System.out.println("doPost2");
            }
        });
        Handler handler=()-> {
            System.out.println("执行任务...");
        };
        ExecutionChain chain=new ExecutionChain(interceptors,handler);
        chain.execute();
    }

}

Spring MVC 拦截器

Spring Web中的拦截器(Interceptor)基于回调机制,可以在目标方法执行之前,先进行业务检测,满足条件则放行,不满足条件则进行拦截,拦截器原理分析

 在这里插入图片描述

Spring框架生态设计

在这里插入图片描述

 AOP概述

AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行).
在这里插入图片描述

 AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。

实现原理

AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象

在这里插入图片描述

其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):

第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.

相关术语分析

切面(aspect): 横切面对象,一般为一个具体类对象。
切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
通知(Advice): 内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个特定位置上执行的动作(扩展功能)。
连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法。连接点与切入点定义如图所示:


说明:我们可以简单的将机场的一个安检口理解为连接点,多个安检口为切入点,安全检查过程看成是 通知。总之,概念很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解。
 

添加AOP依赖

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

说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了 AOP 的一些语法,有一个专门的字节码生成器来生成遵守 java 规范的 class 文件。

JVM 参数说明

输出JVM类加载信息(假如想看类启动时,相关类的加载顺序,
可以配置JVM参数: -XX:+TraceClassLoading)

在这里插入图片描述

 调整JVM堆内存大小,并输出JVM 垃圾回收基本信息(假如设置JVM堆内存大小可以通过 -Xmx设置最大堆内存,-Xms设置最小堆内存,-XX:+PrintGC输出程序运行时的GC信息)

在这里插入图片描述

AOP 执行流程分析

在这里插入图片描述

说明:当我们在项目中定义了AOP切面以后,系统启动时,会对有@Aspect注解描述的类进行加载分析,基于切入点的描述为目标类型对象,创建代理对象,并在代理对象内部创建一个执行链,这个执行链中包含拦截器(封装了切入点信息),通知(Around,…),目标对象等,我们请求目标对象资源时,会直接按执行链的顺序对资源进行调用。

微服务版的单点登陆系统设计

传统的登录系统中,每个站点都实现了自己的专用登录模块。各站点的登录状态相互不认可,各站点需要逐一手工登录。

在这里插入图片描述

 这样的系统,我们又称之为多点登陆系统。应用起来相对繁琐(每次访问资源服务都需要重新登陆认证和授权)。与此同时,系统代码的重复也比较高。由此单点登陆系统诞生。

单点登陆系统

单点登录,英文是 Single Sign On(缩写为 SSO)。即多个站点共用一台认证授权服务器,用户在其中任何一个站点登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互
在这里插入图片描述

添加项目依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
                        
        微服务单点登录
        <!--oauth2依赖 oauth2定义了一种认证授权协议,一种规范,此规范中定义了四种类型的角色:
        1)资源有者(User)
        2)认证授权服务器(jt-auth)
        3)资源服务器(jt-resource)
        4)客户端应用(jt-ui)
        同时,在这种协议中规定了认证授权时的几种模式:
        1)密码模式 (基于用户名和密码进行认证)
        2)授权码模式(就是我们说的三方认证:QQ,微信,微博,。。。。)
        3)...
        -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

单元测试依赖:

     <dependencies>
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <!--定义要排除的依赖-->
            <exclusions>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

测试项目:

@SpringBootTest
public class PasswordTests {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    //测试密码加密
    @Test
    void testEncode(){
        //1.定义密码
        String rawPassword="123456";
        //2.对密码进行加密
        String encodePassword = passwordEncoder.encode(rawPassword);
        System.out.println(encodePassword);
        //$2a$10$dODKIuybIwids30A2C/A9.FBFHh4YXBZmKQmU6keaFkzdYf1qABkK
        //3.检测密码
        boolean flag=
                passwordEncoder.matches("123456",encodePassword);
        System.out.println("flag="+flag);
    }

自定义登陆逻辑

我们的单点登录系统最终会按照如下结构进行设计和实现

在这里插入图片描述

 我们在实现登录时,会在UI工程中,定义登录页面(login.html),然后在页面中输入自己的登陆账号,登陆密码,将请求提交给网关,然后网关将请求转发到auth工程,登陆成功和失败要返回json数据,在这个章节我们会按这个业务逐步进行实现

定义安全配置类

package com.jt.auth.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**初始化密码加密对象*/
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**在这个方法中定义登录规则
     * 1)对所有请求放行(当前工程只做认证)
     * 2)登录成功信息的返回
     * 3)登录失败信息的返回
     * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭跨域工具
        http.csrf().disable();
        //放行所有请求
        http.authorizeRequests().anyRequest().permitAll();
        //登录成功与失败的处理
        http.formLogin()
                .successHandler(successHandler())
                .failureHandler(failureHandler());
    }

        @Bean
        public AuthenticationSuccessHandler successHandler(){
            return ( request, response,
                     authentication) -> {
                //1.构建map对象,封装响应数据
                Map<String,Object> map = new HashMap<>();
                map.put("state",200);
                map.put("message","login SB");
                //2.将map对象写到客户端
                writeJsonToClient(response,map);
            };
        }

        @Bean
        public AuthenticationFailureHandler failureHandler(){
            return ( request,
                     response,
                     e) -> {
                //1.构建map对象,封装响应数据
                Map<String,Object> map = new HashMap<>();
                map.put("state",500);
                map.put("message","login NO");
                //2.将map对象写到客户端
                writeJsonToClient(response,map);
            };
            }
        private void writeJsonToClient(HttpServletResponse response,
                                       Object object) throws IOException {
            //1.将对象转换为json
            //将对象转换为json有3种方案:
            //1)Google的Gson-->toJson  (需要自己找依赖)
            //2)阿里的fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
            //3)Springboot web自带的jackson-->writeValueAsString (spring-boot-starter-web)
            //我们这里借助springboot工程中自带的jackson
            //jackson中有一个对象类型为ObjectMapper,它内部提供了将对象转换为json的方法
            //例如:
            String jsonStr = new ObjectMapper().writeValueAsString(object);
            //3.将json字符串写到客户端
            PrintWriter writer = response.getWriter();
            writer.println(jsonStr);
            writer.flush();
        }
}

定义用户信息处理对象

在spring security应用中底层会借助UserDetailService对象获取数据库信息,并进行封装,最后返回给认证管理器,完成认证操作,例如:

package com.jt.auth.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * 登录时用户信息的获取和封装会在此对象进行实现,
 * 在页面上点击登录按钮时,会调用这个对象的loadUserByUsername方法,
 * 页面上输入的用户名会传给这个方法的参数
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    BCryptPasswordEncoder passwordEncoder;
    //UserDetails用户封装用户信息(认证和权限信息)
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        //1.基于用户名查询用户信息(用户名,用户状态,密码,....)
        String encodePassword= passwordEncoder.encode("123456");
        //2.查询用户权限信息
        List<GrantedAuthority> authorities =
                AuthorityUtils.createAuthorityList(
                        "sys:res:create", "sys:res:retrieve");
        //3.对用户信息进行封装
        return new User(username, encodePassword,authorities);
    }
}

网关中登陆路由配置

        - id: router02
          uri: lb://sca-auth  #lb表示负载均衡,底层默认使用ribbon实现
          predicates: #定义请求规则(请求需要按照此规则设计)
            - Path=/auth/login/** #请求路径设计
          filters:
            - StripPrefix=1 #转发之前去掉path中第一层路径

基于Postman进行访问测试

在这里插入图片描述

自定义登陆页面

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>login</title>
</head>
<body>
<div class="container"id="app">
    <h3>Please Login</h3>
    <form>
        <div class="mb-3">
            <label for="usernameId" class="form-label">Username</label>
            <input type="text" v-model="username" class="form-control" id="usernameId" aria-describedby="emailHelp">
        </div>
        <div class="mb-3">
            <label for="passwordId" class="form-label">Password</label>
            <input type="password" v-model="password" class="form-control" id="passwordId">
        </div>
        <button type="button" @click="doLogin()" class="btn btn-primary">Submit</button>
    </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm=new Vue({
        el:"#app",//定义监控点,vue底层会基于此监控点在内存中构建dom树
        data:{ //此对象中定义页面上要操作的数据
            username:"",
            password:""
        },
        methods: {//此位置定义所有业务事件处理函数
            doLogin() {
                //1.定义url
                let url = "http://localhost:9000/auth/login"
                //2.定义参数

                let params = new URLSearchParams()
                params.append('username',this.username);
                params.append('password',this.password);
                //3.发送异步请求
                axios.post(url, params).then((response) => {
                    debugger
                    let result=response.data;
                    console.log(result);
                    if (result.state == 200) {
                        alert("login ok");
                        location.href="/fileupload.html"
                    } else {
                        alert(result.message);
                    }
                })
            }
        }
    });
</script>
</body>
</html>

SpringSecurity 执行流程

在这里插入图片描述

JS中的断点调试

 在这里插入图片描述

颁发登陆成功令牌

构建令牌配置对象

本次我们借助JWT(Json Web Token-是一种json格式)方式将用户相关信息进行组织和加密,并作为响应令牌(Token),从服务端响应到客户端,客户端接收到这个JWT令牌之后,将其保存在客户端(例如localStorage),然后携带令牌访问资源服务器,资源服务器获取并解析令牌的合法性,基于解析结果判定是否允许用户访问资源.

/**
 * 创建JWT令牌配置类,基于这个类实现令牌对象的创建和解析.
 * JWT令牌的构成有三部分构成:
 * 1)HEADER (头部信息:令牌类型,签名算法)
 * 2)PAYLOAD (数据信息-用户信息,权限信息,令牌失效时间,...)
 * 3)SIGNATURE (签名信息-对header和payload部分进行加密签名)
 */
@Configuration
public class TokenConfig {
    //定义令牌签发口令(暗号),这个口令自己定义即可
    //在对header和PAYLOAD部分进行签名时,需要的一个口令
    private String SIGNING_KEY="auth";
    //初始化令牌生成策略(默认生成策略 UUID)
    //这里我们采用JWT方式生成令牌
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    //构建JWT令牌转换器对象,基于此对象创建令牌,解析令牌
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
       JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
       converter.setSigningKey(SIGNING_KEY);
       return converter;

    }

定义认证授权核心配置

第一步:在SecurityConfig中添加如下方法(创建认证管理器对象,后面授权服务器会用到):

 @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

第二步:所有零件准备好了开始拼装最后的主体部分,这个主体部分就是授权服务器的核心配置

package com.jt.auth.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.Arrays;

/**
 * 完成所有配置的组装,在这个配置类中完成认证授权,JWT令牌签发等配置操作
 * 1)SpringSecurity (安全认证和授权)
 * 2)TokenConfig
 * 3)Oauth2(暂时不说)
 */

@AllArgsConstructor
@Configuration
@EnableAuthorizationServer //开启认证和授权服务
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
    //此对象负责完成认证管理
    private AuthenticationManager authenticationManager;
    //TokenStore负责完成令牌创建,信息读取
    private TokenStore tokenStore;
    //JWT令牌转换器(基于用户信息构建令牌,解析令牌)
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    //密码加密匹配器对象
    private PasswordEncoder passwordEncoder;
    //负责获取用户信息信息
    private UserDetailsService userDetailsService;

    //设置认证端点的配置(/oauth/token),客户端通过这个路径获取JWT令牌
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                //配置认证管理器
                .authenticationManager(authenticationManager)
                //验证用户的方法获得用户详情
                .userDetailsService(userDetailsService)
                //要求提交认证使用post请求方式,提高安全性
                .allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET)
                //要配置令牌的生成,由于令牌生成比较复杂,下面有方法实现
                .tokenServices(tokenService());//这个不配置,默认令牌为UUID.randomUUID().toString()
    }

    //定义令牌生成策略
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        //这个方法的目标就是获得一个令牌生成器
        DefaultTokenServices services=new DefaultTokenServices();
        //支持令牌刷新策略(令牌有过期时间)
        services.setSupportRefreshToken(true);
        //设置令牌生成策略(tokenStore在TokenConfig配置了,本次我们应用JWT-定义了一种令牌格式)
        services.setTokenStore(tokenStore);
        //设置令牌增强(允许设置令牌生成策略,默认是非jwt方式,现在设置为jwt方式,并在令牌Payload部分允许添加扩展数据,例如用户权限信息)
        TokenEnhancerChain chain=new TokenEnhancerChain();
        chain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        services.setTokenEnhancer(chain);
        //设置令牌有效期
        services.setAccessTokenValiditySeconds(3600);//1小时
        //刷新令牌应用场景:一般在用户登录系统后,令牌快过期时,系统自动帮助用户刷新令牌,提高用户的体验感
        services.setRefreshTokenValiditySeconds(3600*72);//3天
        return services;
    }

    //设置客户端详情类似于用户详情
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //客户端id (客户端访问时需要这个id)
                .withClient("gateway-client")
                //客户端秘钥(客户端访问时需要携带这个密钥)
                .secret(passwordEncoder.encode("123456"))
                //设置权限
                .scopes("all")//all只是个名字而已和写abc效果相同
                //允许客户端进行的操作  这里的认证方式表示密码方式,里面的字符串千万不能写错
                .authorizedGrantTypes("password","refresh_token");
    }
    // 认证成功后的安全约束配置,对指定资源的访问放行,我们登录时需要访问/oauth/token,需要对这样的url进行放行
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //认证通过后,允许客户端进行哪些操作
        security
                //公开oauth/token_key端点
                .tokenKeyAccess("permitAll()")
                //公开oauth/check_token端点
                .checkTokenAccess("permitAll()")
                //允许提交请求进行认证(申请令牌)
                .allowFormAuthenticationForClients();
    }
}

配置网关认证的URL

  - id: router02
      uri: lb://sca-auth
      predicates:
        #- Path=/auth/login/**  #没要令牌之前,以前是这样配置
        - Path=/auth/oauth/**   #微服务架构下,需要令牌,现在要这样配置
      filters:
        - StripPrefix=1

登陆页面登陆方法设计

登陆成功以后,将token存储到localStorage中,修改登录页面的doLogin方法,

  doLogin() {
    //1.定义url
     let url = "http://localhost:9000/auth/oauth/token"
    //2.定义参数
      let params = new URLSearchParams()
       params.append('username',this.username);
       params.append('password',this.password);
       params.append("client_id","gateway-client");
       params.append("client_secret","123456");
       params.append("grant_type","password");
      //3.发送异步请求
       axios.post(url, params).then((response) => {
          alert("login ok");
           let result=response.data;
           localStorage.setItem("accessToken",result.access_token);
            location.href="/fileupload.html";
          }).catch((error)=>{
                    console.log(error);
         })
       }

单点登陆-redis

在这里插入图片描述

         

资源服务令牌解析配置

package com.jt.resource.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
 * 资源服务器的配置,在这个对象中重点要实现:
 * 1)JWT令牌解析的配置(客户端带着令牌访问资源时,要对令牌进行解析)
 * 2)启动资源访问的授权配置(不是所有登陆用户可以访问所有资源)
 */
@Configuration
@EnableResourceServer //此注解会启动资源服务器的默认配置
@EnableGlobalMethodSecurity(prePostEnabled = true) //执行方法之前启动权限检查
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;
    /**
     * token服务配置
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
       resources.tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //1.关闭跨越攻击
       http.csrf().disable();
       //2.放行所有资源的访问(对资源的访问不做认证)
       http.authorizeRequests().anyRequest().permitAll();
        // http.exceptionHandling()
        //不携带令牌去访问,会提示请先登录(认证)
        //.authenticationEntryPoint(authenticationEntryPoint())
        //.accessDeniedHandler(accessDeniedHandler());
    }
}

在controller的上传方法上添加

@PreAuthorize(“hasAuthority(‘sys:res:create’)”)注解,用于告诉底层框架方法此方法需要具备的权限,

 //将RequiredLog注解描述的方法作为切入点方法,在方法执行之前和之后进行日志记录
    //@PreAuthorize注解描述的方法表示必须已认证用户且有sys:res:create这个权限才可以访问
    @PreAuthorize("hasAuthority('sys:res:create')")
    @RequiredLog("文件上传") //扩展业务->日志记录
    @PostMapping("/upload/") //核心业务->文件上传

检查令牌

在这里插入图片描述

权限校验过程分析

在这里插入图片描述

postman 上传文件

在这里插入图片描述

文件上传JS方法设计

  axios.post(url,form,{headers:{"Authorization":"Bearer "+localStorage.getItem("accessToken")}})
            .then(function (response) {
            console.log("response",response);
            let result=response.data;
            if (result.state&&result.state==403){
                alert(result.message);
                return;
            }
            alert("upload OJ8K")
            })
    }

总结

重难点分析

  • 单点登陆系统的设计架构(微服务架构)
  • 服务的设计及划分(资源服务器,认证服务器,网关服务器,客户端服务)
  • 认证及资源访问的流程(资源访问时要先认证再访问)
  • 认证和授权时的一些关键技术(Spring Security,Jwt,Oauth2)

FAQ 分析
 

为什么要单点登陆(分布式系统,再访问不同服务资源时,不要总是要登陆,进而改善用户体验)
单点登陆解决方案?(市场常用两种: spring security+jwt+oauth2,spring securit+redis+oauth2)
Spring Security 是什么?(spring框架中的一个安全默认,实现了认证和授权操作)
JWT是什么?(一种令牌格式,一种令牌规范,通过对JSON数据采用一定的编码,加密进行令牌设计)
OAuth2是什么?(一种认证和授权规范,定义了单点登陆中服务的划分方式,认证的相关类型)

Bug 分析

  • 401 : 访问资源时没有认证。
  • 403 : 访问资源时没有权限。
  • 404:访问的资源找不到(一定要检查你访问资源的url)
  • 405: 请求方式不匹配(客户端请求方式是GET,服务端处理请求是Post就是这个问题)
  • 500: 不看后台无法解决?(error,warn)

存储数据实现: implements Serializable,实现一个序列化接口,都要添加一个ID

生成序列化ID

在这里插入图片描述

Idea&@Autowired

当使用@Autowired注解描述属性时,假如属性下有红色波浪线提示,可参考如下配置
在这里插入图片描述

系统服务设计及实现

 在这里插入图片描述

服务设计

 在这里插入图片描述

 日志里面记录:谁在什么时间点执行了什么操作,访问了什么方法,传递了什么参数,耗时多少.这些都要记录

 后续获取登录用户信息


        Object principal = SecurityContextHolder.getContext()
                .getAuthentication()
                .getPrincipal();
        System.out.println("principal="+principal);
        //假如要获取目标方法信息可以通过joinPoint对象,去拿到目标方法以及实际参数
        //后续要封装日志信息,然后通过RemoteLogService将日志提交日志服务或系统服务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值