SpringCloud_Gateway服务网关

文章目录

  • 一、SpringCloudGateway服务网关概论
    • 1、SpringCloudGateway服务网关概论
    • 2、SpringCloudGateway的三大核心概念
  • 二、SpringCloudGateway的路由及断言
    • 1、子模块项目SpringCloudGateway的搭建
    • 2、SpringCloudGateway_Java API构建路由
    • 3、SpringCloudGateway的动态路由功能
    • 4、SpringCloudGateway的路由断言
  • 三、SpringCloudGateway的过滤器及跨域
    • 1、SpringCloudGateway的过滤器
    • 2、网关过滤器GatewayFilter
    • 3、自定义网关过滤器GatewayFilter
    • 4、自定义全局过滤器GlobalFilter
    • 5、内置全局过滤器
    • 6、服务网关Gateway实现跨域
  • 四、SpringCloudGateway实现用户鉴权
    • 1、JsonWebToken概论
    • 2、创建用户的微服务及登录操作
    • 3、服务网关Gateway实现用户鉴权
  • 总结

一、SpringCloudGateway服务网关概论

1、SpringCloudGateway服务网关概论

Spring Cloud Gateway 用"Netty + Webflux"实现,不需要导入Web依赖。

  1. Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作,这些线程称为Loop线程,而业务交给响应式编程框架处理,响应式编程是非常灵活的,用户可以将业务中阻塞的操作提交到响应式框架的work线程中执行,而不阻塞的操作依然可以在Loop线程中进行处理,大大提高了Loop线程的利用率。
    即Webflux中的Loop线程不仅可以处理请求和响应请求,还可以对业务中不阻塞的操作进行处理,从而提高它的利用率。阻塞的操作由work线程进行处理。
  2. Webflux虽然可以兼容多个底层的通信框架,但是一般情况下,底层使用的还是Netty,毕竟,Netty是目前业界认可的最高性能的通信框架。
    Netty 是一个基于NIO的客户、服务器端的编程框架。提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
  3. Spring Cloud Gateway特点
    1)易于编写谓词( Predicates )和过滤器( Filters ) 。其Predicates和Filters
    可作用于特定路由。
    2)支持路径重写。
    3)支持动态路由。
    4)集成了Spring Cloud DiscoveryClient。

2、SpringCloudGateway的三大核心概念

  1. 路由(Route)
    这是网关的基本构建块。它由一个ID,一个目标URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
    即根据URL请求去匹配路由。
  2. 断言(predicate)
    输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数。匹配请求内容。
    匹配完路由后,每个路由上面都会有断言,然后根据断言来判断是否可以进行路由。
  3. 过滤(filter)
    在匹配完路由和断言为真后,可以在请求被路由前或者之后对请求进行修改。
    即根据业务对其进行监控,限流,日志输出等等。

二、SpringCloudGateway的路由及断言

1、子模块项目SpringCloudGateway的搭建

  1. 在cloud父项目中新建一个模块Module,创建子模块网关cloud-gateway-gateway9527

  2. 在POM文件中添加如下依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud</artifactId><groupId>com.zzx</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>cloud-gateway-gateway9527</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><!--  引入网关Gateway依赖   --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><!--  引入Eureka client依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!-- actuator监控信息完善 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies></project>
    
  3. 在gateway子模块中创建包com.zzx,在包下创建主启动类GatewayMain9527

    package com.zzx;import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
    @Slf4j
    public class GatewayMain9527 {public static void main(String[] args) {SpringApplication.run(GatewayMain9527.class,args);log.info("************ GatewayMain9527服务 启动成功 *************");}
    }
  4. 在resources目录下创建application.yml文件,配置如下

    server:port: 9527
    spring:cloud:gateway:routes:# 路由ID,没有固定规则但要求唯一,建议配合服务名- id: cloud-payment-provider# 匹配后提供服务的路由地址 (即目标服务地址)uri: http://localhost:8001# 断言会接收一个输入参数,返回一个布尔值结果predicates:# 路径相匹配的进行路由- Path=/payment/*
  5. 测试
    1)先开启7001和7002的Eureka服务,payment8001服务提供者和gateway9527服务。
    2)在浏览器使用9527端口,也就是网关进行访问payment8001服务即可。
    在浏览器输入:http://localhost:9527/payment/index

2、SpringCloudGateway_Java API构建路由

  1. 在子模块cloud-gateway-gateway9527中的com.zzx包下,创建包config,并在包下创建GatewayConfig

    package com.zzx.config;import org.springframework.cloud.gateway.route.RouteLocator;
    import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;@Configuration
    public class GatewayConfig {@Beanpublic RouteLocator routeLocator(RouteLocatorBuilder builder){//获取路由RouteLocatorBuilder.Builder routes = builder.routes();/*** 设置路由* 1.路由id* 2.路由匹配规则* 3.目标地址*/routes.route("path_route",r->r.path("/payment/*").uri("http://localhost:8001/")).build();return routes.build();}
    }
  2. 测试
    1)将yml文件中的gateway配置注释掉,然后重启该服务。
    2)在浏览器上访问:http://localhost:9527/payment/index

3、SpringCloudGateway的动态路由功能

  1. 再添加一个服务提供者,用以实现Gateway网关的动态路由的功能。
    1)复制payment8001服务,然后点击cloud父工程,ctrl+v进行粘贴,修改名字为8002
    2)修改POM文件:

    <artifactId>cloud-provider-payment8002</artifactId>
    

    3)将POM右键,选择添加为Maven项目Add as Maven Project
    在这里插入图片描述
    4)修改com.zzx包下的启动类的名字以及类中的名字

    package com.zzx;import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;/*** 主启动类*/
    @SpringBootApplication
    @Slf4j
    public class PaymentMain8002 {public static void main(String[] args) {SpringApplication.run(PaymentMain8002.class,args);log.info("****** PaymentMain8002服务启动成功 *****");}
    }

    5)将yml文件的端口号port和instance-id的名字有8001部分都修改为8002
    然后在启动类中运行该payment8002服务。

  2. 修改gateway9527项目的yml文件

    server:port: 9527eureka:instance:# 注册名instance-id: cloud-gateway-gateway9527client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/
    spring:application:#设置应用名name: cloud-gatewaycloud:gateway:routes:# 路由ID,没有固定规则但要求唯一,建议配合服务名- id: cloud-payment-provider# 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字uri: lb://CLOUD-PAYMENT-PROVIDER# 断言会接收一个输入参数,返回一个布尔值结果predicates:# 路径相匹配的进行路由- Path=/payment/*
    
  3. 注释之前的配置文件GatewayConfig中的方法。

  4. 在服务提供者payment8001和payment8002中的com.zzx.controller的PaymentController类中添加如下代码

    @Value("${server.port}")
    private String port;
    @GetMapping("lb")
    public String lb(){return port;
    }
    

    即通过该lb的url请求来测试动态路由是否配置生效。

  5. 测试动态路由是否配置生效。
    1)重启payment8001和payment8002以及gateway9527服务
    2)浏览器中访问:http://localhost:9527/payment/lb
    在这里插入图片描述
    在这里插入图片描述
    此时刷新后随即出现8001或8002,估计是轮询的策略。

4、SpringCloudGateway的路由断言

  1. UTC时间格式的时间参数时间生成方法

    package demo;import java.time.ZonedDateTime;public class Test1 {public static void main(String[] args) {ZonedDateTime now = ZonedDateTime.now();System.out.println(now);}
    }
    
  2. Postman的下载地址:https://dl.pstmn.io/download/latest/win64
    Postman即用来URL请求测试的软件,可以很方便的添加任何请求参数。
    点击+号即可创建新的请求窗口,用来发送URL请求
    在这里插入图片描述

  3. After路由断言

    predicates:- Path=/payment/*# 在这个时间点之后才能访问- After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    

    即使用生成的UTC时间格式的时间,在该时间之后才允许访问。

  4. Before路由断言

    predicates:- Path=/payment/*# 在这个时间点之前才能访问- Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    

    即使用生成的UTC时间格式的时间,在该时间之前才允许访问。

  5. Between路由断言

    predicates:- Path=/payment/*# 在两个时间内才能访问- Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    

    即使用生成的UTC时间格式的时间,在两个时间内才允许访问。

  6. Cookie路由断言
    1)Cookie验证的是Cookie中保存的信息,Cookie断言和上面介绍的两种断言使用方式大同小异,唯一的不同是它必须连同属性值一同验证,不能单独只验证属性是否存在。

    predicates:- Path=/payment/*- Cookie=username,zzx
    

    即Cookie的username的值为zzx才允许访问
    2)使用postman进行测试,在headers添加Cookie即可
    在这里插入图片描述
    此时如果不带Cookie,则报404错误

  7. Header路由断言
    1)这个断言会检查Header中是否包含了响应的属性,通常可以用来验证请求是否携带了访问令牌。

    predicates:- Path=/payment/*- Header=X-Request-Id,\d+
    

    2)使用postman进行测试,在headers添加X-Request-Id即可
    在这里插入图片描述

  8. Host路由断言
    1)Host 路由断言 Factory包括一个参数:host name列表。使用Ant路径匹配规则, .作为分隔符。访问的主机匹配http或者https, baidu.com 默认80端口, 就可以通过路由。 多个参数使用,号隔开。

    predicates:- Path=/payment/*- Host=127.0.0.1,localhost
    

    2)使用postman进行测试,在headers添加Host即可
    在这里插入图片描述

  9. Method路由断言
    1)即Request请求的方式,例如GET或POST请求,不匹配则无法进行请求

    predicates:- Path=/payment/*- Method=GET,POST
    

    2)可以使用postman,也可以使用浏览器直接访问,因为不需要加任何参数

  10. Query路由断言
    1)请求断言也是在业务中经常使用的,它会从ServerHttpRequest中的Parameters列表中查询指定的属性,例如验证参数的类型等

    predicates:- Path=/payment/*- Query=age,\d+
    

    2)在参数Params中添加age属性,值为正整数即可访问
    在这里插入图片描述

三、SpringCloudGateway的过滤器及跨域

1、SpringCloudGateway的过滤器

  1. 过滤器Filter
    在用户访问各个服务前,应在网关层统一做好鉴权、限流等工作。
    1)Filter的生命周期
    根据生命周期可以将Spring Cloud Gateway中的Filter分为"PRE"和"POST"两种。
    PRE:代表在请求被路由之前执行该过滤器,此种过滤器可用来实现参数校验、权限校验、流量监控、日志输出、协议转换等功能。
    POST:代表在请求被路由到微服务之后执行该过滤器。此种过滤器可用来实现响应头的修改(如添加标准的HTTP Header )、收集统计信息和指标、将响应发送给客户端、输出日志、流量监控等功能。
    即PRE是路由之前,POST是路由之后。
    2)Filter分类
    根据作用范围,Filter可以分为以下两种。
    GatewayFilter:网关过滤器,此种过滤器只应用在单个路由或者一个分组的路由上。
    GlobalFilter:全局过滤器,此种过滤器会应用在所有的路由上。

2、网关过滤器GatewayFilter

  1. 官方的配置文档:https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gatewayfilter-factories
  2. 使用内置过滤器SetStatus
    1)在yml文件中的filters下添加过滤器
    server:port: 9527eureka:instance:# 注册名instance-id: cloud-gateway-gateway9527client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/
    spring:application:#设置应用名name: cloud-gatewaycloud:gateway:routes:# 路由ID,没有固定规则但要求唯一,建议配合服务名- id: cloud-payment-provider# 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字uri: lb://CLOUD-PAYMENT-PROVIDER# 断言会接收一个输入参数,返回一个布尔值结果predicates:# 路径相匹配的进行路由- Path=/payment/*# 在这个时间点之后才能访问
    #            - After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]# 在这个时间点之前才能访问
    #            - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]# 在两个时间内才能访问
    #            - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    #            - Cookie=username,zzx
    #            - Header=X-Request-Id,\d+
    #            - Host=127.0.0.1,localhost
    #            - Method=GET,POST
    #            - Query=age,\d+#过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改filters:# 修改原始响应的状态码- SetStatus=250
    
    2)在浏览器测试:http://localhost:9527/payment/lb
    在这里插入图片描述
    此时响应码成功修改为250。

3、自定义网关过滤器GatewayFilter

  1. 在gateway9527服务的com.zzx.config包下,创建日志网关过滤器类LogGatewayFilterFactory

    package com.zzx.config;import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.stereotype.Component;import java.util.Arrays;
    import java.util.List;/*** 日志网关过滤器*/
    @Component
    @Slf4j
    public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {public LogGatewayFilterFactory() {super(Config.class);}/*** 表示配置填写顺序* @return*/@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("consoleLog");}/*** 执行过滤的逻辑* @param config* @return*/@Overridepublic GatewayFilter apply(Config config) {return ((exchange, chain) -> {if(config.consoleLog){log.info("********* consoleLog日志 开启 ********");}return chain.filter(exchange);});}/*** 过滤器使用的配置内容**/@Datapublic static class Config{private boolean consoleLog;}}
  2. 在YML文件中,添加如下

    filters:# 控制日志是否开启- Log=true
    

    即开启日志,该true会被consoleLog获取到。 然后即可打印对应的日志。

  3. 测试
    1)重启Gateway9527服务
    2)在浏览器中访问:http://localhost:9527/payment/lb
    在这里插入图片描述
    步骤:
    1、类名必须叫做XxxGatewayFilterFactory,注入到Spring容器后使用时的名称就叫做Xxx。
    2、创建一个静态内部类Config, 里面的属性为配置文件中配置的参数, - 过滤器名称=参数1,参数2…
    2、类必须继承 AbstractGatewayFilterFactory,让父类帮实现配置参数的处理。
    3、重写shortcutFieldOrder()方法,返回List参数列表为Config中属性集合
    return Arrays.asList(“参数1”,参数2…)
    4、无参构造方法中super(Config.class)
    5、编写过滤逻辑 public GatewayFilter apply(Config config)

4、自定义全局过滤器GlobalFilter

  1. 在gateway9527服务的com.zzx.config包下,创建用户鉴权全局过滤器类AuthGlobalFilter

    package com.zzx.config;import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;/*** 用户鉴权全局过滤器*/
    @Component
    public class AuthGlobalFilter implements GlobalFilter, Ordered {/*** 自定义全局过滤器逻辑* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1。请求中获取Token令牌String token = exchange.getRequest().getQueryParams().getFirst("token");//2.判断token是否为空if(StringUtils.isEmpty(token)){System.out.println("鉴权失败,令牌为空");//将状态码设置为未授权exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}//3。判断token是否有效if(!token.equals("zzx")){System.out.println("token令牌无效");exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}return chain.filter(exchange);}/*** 全局过滤器执行顺序 数值越小,优先级越高* @return*/@Overridepublic int getOrder() {return 0;}
    }
  2. 使用postman测试,在params中添加一个token进行测试
    在这里插入图片描述

5、内置全局过滤器

  1. 官方的配置文档:https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#global-filters
    SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理的。
  2. 路由过滤器(Forward)
  3. 路由过滤器(LoadBalancerClient)
  4. Netty路由过滤器
  5. Netty写响应过滤器(Netty Write Response F)
  6. RouteToRequestUrl 过滤器
  7. 路由过滤器 (Websocket Routing Filter)
  8. 网关指标过滤器(Gateway Metrics Filter)
  9. 组合式全局过滤器和网关过滤器排序(Combined Global Filter and GatewayFilter Ordering)
  10. 路由(Marking An Exchange As Routed)

6、服务网关Gateway实现跨域

  1. 跨域
    即当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

  2. 在resources目录下创建index.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><title>Title</title>
    </head>
    <body></body>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>$.get("http://localhost:9527/payment/lb?token=zzx",function(data,status){alert("Data: " + data + "\nStatus: " + status);});
    </script>
    </html>
  3. 配置允许跨域
    1)在未配置允许跨域之前,打开该index.html文件时,如图
    在这里插入图片描述
    2)在yml文件中配置允许跨域

    spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowCredentials: trueallowedOriginPatterns: "*"allowedMethods: "*"allowedHeaders: "*"add-to-simple-url-handler-mapping: true

3)配置后,打开该index.html文件时,如图
在这里插入图片描述

四、SpringCloudGateway实现用户鉴权

1、JsonWebToken概论

  1. JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录(SSO)场景。

  2. JWT优点
    1)无状态
    2)适合移动端应用
    3)单点登录友好

  3. 用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名,服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

  4. JWT 的三个部分依次如下:
    1)头部(header)
    JSON对象,描述 JWT 的元数据。其中 alg 属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ 属性表示这个令牌(token)的类型(type),统一写为 JWT。

    {"alg": "HS256","typ": "JWT"
    }
    

    2)载荷(payload)
    内容又可以分为3种标准
    1.标准中注册的声明
    iss: jwt签发者
    sub: jwt所面向的用户
    aud: 接收jwt的一方
    exp: jwt的过期时间,这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前,该jwt都是不可用的.
    iat: jwt的签发时间
    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
    2.公共的声明
    公共的声明可以添加任何的信息。一般这里我们会存放一下用户的基本信息(非敏感信息)。
    3.私有的声明
    私有声明是提供者和消费者所共同定义的声明。需要注意的是,不要存放敏感信息
    base64编码,任何人获取到jwt之后都可以解码!!

    {"sub": "1234567890","name": "John Doe","iat": 1516239022
    }
    

    3)签证(signature)
    这部分就是 JWT 防篡改的精髓,其值是对前两部分base64UrlEncode 后使用指定算法签名生成,以默认 HS256 为例,指定一个密钥(secret),就会按照如下公式生成:

    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret,
    )
    
  5. 客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

2、创建用户的微服务及登录操作

  1. 在cloud父工程下,创建子模块项目cloud-auth-user6500
    在这里插入图片描述

  2. 在cloud-auth-user6500项目的pom文件中引入依赖

     <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- eureka client 依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--   引入JWT依赖   --><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.23</version></dependency><!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.2.1</version></dependency></dependencies>
  3. 在com.zzx中创建一个包utils,创建工具类JWTUtils

    package com.zzx.utils;import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTVerificationException;
    import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;
    import java.util.concurrent.TimeUnit;public class JWTUtils {// 签发人private static final String ISSUSER = "zzx";// 过期时间 1分钟private static final long TOKEN_EXPIRE_TIME = 60*1000;// 秘钥public static final String SECRET_KEY = "zzx-13256";/*** 生成令牌* @return*/public static String token(){Date now = new Date();Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);// 1.创建JWTString token = JWT.create().// 签发人withIssuer(ISSUSER)// 签发时间.withIssuedAt(now)// 过期时间.withExpiresAt(new Date(now.getTime()+TOKEN_EXPIRE_TIME))// 加密算法.sign(hmac256);return token;}/*** 验证令牌* @return*/public static boolean verify(String token){try {Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);JWTVerifier verifier = JWT.require(hmac256)// 签发人.withIssuer(ISSUSER).build();// 如果校验有问题则抛出异常DecodedJWT verify = verifier.verify(token);return true;} catch (IllegalArgumentException e) {e.printStackTrace();} catch (JWTVerificationException e) {e.printStackTrace();}return false;}public static void main(String[] args) throws InterruptedException {String token = token();System.out.println(token);boolean verify = verify(token);System.out.println(verify);verify = verify(token+" 11");System.out.println(verify);TimeUnit.SECONDS.sleep(61);verify = verify(token);System.out.println(verify);}
    }

    在该工具类JWTUtils中创建main方法用来测试该工具类。后面需要删掉。

  4. 在com.zzx中创建一个包common,创建类Result

    package com.zzx.common;import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;/*** 返回实体类*/
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @Builder
    public class Result {// 状态码private int code;// 描述信息private String msg;// token令牌private String token;
    }

    即用该类来封装返回值信息。

  5. 在com.zzx中创建一个包controller,创建控制层类UserController

    package com.zzx.controller;import com.zzx.common.Result;
    import com.zzx.utils.JWTUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;/*** 用户控制层*/
    @RestController
    @RequestMapping("user")
    public class UserController {/*** 登录* @param username* @param password*/@PostMapping("login")public Result login(String username, String password){// 1.验证用户名和密码// TODO 模拟数据库操作if("zzx".equals(username)&&"123456".equals(password)){// 2.生成令牌String token = JWTUtils.token();return Result.builder().code(200).msg("success").token(token).build();}else{return Result.builder().code(500).msg("用户名或密码不正确").build();}}
    }
  6. 在resources目录下创建一个application.yml配置文件

    server:port: 6500eureka:instance:# 注册名instance-id: cloud-auth-user6500client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/
    spring:application:#设置应用名name: cloud-auth-user
    
  7. 在com.zzx中,修改主启动类Main,修改为UserMain6500

    package com.zzx;import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;/*** 主启动类*/
    @Slf4j
    @SpringBootApplication
    public class UserMain6500 {public static void main(String[] args) {SpringApplication.run(UserMain6500.class,args);log.info("************ UserMain6500服务 启动成功 ************");}
    }
    
  8. 测试User控制层的login方法
    1)启动eureka服务eureka7001和eureka7002以及user6500
    在这里插入图片描述

2)在postman中,使用POST请求传入用户名和密码,对该url进行测试
在这里插入图片描述

3、服务网关Gateway实现用户鉴权

即在网关过滤器中加入JWT来鉴权

  1. 在gateway9527项目的POM文件中添加JWT依赖

     <!--   引入JWT依赖     --><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.23</version></dependency><!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.2.1</version></dependency>
    
  2. 将user6500项目中com.zzx.utils包下的JWTUtils复制到gateway9527项目的com.zzx.utils包下

    package com.zzx.utils;import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTVerificationException;
    import com.auth0.jwt.interfaces.DecodedJWT;import java.util.Date;public class JWTUtils {// 签发人private static final String ISSUSER = "zzx";// 过期时间 1分钟private static final long TOKEN_EXPIRE_TIME = 60*1000;// 秘钥public static final String SECRET_KEY = "zzx-13256";/*** 生成令牌* @return*/public static String token(){Date now = new Date();Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);// 1.创建JWTString token = JWT.create().// 签发人withIssuer(ISSUSER)// 签发时间.withIssuedAt(now)// 过期时间.withExpiresAt(new Date(now.getTime()+TOKEN_EXPIRE_TIME))// 加密算法.sign(hmac256);return token;}/*** 验证令牌* @return*/public static boolean verify(String token){try {Algorithm hmac256 = Algorithm.HMAC256(SECRET_KEY);JWTVerifier verifier = JWT.require(hmac256)// 签发人.withIssuer(ISSUSER).build();// 如果校验有问题则抛出异常DecodedJWT verify = verifier.verify(token);return true;} catch (IllegalArgumentException e) {e.printStackTrace();} catch (JWTVerificationException e) {e.printStackTrace();}return false;}}
  3. 修改application.yml文件

    server:port: 9527eureka:instance:# 注册名instance-id: cloud-gateway-gateway9527client:service-url:# Eureka server的地址#集群defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka#单机#defaultZone: http://localhost:7001/eureka/org:my:jwt:# 跳过认证路由skipAuthUrls:- /user/loginspring:application:#设置应用名name: cloud-gatewaycloud:gateway:# 路由配置routes:# 路由ID,没有固定规则但要求唯一,建议配合服务名- id: cloud-auth-user# 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字uri: lb://CLOUD-AUTH-USER# 断言会接收一个输入参数,返回一个布尔值结果predicates:# 路径相匹配的进行路由- Path=/user/*# 路由ID,没有固定规则但要求唯一,建议配合服务名- id: cloud-payment-provider# 匹配后提供服务的路由地址 (即目标服务地址) lb后跟提供服务的微服务的名字uri: lb://CLOUD-PAYMENT-PROVIDER# 断言会接收一个输入参数,返回一个布尔值结果predicates:# 路径相匹配的进行路由- Path=/payment/*# 在这个时间点之后才能访问
    #            - After=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]# 在这个时间点之前才能访问
    #            - Before=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]# 在两个时间内才能访问
    #            - Between=2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai],2030-04-28T11:50:49.213572400+08:00[Asia/Shanghai]
    #            - Cookie=username,zzx
    #            - Header=X-Request-Id,\d+
    #            - Host=127.0.0.1,localhost
    #            - Method=GET,POST
    #            - Query=age,\d+#过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改filters:# 修改原始响应的状态码
    #            - SetStatus=250# 控制日志是否开启- Log=trueglobalcors:cors-configurations:'[/**]':allowCredentials: trueallowedOriginPatterns: "*"allowedMethods: "*"allowedHeaders: "*"add-to-simple-url-handler-mapping: true
    

    即需要添加一个user微服务的路由,以及跳过权限验证的Path路径

  4. 将gateway9527项目的com.zzx.config包下原先的用户鉴权类AuthGlobalFilter上面的@Component注解注释掉,即不使用这个类来鉴权;创建使用另一个类UserAuthGlobalFilter来鉴权

    package com.zzx.config;import com.alibaba.fastjson.JSONObject;
    import com.zzx.common.Response;
    import com.zzx.utils.JWTUtils;
    import io.micrometer.common.util.StringUtils;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;/*** 用户鉴权全局过滤器*/
    @Data
    @ConfigurationProperties("org.my.jwt")
    @Component
    @Slf4j
    public class UserAuthGlobalFilter implements GlobalFilter, Ordered {private String[] skipAuthUrls;/*** 过滤器逻辑* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求url地址String path = exchange.getRequest().getURI().getPath();// 跳过不需要验证的路径if(skipAuthUrls!=null && isSKip(path)){return chain.filter(exchange);}// 1.从请求头中获取tokenString token = exchange.getRequest().getHeaders().getFirst("token");// 2.判断tokenif(StringUtils.isEmpty(token)){// 3.设置响应ServerHttpResponse response = exchange.getResponse();// 4.设置响应状态码response.setStatusCode(HttpStatus.OK);// 5.设置响应头response.getHeaders().add("Content-Type","application/json;charset=UTF-8");// 6.创建响应对象Response res = new Response(200, "token 参数缺失");// 7.对象转字符串byte[] bytes = JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8);// 8.数据流返回数据DataBuffer wrap = response.bufferFactory().wrap(bytes);return response.writeWith(Flux.just(wrap));}// 验证tokenboolean verify = JWTUtils.verify(token);if(!verify){// 3.设置响应ServerHttpResponse response = exchange.getResponse();// 4.设置响应状态码response.setStatusCode(HttpStatus.OK);// 5.设置响应头response.getHeaders().add("Content-Type","application/json;charset=UTF-8");// 6.创建响应对象Response res = new Response(200, "token 失效");// 7.对象转字符串byte[] bytes = JSONObject.toJSONString(res).getBytes(StandardCharsets.UTF_8);// 8.数据流返回数据DataBuffer wrap = response.bufferFactory().wrap(bytes);return response.writeWith(Flux.just(wrap));}// token 令牌通过return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}private boolean isSKip(String url){for (String skipAuthUrl :skipAuthUrls) {if(url.startsWith(skipAuthUrl)){return true;}}return false;}
    }
  5. 测试
    1)先启动eureka7001和eureka7002,还有Payment8001和Payment8002,以及user6500和gateway9527服务。
    在这里插入图片描述

2)使用postman工具来测试,先进行登录,拿到用户的token
在这里插入图片描述
3)再切换到之前9527的url测试
token有效时
在这里插入图片描述
token过期失效时
在这里插入图片描述
没有token时(即未登录时)
在这里插入图片描述

总结

  1. Spring Cloud Gateway 用"Netty + Webflux"实现,不需要导入Web依赖。
    1)Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作,这些线程称为Loop线程。Webflux中的Loop线程不仅可以处理请求和响应请求,还可以对业务中不阻塞的操作进行处理,从而提高它的利用率。阻塞的操作由work线程进行处理。
    Webflux底层使用的还是Netty,Netty是目前业界认可的最高性能的通信框架。
    2)SpringCloudGateway的三大核心概念,分别是路由、断言、过滤。
    即根据url请求进行匹配到指定路由;每个路由上面都有断言,根据断言来判断是否可以进行路由;最后对该url请求进行一个过滤,例如监控、限流和日志输出等操作。
  2. 1)SpringCloudGateway的搭建,需要先引入依赖,然后创建主启动类,最后配置Gateway的配置文件。- id属性值需要唯一;uri的属性值即对应的服务器ip地址+端口号;predicates断言的属性值,例如OrderController第一层@RequestMapping注解的url属性值,即判断url是否跟该值一致。
    2)服务网关Gateway通过Java API构建时需要实现RouteLocator接口构建路由规则。即先将yml文件中等价的gateway配置注释掉,然后创建一个config配置类,在配置类中,创建一个方法使用RouteLocator接口来构建路由。并在该方法上添加@Bean注解,即由SpringIOC容器进行管理。
    3)SpringCloudGateway的动态路由功能,即在yml文件中将原本路由的uri改成lb://服务提供者的微服务的名字;然后需要引入EureakaClient和Gateway等依赖即可实现Gateway的动态路由功能。
    也就是说需要配置和使用Eureka,但是可以设置不把自身注册到Eureka服务中。
    4)SpringCloudGateway的路由断言,路由断言分别有After、Before、Between、Cookie、Header、Host、Method、Query等。其中After、Before、Between都是跟时间有关的;Cookie、Header、Host都是在头文件Headers中携带的参数;Method是匹配指定的请求方法;Query是在Params中检查参数的合法性。
    断言是在YML文件的spring.cloud.gateway.routes.predicates下进行配置的。
    5)SpringCloudGateway的过滤器,在用户访问各个服务前,应在网关层统一做好鉴权、限流等工作。过滤器Filter的生命周期分为PRE和POST,即PRE是路由之前,POST是路由之后。它作用范围分为GatewayFilter和GlobalFilter,即GatewayFilter是网关路由器,是应用在单个路由或一个分组的路由上的,而GlobalFilter是全局路由器,会应用在所有路由上的。
  3. 1)内置过滤器,即在YML文件中,在filters下添加内置过滤器。
    2)自定义网关过滤器,即需要创建一个配置类,类名必须叫做XxxGatewayFilterFactory,在该类上使用@Component注解;该类需要创建一个静态内部类Config,里面的属性为配置文件中配置的参数;必须继承AbstractGatewayFilterFactory;重写shortcutFieldOrder()方法,返回List参数列表为Config中属性集合;创建无参构造方法,方法体为super(Config.class);编写过滤逻辑 public GatewayFilter apply(Config config)。
    3)自定义全局过滤器,当客户端第一次请求服务时,服务端对用户进行信息认证(登录);认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证;以后每次请求,客户端都携带认证的token;服务端对token进行解密,判断是否有效,有效则继续允许访问服务,无效则不允许访问服务。
    此时需要实现GlobalFilter, Ordered接口,一个是全局过滤器接口,一个是全局过滤器执行顺序的接口。
    即在Ordered接口的实现类中返回一个数值,该值越小,当前过滤器的优先级越高。
    GlobalFilter接口的实现类,对请求参数进行一个过滤操作。
  4. 1)配置跨域,即在yml文件配置允许跨域即可。
    2)JsonWebToken,是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。适用于分布式的单点登录(SSO)。
    客户端收到服务器返回的 JWT,会把数据保存到loadStorage。JWT签证默认的算法是 HMAC SHA256(HS256)。
    3)用户登录并生成token返回的业务流程,登录成功时JWT的Token通过JWTUtils工具类生成,状态码为200,消息为成功,以及返回值的类型需要封装为Result实体类;登录失败时不生成token,状态码500,消息为用户名或密码错误,同时返回值的类型也是需要封装为Result实体类。此时该方法为Post请求,因为涉及帐号信息。
    并且需要引入JWT和fastjson依赖。
  5. 使用gateway网关进行用户鉴权,在application.yml文件中配置跳过login登录的鉴权,即其他的url请求都要进行用户鉴权;需要引入JWT和fastjson的依赖;使用JWTUtils的生成token方法以及验证token是否有效的方法;在用户鉴权时,需要先获取跳过的路径,进行匹配,匹配成功则跳过鉴权进行下一步的业务操作;匹配失败,则说明该请求需要验证token,首先需要从request请求的请求头中获取token,如果token为空,则返回一个response对象,包含状态码和字符串消息;如果token不为空,则进行下一步验证,即使用JWTUtils的token验证方法,如果返回false,则表示token无效或者失效,则返回一个response对象,包含状态码和字符串消息;如果token不为空且token有效,则进行下一步的业务操作。
    即用户鉴权,实际上就判断该请求是否需要跳过鉴权,以及token是否为空和token是否有效的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/190172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学生备考护眼灯哪个牌子好?值得入手的护眼台灯推荐

护眼台灯作为这几年人们很关注的电器产品&#xff0c;家里有孩子或者是经常面对电子设备的人士&#xff0c;相信都会对其有所了解并且购买了护眼台灯&#xff0c;但是还有些家长对护眼台灯的认知不够深&#xff0c;以至于还没有给孩子安排上护眼台灯&#xff0c;还在疑惑护眼台…

FlowJo软件的简单介绍 掌控流式细胞分析的科技巨匠 FlowJo10

FlowJo 10 for Mac是一款强大的流式细胞数据分析软件&#xff0c;具有以下功能&#xff1a; 数据导入与预处理&#xff1a;FlowJo 10可以轻松导入各种类型的流式细胞数据&#xff0c;并对数据进行预处理&#xff0c;包括去噪、背景校正等&#xff0c;以确保数据的准确性和可靠…

小小窗户,在风水中有大大的作用

风水&#xff0c;不是迷信&#xff0c;是环境科学&#xff0c;是地磁学。过去大户人家&#xff0c;官府都是很注重风水的。现在&#xff0c;越是往社会上层越是注重风水。在实际的家居风水之中&#xff0c;要想生旺气场&#xff0c;增加财运&#xff0c;风水纳气一定要充足。 自…

CCC联盟数字车钥匙(七)——BLE连接流程

本文接上一篇CCC数字钥匙BLE概述&#xff0c;介绍BLE中相关连接流程的实现。 2、BLE流程 2.1 所有者配对连接建立 CCC中使用Bluetooth OOB&#xff08;Out of Band, 带外&#xff09;配对完成所有者配对、连接建立的流程。BLE设置分为以下两个子部分&#xff1a; BLE链路层连…

UI自动化Selenium find_elements和find_element的区别

# 如果获取的element是list&#xff0c;那么需要用find_elements方法&#xff1b;此方法会返回list&#xff0c;然后使用len() 方法&#xff0c;计算对象的个数&#xff1b; # find_element方法返回的不是list对象&#xff0c;所以导致没办法计算对象个数 # 1.返回值类型不同…

oops-framework框架 之 初始了解(一)

引擎&#xff1a;CocosCreator 环境&#xff1a; Mac Gitee: oops-framework 简介 oops-framework是由作者dgflash编写&#xff0c;基于CocosCreator 3.x而实现的开源框架。 该框架以插件形式存在&#xff0c;主要目的是为了降低与项目的耦合&#xff0c;并且通过插件内部的…

LeetCode | 100. 相同的树

LeetCode | 100. 相同的树 OJ链接 判断两个节点是否等于空&#xff0c;两个都等于空就直接返回true如果一个等于空&#xff0c;另一个不等于空&#xff0c;说明false然后再判断两个树的值是否相等最后递归p的左&#xff0c;q的左&#xff0c;p的右&#xff0c;q的右 bool isS…

仿真的整体框架和类图设计

之前的写的模拟代码没有模块&#xff0c;没有对象&#xff0c;写的逻辑结构也很混乱。我花了些时间进行整理&#xff0c;首先所有的类如下图 在管理类中有统一的管理类的接口 &#xff0c;提供所有管理类的虚拟初始化和关闭方法 然后事件的管理类 我希望在这个类中管理所有的脉…

K7系列FPGA多重启动(Multiboot)

Xilinx 家的 FPGA 支持多重启动功能&#xff08;Multiboot&#xff09;&#xff0c;即可以从多个 bin 文件中进行选择性加载&#xff0c;从而实现对系统的动态更新&#xff0c;或系统功能的动态调整。 这一过程可以通过嵌入在 bit 文件里的 IPROG 命令实现上电后的自动加载。而…

牛客剑指offer刷题模拟篇

文章目录 顺时针打印矩阵题目思路代码实现 扑克牌顺子题目思路代码实现 把字符串转换成整数题目思路代码实现 表示数值的字符串题目思路代码实现 顺时针打印矩阵 题目 描述 输入一个矩阵&#xff0c;按照从外向里以顺时针的顺序依次打印出每一个数字&#xff0c;例如&#xf…

机器学习笔记 - 基于百度飞桨PaddleSeg的人体分割模型以及TensorRT部署说明

一、简述 虽然Segment Anything用于图像分割的通用大模型看起来很酷(飞桨也提供分割一切的模型),但是个人感觉落地应用的时候心里还是更倾向于飞桨这种场景式的,因为需要用到一些人体分割的需求,所以这里主要是对飞桨高性能图像分割开发套件进行了解和使用,但是暂时不训练…

Java 线程同步和通信

Android 11 废弃了AsyncTask 线程 Thread: 通过start 开启 源码: start0 native方法 通过虚拟机跟操作系统交互 进程和线程区别: 进程是操作系统的独立区域,各个区域互不干扰,一个进程可以有多条线程同时工作,进程大于线程,线程依赖进程,线程间可以共享资源 Runnable: 接口…

matlab操作方法(二)——基本作图

matlab提供很多灵活的二维作图功能函数。这些作图函数分为3类&#xff1a;图形处理、曲线和曲面图的创建、注释和图形特性。作图函数虽多&#xff0c;但语法大致相同 在 MATLAB 中&#xff0c;figure 函数用于创建或选择图形窗口。 matlab figure函数的用法_matlab中figure-C…

89基于matlab的人工蜂群和粒子群混合优化的路径规划算法

基于matlab的人工蜂群和粒子群混合优化的路径规划算法&#xff0c;起点和终点确定的前提下&#xff0c;在障碍物中寻找最佳路径。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 89人工蜂群和粒子群混合优化 (xiaohongshu.com)https://www.xiaohongshu.com/e…

用JavaScript的管道方法简化代码复杂性

用JavaScript的管道方法简化代码复杂性 在现代 web 开发中&#xff0c;维护干净有效的代码是必不可少的。随着项目的增加&#xff0c;我们功能的复杂性也在增加。然而&#xff0c;javaScript为我们提供了一个强大的工具&#xff0c;可以将这些复杂的函数分解为更小的、可管理的…

【 RTTI 】

RTTI 概念&#xff1a; RTTI(Run Time Type Identification)即通过运行时类型识别&#xff0c;程序能够使用基类的指针或引用来检 查着这些指针或引用所指的对象的实际派生类型。 原因&#xff1a; C是一种静态类 型语言。其数据类型是在编译期就确定的&#xff0c;不能在运…

MySQL的系统信息函数

系统信息函数让你更好的使用MySQL数据库 1、version()函数 查看MySQL系统版本信息号 select version();2、connection_id()函数 查看当前登入用户的连接次数 直接调用CONNECTION_ID()函数--不需任何参数--就可以看到当下连接MySQL服务器的连接次数&#xff0c;不同时间段该…

(数据结构)顺序表的插入删除

#include<stdio.h> #include<stdlib.h> #define MAX 10 typedef struct {int data[MAX];int lenth; }List; //初始化 void CreateList(List* L) {L->lenth 0;for (int i 0; i < MAX; i){L->data[i] 0;} } //插入 int ListInsert(List* L,int i,int e) …

ESP32-Web-Server编程- 实现 Web 登录网页

ESP32-Web-Server编程- 实现 Web 登录网页 概述 是时候实现更加安全的网页了。登录机制是最简单的控制网页访问权限的方法。 需求及功能解析 本节演示如何在 ESP32 上部署一个 Web 服务器&#xff0c;并建立登录页面的机制&#xff0c;用户可以实现登录、登出的功能&#x…

算法题-统计字符个数(Python题解)

文章目录 前言思路code 前言 先前笔试做了一道算法题&#xff0c;题目是这样子的&#xff1a;&#xff08;PS&#xff1a;不用惊讶&#xff0c;是的&#xff0c;我不打算24今年考研了&#xff0c;一是&#xff0c;当初填报的学校不是我想要去的学校&#xff08;当初想一战成硕…