架构-微服务-服务网关

文章目录

  • 前言
  • 一、网关介绍
    • 1. 什么是API网关
    • 2. 核心功能特性
    • 3. 解决方案
  • 二、Gateway简介
  • 三、Gateway快速入门
    • 1. 基础版
    • 2. 增强版
    • 3. 简写版
  • 四、Gateway核心架构
    • 1. 基本概念
    • 2. 执行流程
  • 五、Gateway断言
    • 1. 内置路由断言工厂
    • 2. 自定义路由断言工厂
  • 六、过滤器
    • 1. 基本概念
    • 2. 局部过滤器
    • 3. 全局过滤器
  • 七、网关限流


前言

服务网关--Gateway

  在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:

  • 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
  • 认证复杂,每个服务都需要独立认证。
  • 存在跨域请求,在一定场景下处理相对复杂

  那么应该怎么解决呢, 这时候就需要通过分布式架构中的网关中间件实现路由分发和负载均衡


一、网关介绍

1. 什么是API网关

  所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。

  系统架构图如下所示:

API网关

2. 核心功能特性

Gateway网关是服务的守门神,所有微服务的统一入口。

  • 权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
  • 路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
  • 限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

3. 解决方案

在业界比较流行的网关,有下面这些:

  • Ngnix+lua
    使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用。
    lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本。

  • Kong
    基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:
    只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。

  • Zuul
    Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx。

  • Spring Cloud Gateway
    Spring组织为了替换Zuul而开发的网关服务,将在下面具体介绍。

注意:SpringCloud Alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关。

总结:Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

二、Gateway简介

  Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。

优点:
性能强劲:是第一代网关Zuul的1.6倍。
功能强大:内置了很多实用的功能,例如转发、监控、限流等。
设计优雅,容易扩展。

缺点:
其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高。
不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行。
需要Spring Boot 2.0及以上的版本,才支持。

三、Gateway快速入门

要求: 通过浏览器访问api网关,然后通过网关将请求转发到商品微服务

1. 基础版

第1步:创建一个 api-gateway 的模块,导入相关依赖

<?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>springcloud-alibaba</artifactId><groupId>com.itheima</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>api-gateway</artifactId><dependencies><!--gateway网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency></dependencies>
</project>

第2步: 创建主类

package com.itheima;@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}

第3步: 添加配置文件

server:port: 7000
spring:application:name: api-gatewaycloud:gateway:routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]- id: product_route # 当前路由的标识, 要求唯一uri: http://localhost:8081 # 请求要转发到的地址order: 1 # 路由的优先级,数字越小级别越高predicates: # 断言(就是路由转发要满足的条件)- Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改- StripPrefix=1 # 转发之前去掉1层路径

第4步: 启动项目, 并通过网关去访问微服务

服务网关基础班测试

2. 增强版

  现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从注册中心获取此地址.

第1步:加入nacos依赖

<!--nacos客户端-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第2步:在主类上添加注解

@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {public static void main(String[] args) {SpringApplication.run(ApiGatewayApplication.class, args);}
}

第3步:修改配置文件

server:port: 7000
spring:application:name: api-gatewaycloud:nacos:discovery:server-addr: 127.0.0.1:8848gateway:discovery:locator:enabled: true # 让gateway可以发现nacos中的微服务routes:- id: product_routeuri: lb://service-product # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略predicates:- Path=/product-serv/**filters:- StripPrefix=1

第4步:测试

服务网关增强版测试

3. 简写版

第1步:去掉关于路由的配置

server:port: 7000
spring:application:name: gatewaycloud:nacos:discovery:server-addr: 127.0.0.1:8848gateway:discovery:locator:enabled: true

第2步: 启动项目,并通过网关去访问微服务

服务网关简写版测试这时候,就发现只要按照网关地址/微服务/接口的格式去访问,就可以得到成功响应。

四、Gateway核心架构

1. 基本概念

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

  • id,路由标识符,区别于其他 Route。
  • uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
  • order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
  • predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
  • filter,过滤器用于修改请求和响应信息。

2. 执行流程

gateway执行流程
执行流程大体如下:

  1. Gateway Client向Gateway Server发送请求。
  2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文。
  3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping。
  4. RoutePredicateHandlerMapping负责路由查找,匹配路由信息,并根据路由,断言判断路由是否可用。
  5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用过滤器。
  6. 请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应。

五、Gateway断言

  Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。
断言就是说: 在 什么条件下 才能进行路由转发

1. 内置路由断言工厂

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:

  • 基于Datetime类型的断言工厂
    此类型的断言根据时间做判断,主要有三个:
    AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
    BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
    BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

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

  • 基于远程地址的断言工厂
    RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中。

-RemoteAddr=192.168.1.1/24

  • 基于Cookie的断言工厂
    CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。

-Cookie=chocolate, ch.

  • 基于Header的断言工厂
    HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。

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

  • 基于Host的断言工厂
    HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org

  • 基于Method请求方法的断言工厂
    MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

-Method=GET

  • 基于Path请求路径的断言工厂
    PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

-Path=/foo/{segment}

  • 基于Query请求参数的断言工厂
    QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

-Query=baz, ba.

  • 基于路由权重的断言工厂
    WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。

routes:
-id: weight_route1 uri: host1 predicates:
-Path=/product/**
-Weight=group3, 1
-id: weight_route2 uri: host2 predicates:
-Path=/product/**
-Weight= group3, 9

内置路由断言工厂的使用
接下来我们验证几个内置断言的使用:

server:port: 7000spring:application:name: api-gatewaycloud:nacos:discovery:server-addr: 127.0.0.1:8848gateway:discovery:locator:enabled: trueroutes:- id: product_routeuri: lb://service-productpredicates:- Path=/product-serv/**- Before=2019-11-28T00:00:00.000+08:00 #限制请求时间在2019-11-28之前- Method=POST #限制请求方式为POSTfilters:- StripPrefix=1

2. 自定义路由断言工厂

我们来设定一个场景: 假设我们的应用仅仅让age在(min,max)之间的人来访问。

第1步:在配置文件中,添加一个Age的断言配置

spring:
application:name: api-gateway
cloud:nacos:discovery:server-addr: 127.0.0.1:8848gateway:discovery:locator:enabled: trueroutes:- id: product-routeuri: lb://service-productpredicates:- Path=/product-serv/**- Age=18,60 # 限制年龄只有在18到60岁之间的人能访问filters:- StripPrefix=1

第2步:自定义一个断言工厂, 实现断言方法

package com.itheima.predicates;//泛型 用于接收一个配置类,配置类用于接收中配置文件中的配置
@Component
public class AgeRoutePredicateFactory
extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {public AgeRoutePredicateFactory() {super(AgeRoutePredicateFactory.Config.class);}//用于从配置文件中获取参数值赋值到配置类中的属性上@Overridepublic List<String> shortcutFieldOrder() {//这里的顺序要跟配置文件中的参数顺序一致return Arrays.asList("minAge", "maxAge");}//断言@Overridepublic Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Configconfig) {return new Predicate<ServerWebExchange>() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {//从serverWebExchange获取传入的参数String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");if (StringUtils.isNotEmpty(ageStr)) {int age = Integer.parseInt(ageStr);return age > config.getMinAge() && age < config.getMaxAge();}return true;}};}
}//自定义一个配置类, 用于接收配置文件中的参数
@Data
class Config {private int minAge;private int maxAge;
}

第4步:启动测试

#测试发现当age在(20,60)可以访问,其它范围不能访问
http://localhost:7000/product-serv/product/1?age=30
http://localhost:7000/product-serv/product/1?age=10

六、过滤器

1. 基本概念

  网关过滤器(Gateway Filter)是微服务架构中,处理进出网络请求的重要组件,它主要用于拦截并链式处理web请求。实现横切与应用无关的需求,如安全、访问控制、缓存、日志记录、负载均衡等。

  1. 作用:过滤器就是在请求的传递过程中,对HTTP请求和响应进行必要的修改或处理。

  2. 生命周期:

  • **Pre **: 在请求被路由之前调用。可用来实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • Post:在路由到微服务以后执行。可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  1. 根据其作用范围分为:
  • 局部过滤器(GatewayFilter),应用到单个路由或者一个分组的路由上。
  • 全局过滤器(GlobalFilter),应用到所有的路由上。

网管过滤器

2. 局部过滤器

  局部过滤器是针对单个路由的过滤器。

  1. 内置局部过滤器

  在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:

过滤器工厂作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
SaveSession在转发请求之前,强制执行WebSession::save操作
secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
SetPath修改原始的请求路径修改后的路径
SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
SetStatus修改原始响应的状态码HTTP状态码,可以是数字,也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
Retry针对不同的响应进行重试retries、statuses、methods、series
RequestSize设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回413PayloadTooLarge请求包大小,单位为字节,默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容

内置局部过滤器的使用

server:port: 7000spring:application:name: api-gatewaycloud:nacos:discovery:server-addr: localhost:8848gateway:discovery:locator:enabled: trueroutes:- id: product_routeuri: lb://service-productorder: 1predicates:- Path=/product-serv/**filters:- StripPrefix=1- SetStatus=2000 # 修改返回状态
  1. 自定义局部过滤器

第1步:在配置文件中,添加一个Log的过滤器配置

spring:application:name: gatewaycloud:nacos:discovery:server-addr: 127.0.0.1:8848gateway:discovery:locator:enabled: trueroutes:- id: consumerorder: -1uri: lb://consumerpredicates:- Path=/consumer-serv/**filters:- StripPrefix=1- Log=true,false # 控制日志是否开启

第2步:自定义一个过滤器工厂,实现方法

//自定义局部过滤器
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {//构造函数public LogGatewayFilterFactory() {super(LogGatewayFilterFactory.Config.class);}//读取配置文件中的参数 赋值到 配置类中@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("consoleLog", "cacheLog");}//过滤器逻辑@Overridepublic GatewayFilter apply(LogGatewayFilterFactory.Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {if (config.isCacheLog()) {System.out.println("cacheLog已经开启了....");}if (config.isConsoleLog()) {System.out.println("consoleLog已经开启了....");}return chain.filter(exchange);}};}//配置类 接收配置参数@Data@NoArgsConstructorpublic static class Config {private boolean consoleLog;private boolean cacheLog;}
}

第3步:启动测试

3. 全局过滤器

  全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

  1. 内置全局过滤器

  在SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

网关全局过滤器
2. 自定义全局过滤器

  内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。

开发中的鉴权逻辑:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)。
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证。
  • 以后每次请求,客户端都携带认证的token。
  • 服务端对token进行解密,判断是否有效。

网关自定义全局过滤器
如上图,对于验证用户是否已经登录鉴权的过程可以在网关统一检验。
检验的标准就是请求中是否携带token凭证以及token的正确性

下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。

package com.itheima.filters;//自定义全局过滤器需要实现GlobalFilter和Ordered接口
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {//完成判断逻辑@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String token = exchange.getRequest().getQueryParams().getFirst("token");if (StringUtils.isBlank(token)) {System.out.println("鉴权失败");exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}//调用chain.filter继续向下游执行return chain.filter(exchange);}//顺序,数值越小,优先级越高
@Override
public int getOrder() {return 0;}
}

七、网关限流

  网关是所有请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们本次采用前面学过的Sentinel组件来实现网关的限流。Sentinel支持Spring Cloud Gateway、Zuul等主流网关进行限流。

网关限流
从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:

  • route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId
  • 自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组
  1. 导入依赖
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
  1. 编写配置类

  基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。

@Configuration
public class GatewayConfiguration {private final List<ViewResolver> viewResolvers;private final ServerCodecConfigurer serverCodecConfigurer;public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);this.serverCodecConfigurer = serverCodecConfigurer;}// 初始化一个限流的过滤器@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public GlobalFilter sentinelGatewayFilter() {return new SentinelGatewayFilter();}// 配置初始化的限流参数@PostConstructpublic void initGatewayRules() {Set<GatewayFlowRule> rules = new HashSet<>();rules.add(new GatewayFlowRule("product_route") //资源名称,对应路由id.setCount(1) // 限流阈值.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒);GatewayRuleManager.loadRules(rules);}// 配置限流的异常处理器@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SentinelGatewayBlockExceptionHandlersentinelGatewayBlockExceptionHandler() {return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);}// 自定义限流异常页面@PostConstructpublic void initBlockHandlers() {BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {Map map = new HashMap<>();map.put("code", 0);map.put("message", "接口被限流了");return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(BodyInserters.fromObject(map));}};GatewayCallbackManager.setBlockHandler(blockRequestHandler);}
}
  1. 测试

在一秒钟内多次访问http://localhost:7000/product-serv/product/1就可以看到限流启作用了。

网关限流测试
4. 自定义API分组

自定义API分组是一种更细粒度的限流规则定义

	/*** 配置初始化的限流参数*/@PostConstructpublic void initGatewayRules() {Set<GatewayFlowRule> rules = new HashSet<>();rules.add(new GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1));rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1));GatewayRuleManager.loadRules(rules);}//自定义API分组@PostConstructprivate void initCustomizedApis() {Set<ApiDefinition> definitions = new HashSet<>();ApiDefinition api1 = new ApiDefinition("product_api1").setPredicateItems(new HashSet<ApiPredicateItem>() {{// 以/product-serv/product/api1 开头的请求add(new ApiPathPredicateItem().setPattern("/productserv/product/api1/**").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));}});ApiDefinition api2 = new ApiDefinition("product_api2").setPredicateItems(new HashSet<ApiPredicateItem>() {{// 以/product-serv/product/api2/demo1 完成的url路径匹配add(new ApiPathPredicateItem().setPattern("/productserv/product/api2/demo1"));}});definitions.add(api1);definitions.add(api2);GatewayApiDefinitionManager.loadApiDefinitions(definitions);}

本文的引用仅限自我学习如有侵权,请联系作者删除。
参考知识
传智教育·黑马程序员


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

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

相关文章

idea怎么打开两个窗口,运行两个项目

今天在开发项目的时候&#xff0c;前端希望运行一下以前的项目&#xff0c;于是就需要开两个 idea 窗口&#xff0c;运行两个项目 这里记录一下如何设置&#xff1a;首先依次点击&#xff1a; File -> Settings -> Appearance & Behavior ->System Settings 看到如…

零碎04 MybatisPlus自定义模版生成代码

目录 背景 动手开干 需要的依赖包&#xff0c;需要注意mybatis-plus-generator的3.5版本是没有兼容历史版本的。 定义一个CodeGenerator类&#xff0c;负责生成代码和配置属性 Entity模版 背景 MybatisPlus代码生成使用默认的velocity模版解决不了定制化的需求&#xff0…

CentOS8.5.2111(7)完整的Apache综合实验

一、实验目标 1.掌握Linux系统中Apache服务器的安装与配置&#xff1b; 2.掌握个人主页、虚拟目录、基于用户和主机的访问控制及虚拟主机的实现方法。 二、实验要求 练习使用linux系统下WEB服务器的配置方法。 三、实验背景 重庆工程学院为筹备“重庆工程大学”特申请了c…

Cocos编辑器

1、下载 下载地址&#xff1a;https://www.cocos.com/creator-download 2、编辑器界面介绍 官方链接&#xff1a;https://docs.cocos.com/creator/3.8/manual/zh/editor/ 3、项目结构 官方链接&#xff1a;https://docs.cocos.com/creator/3.8/manual/zh/getting-started/…

Easyui 实现订单拆分开票功能

Easyui 实现订单拆分开票功能 需求 1、实现一个订单开具多分发票功能&#xff1b; 2、支持拆行&#xff1b; 3、支持拆数量&#xff1b; 流程设计 1、操作页面展示订订单头信息&#xff0c;订单明细信息 2、点击新增发票按钮弹出一个弹出框用于创建一张拆分发票&#xff0c;弹…

解决 java -jar 报错:xxx.jar 中没有主清单属性

问题复现 在使用 java -jar xxx.jar 命令运行 Java 应用程序时&#xff0c;遇到了以下错误&#xff1a; xxx.jar 中没有主清单属性这个错误表示 JAR 文件缺少必要的启动信息&#xff0c;Java 虚拟机无法找到应用程序的入口点。本文将介绍该错误的原因以及如何通过修改 pom.xm…

推荐一款龙迅HDMI2.0转LVDS芯片 LT6211UX LT6211UXC

龙迅的HDMI2.0转LVDS芯片LT6211UX和LT6211UXC是两款高性能的转换器芯片&#xff0c;它们在功能和应用上有所差异&#xff0c;同时也存在一些共同点。以下是对这两款芯片的详细比较和分析&#xff1a; 一、LT6211UX 主要特性&#xff1a; HDMI2.0至LVDS和MIPI转换器。HDMI2.0输…

flink学习(7)——window

概述 窗口的长度(大小): 决定了要计算最近多长时间的数据 窗口的间隔: 决定了每隔多久计算一次 举例&#xff1a;每隔10min,计算最近24h的热搜词&#xff0c;24小时是长度&#xff0c;每隔10分钟是间隔。 窗口的分类 1、根据window前是否调用keyBy分为键控窗口和非键控窗口…

C语言解析命令行参数

原文地址&#xff1a;C语言解析命令行参数 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 C语言有一个 getopt 函数&#xff0c;可以对命令行进行解析&#xff0c;下面给出一个示例&#xff0c;用的时候可以直接copy过去修改&#xff0c;很方便…

精密工装夹具加工:打造高精度产品

在现代制造业中&#xff0c;精密工装夹具加工扮演着关键角色&#xff0c;是打造高精度产品不可缺少的环节。 精密工装夹具的设计与制造&#xff0c;首先依赖于对加工工艺的深入理解与精准把握。工程师们需要根据待加工产品的形状、尺寸、精度要求以及加工设备的特性&#xff0c…

C++ 优先算法 —— 无重复字符的最长子串(滑动窗口)

目录 题目&#xff1a; 无重复字符的最长子串 1. 题目解析 2. 算法原理 Ⅰ. 暴力枚举 Ⅱ. 滑动窗口&#xff08;同向双指针&#xff09; 3. 代码实现 Ⅰ. 暴力枚举 Ⅱ. 滑动窗口 题目&#xff1a; 无重复字符的最长子串 1. 题目解析 题目截图&#xff1a; 此题所说的…

huggingface使用

import warnings warnings.filterwarnings("ignore") from transformers import pipeline#用人家设计好的流程完成一些简单的任务 classifier pipeline("sentiment-analysis") classifier( [ "Ive been waiting for a HuggingFace cours…

第六届机器人、智能控制与人工智能国际(RICAI 2024)

会议信息 会议时间与地点&#xff1a;2024年12月6-8日&#xff0c;中国南京 会议官网&#xff1a;www.ic-ricai.org &#xff08;点击了解大会参会等详细内容&#xff09; 会议简介 第六届机器人、智能控制与人工智能国际学术会议&#xff08;RICAI 2024&#xff09;将于20…

【设计模式】创建型模式之单例模式(饿汉式 懒汉式 Golang实现)

定义 一个类只允许创建一个对象或实例&#xff0c;而且自行实例化并向整个系统提供该实例&#xff0c;这个类就是一个单例类&#xff0c;它提供全局访问的方法。这种设计模式叫单例设计模式&#xff0c;简称单例模式。 单例模式的要点&#xff1a; 某个类只能有一个实例必须…

C++11特性(详解)

目录 1.C11简介 2.列表初始化 3.声明 1.auto 2.decltype 3.nullptr 4.范围for循环 5.智能指针 6.STL的一些变化 7.右值引用和移动语义 1.左值引用和右值引用 2.左值引用和右值引用的比较 3.右值引用的使用场景和意义 4.右值引用引用左值及其一些更深入的使用场景分…

C++-右值引用和移动构造

目录 1. 两种引用方式: 1.1 左值引用&#xff1a; 1.2右值引用 1.3如何判断左右值&#xff1a; 1.4左值引用与右值引用比较 2. 浅拷贝、深拷贝 3.1右值引用的意义&#xff1a; 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forward &#x1f33c;&…

新能源汽车充电插口类型识别-YOLO标记,可识别Type1,ccs2的充电标准

前言: CCS标准定义的Type-2 CCS汽车充电端口&#xff0c;右侧装有直流充电枪的插头。汽车的充电端口设计巧妙地将交流部分&#xff08;上半部分&#xff09;与直流部分&#xff08;下半部分的两个粗大的接口&#xff09;集于一体。在交流和直流充电过程中&#xff0c;电动汽车…

Pytest使用Jpype调用jar包报错:Windows fatal exception: access violation

问题描述 ​   之前我们有讲过如何使用Jpype调用jar包&#xff0c;在成功调用jar包后&#xff0c;接着在Pytest框架下编写自动测试用例。但是在Pytest下使用Jpype加载jar包&#xff0c;并调用其中的方法会以下提示信息&#xff1a; ​   虽然提示信息显示有Windows显示致命…

Netty基本原理

目录 前言 原生NIO VS Netty 原生NIO存在的问题 Netty的优点 线程模型 传统阻塞 I/O (Blocking I/O) 2. 非阻塞 I/O (Non-blocking I/O) 3. 多路复用 I/O (Multiplexed I/O) 4. Reactor 模式 常见的 Reactor 模式的变体&#xff1a; Netty线程模型 工作原理 前言 N…

MySQL系列之数据类型(Numeric)

导览 前言一、数值类型综述二、数值类型详解1. NUMERIC1.1 UNSIGNED或SIGNED1.2 数据类型划分 2. Integer类型取值和存储要求3. Fixed-Point类型取值和存储要求4. Floating-Point类型取值和存储要求 结语精彩回放 前言 MySQL系列最近三篇均关注了和我们日常工作或学习密切相关…