官网文档: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/

1. 概述

1.1 什么是网关

微服务架构里面还有一个非常重要的组件,就是网关,

在Spring Cloud 全家桶里面也有这个角色, 在 1.x 版本中 采用的是 Zuul 网关,

但是因为 zuul的升级一直跳票,一直放鸽子, Spring Cloud 在2.x 中研发了一个自己的网关 替代了 Zuul, 那就是 Gateway

网关常见的功能有路由转发、权限校验、限流控制等作用。

例如在微服务架构中, 如果可以使用网关对 请求进行转发, 前端只需访问一个地址,并携带需要调用的目标地址,由网关进行统一管理. 并且在请求过程中 对请求进行过滤,鉴权,使 微服务的 服务地址不直接暴露,保护了 微服务节点的安全

微服务架构中网关的位置:

1.2 Gateway 网关的基本特性

  1. Gateway 是在Spring 生态体系之上构建的 API 网关服务,由于底层使用 netty, 所以是基于 Spring5, Spring Boot2 和 Project Reactor等技术,Gateway旨在 提供一种简单而有效的方式来对 API 进行路由, 以及提供一些强大的过滤器功能,例如 熔断,限流,重试等
  2. Gateway作为 Spring Cloud 生态体系中的网关,目标是替代 Zuul, 在Spring Cloud 2.0 以上的版本中,没有对新版本的Zuul 2.0 以上最新高性能版本进行集成, 仍然使用老的 非 Reactor 模式的 ,
  3. 为了提高性能, Gateway 是基于 WebFlux框架实现的, 而WebFlux 框架底层使用了高性能的 Reactor 模式的通信框架 Netty
  4. Gateway 的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如 : 安全, 监控/指标, 和限流

源码架构:

什么是WebFlux

这里做基本介绍,详细请自行官网学习

  1. 传统的 Web框架, 比如说 spring mvc 等 都是 Servlet API 与 Servlet 容器基础上运行的
  2. 但是, 在 Servlet 3.1 之后, 有了异步非阻塞的支持, 而WebFlux 是一个典型的非阻塞异步的框架,它的核心是基于Reactor模式的相关API 实现的,相对于传统的web 框架来说, 它可以 运行在诸如Netty,Undertow 等支持Servlet3.1 的容器上, 非阻塞+ 函数式编程(Spring5 必须使用java8)
  3. Spring WebFlux 是 Spring 5.0 引入的新的响应式框架, 区别于Springmvc, 它不需要依赖 Servlet API ,他是完全异步非阻塞的,并且基于Reactor模式来实现响应式流规范

基本核心组件

Gateway 三个组件

  • 路由: 网关的基本构建模块,它是由ID、目标URl、断言集合和过滤器集合定义, 如果集合断言为真,则匹配路由。
  • Predicate(断言):这是java 8的一个函数式接口predicate,可以用于lambda表 达式和方法引用,输入类型是:Spring Framework ServerWebExchange,允许 开发人员匹配来自HTTP请求的任何内容,例如请求头headers和参数paramers ,如果请求与断言相匹配,则进行对应的路由
  • Filter(过滤器):这些是使用特定工厂构建的Spring Framework GatewayFilter 实例,这里可以在发送请求之前或之后修改请求和响应

2. 基本使用

2.1 工程搭建及测试

需要搭建 Gateway 网关的微服务, 并注册到注册中心.

pom依赖:

    <dependencies>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- &lt;!&ndash; web Gateway不需要web依赖 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> </dependencies>

yml配置:

server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址,即路由跳转地址
predicates:
- Path=/payment/get/** #路径类型的断言,路径相匹配的则匹配路由 - id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由 eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

主启动类:

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

上面的代码中, 将微服务启动, 并注册到7001 微服务,并在配置文件中, 对 路径/payment/get/**/payment/lb/** 进行拦截,这就是断言,若断言为true,则匹配该路由,并跳转到对应的uri属性下的 地址中

测试结果:

  • 直接访问 8001 微服务的 接口 http://127.0.0.1:8001/payment/lb

    返回结果 : "8001" 此接口返回 8001 微服务的端口

  • 访问 9527 Gateway 微服务的 地址: http://127.0.0.1:9527/payment/lb

    断言成功, 跳转路由, 返回结果: "8001", 成功调用

若访问的路径,没有任何路由匹配,则报错404:

2.2 编码方式配置路由

上面使用 yml 配置文件的方式进行 配置路由规则, 也可以使用编码的方式进行配置

下面我们使用编码的方式配置路由,跳转到百度的国内新闻

@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
//路由构建器
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//配置路由规则,对比 yml 文件配置
// id: path_route , predicates: /guonei , uri: http://news.baidu.com/guonei
routes.route("path_route"
, r->r.path("/guonei").uri("http://news.baidu.com/guonei"))
.build();
return routes.build();
}
}

下面进行测试:

  • 直接访问百度国内新闻 http://news.baidu.com/guonei,成功跳转
  • 通过Gateway 微服务访问 http://127.0.0.1:9527/guonei ,也可以跳转

2.3 使用微服务名跳转

上面的代码中,我们跳转到某个微服务,都是 直接写对方的ip 地址,

Gateway 会自动 从注册中心中获取服务列表, 可以通过微服务的名称作为路由转发,那么上面的代码就不用写成

http://localhost:8001 而是 lb://cloud-payment-service lb 为负载均衡,若该微服务有两个实现,则进行负载均衡

代码演示:

首先必须先开启注册中心路由功能: spring.cloud.gateway.discovery.locator.enabled=true

pom修改:

server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由 eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

开启 8001,8002 微服务,调用9527地址: http://127.0.0.1:9527/payment/lb,

轮流返回 "8001","8002" 对应微服务的地址,调用成功,并负载均衡

3. 断言工厂

3.1 概述

Gateway网关中 另一个非常重要的组件: 断言, 它决定一个请求是否由匹配此路由. 在前面的案例中使用的就是其中的Path 断言工厂生成的 断言类, 并匹配请求,跳转到指定路径,

GateWay给我们提供了很多不同类型的断言工厂,都是抽象类AbstractRoutePredicateFactory 的子类

详细使用,请查看官网文档 : https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories

分类:

时间相关:

  • AfterRoutePredicateFactory: 匹配在指定日期时间之后发生的请求

    示例:

    # 表示在 2017年1月20日17:42:47 之后
    #此时间格式 可以使用 ZonedDateTime 类获取
    #ZonedDateTime.now(); // 默认时区
    #ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
    predicates:
    - After=2017-01-20T17:42:47.789-07:00[America/Denver]
  • BeforeRoutePredicateFactory 匹配在指定日期时间之前发生的请求

  • BetweenRoutePredicateFactory 匹配在datetime1之后和在datetime2之前的请求。该datetime2参数必须datetime1之后

    示例:

    #表示在2017年1月20日17:42:47之后 并且 在2017年1月21日17:42:47之前
    predicates:
    - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

Cookie相关:

  • CookieRoutePredicateFactory 匹配具有指定Cookie,并且值与指定的正则匹配的请求

    示例:

    # 表示Cookie 中携带 键为chocolate,值为可以匹配正则"ch.p" 的字符串
    predicates:
    - Cookie=chocolate, ch.p

Header相关:

  • HeaderRoutePredicateFactory, 匹配 请求头中有指定的名,并且值匹配指定的正则表达式

    示例:

    predicates:
    - Header=X-Request-Id, \d+
  • HostRoutePredicateFactory, 匹配Host 域名列表

    示例:

    # 匹配路径中host 为 *.baidu.com 的 和 *.souhu.com的
    predicates:
    - Host=**.baidu.com,**.sohu.com
  • RemoteAddrRoutePredicateFactory ,匹配请求的Remote(客户端i来源ip)

    示例:

    # 匹配Remote 为 192.168.1.1 至 192.168.1.254
    # 斜杠后面的24 表示最后一位的最大值 即254
    #16 表示最后两位 即 255.254
    #8 表示最后三位 即 255.255.254
    predicates:
    - RemoteAddr=192.168.1.1/24

请求相关:

  • MethodRoutePredicateFactory 匹配指定的请求方式

    示例:

    #匹配 GET,POST 请求
    predicates:
    - Method=GET,POST
  • QueryRoutePredicateFactory 匹配请求有指定的参数key,并且值匹配指定的正则

    示例:

    # 请求键为 red 值匹配正则 "gree."
    predicates:
    - Query=red, gree.
  • PathRoutePredicateFactory 匹配url 路径,也就是我们上面案例中用到的

3.2 断言工厂的工作原理

下面使用MethodRoutePredicateFactory 来进行演示

源码

public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
/** @deprecated */
@Deprecated
public static final String METHOD_KEY = "method";
public static final String METHODS_KEY = "methods"; public MethodRoutePredicateFactory() {
super(MethodRoutePredicateFactory.Config.class);
}
/**
* 封装是 config 类中使用哪个字段接受参数
*/
public List<String> shortcutFieldOrder() {
return Arrays.asList("methods");
} public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
} /**
* 实际生产 断言操作类的方法
*/
public Predicate<ServerWebExchange> apply(MethodRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
/**
* 断言操作类的test方法,判断请求是否符合条件
*/
public boolean test(ServerWebExchange exchange) {
HttpMethod requestMethod = exchange.getRequest().getMethod();
return Arrays.stream(config.getMethods()).anyMatch((httpMethod) -> {
return httpMethod == requestMethod;
});
} public String toString() {
return String.format("Methods: %s", Arrays.toString(config.getMethods()));
}
};
} /**
* 配置类,接受配置文件中的配置的信息,
*/
@Validated
public static class Config {
// 一个枚举数组, 接受请求方式,例如 [GET,POST]
private HttpMethod[] methods; public Config() {
} /** @deprecated */
@Deprecated
public HttpMethod getMethod() {
return this.methods != null && this.methods.length > 0 ? this.methods[0] : null;
} /** @deprecated */
@Deprecated
public void setMethod(HttpMethod method) {
this.methods = new HttpMethod[]{method};
} public HttpMethod[] getMethods() {
return this.methods;
} public void setMethods(HttpMethod... methods) {
this.methods = methods;
}
}
}

上面的代码中, 可以看出其实MethodRoutePredicateFactory 的实现比较简单.生产一个GatewayPredicate进行断言.主要做了如下两个操作

  • 获取配置文件中配置的参数
  • 判断请求的方法是否匹配其中任意一个参数

3.3 自定义断言工厂

根据上面的规则,我们可以实现自己的自定义断言工厂

接收参数的Config 类:

public class TulingTimeBetweenConfig {

    private LocalTime startTime;

    private LocalTime endTime;

    public LocalTime getStartTime() {
return startTime;
} public void setStartTime(LocalTime startTime) {
this.startTime = startTime;
} public LocalTime getEndTime() {
return endTime;
} public void setEndTime(LocalTime endTime) {
this.endTime = endTime;
}
}

断言工厂类,注意工厂类的类名必须以"RoutePredicateFactory"开头, "RoutePredicateFactory" 之前的一部分则作为配置文件中的键

@Component
public class TulingTimeBetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<TulingTimeBetweenConfig> { public TulingTimeBetweenRoutePredicateFactory() {
super(TulingTimeBetweenConfig.class);
}
/**
* 真正的业务判断逻辑
*/
@Override
public Predicate<ServerWebExchange> apply(TulingTimeBetweenConfig config) { LocalTime startTime = config.getStartTime(); LocalTime endTime = config.getEndTime(); return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
LocalTime now = LocalTime.now();
//判断当前时间是否在在配置的时间范围类
return now.isAfter(startTime) && now.isBefore(endTime);
}
}; } /**
* 用于接受yml中的配置 ‐ TulingTimeBetween=上午7:00,下午11:00
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("startTime", "endTime");
} }

yaml配置文件,使用逗号分隔

predicates:
- TulingTimeBetween=上午7:00,下午11:00

测试,当请求时间为上午七点到下午十一点前的所有请求,都会被拦截

4. 过滤器工厂

上面的操作中,我们仅仅只是将 请求拦截,并跳转到某个地址,貌似没做什么操作,作用很小,下面介绍过滤器的使用,将在拦截过程中做一些操作

,SpringCloudGateway 也提供了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等.

官网文档: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

4.1 常用过滤器简单介绍

下面简单介绍几种常用的:

添加请求头:

给拦截到请求中加入指定的请求头和指定的值

predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ AddRequestHeader=X‐Request‐Company,tuling

添加请求参数

给请求加上指定的 Parameter 参数,和指定的值

predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ AddRequestParameter=company,tuling

为匹配的路由统一添加前缀

给请求加上指定的前缀,比如下面的配置中,请求http://localhost:8888/selectProductInfoById/1会转发到

http://localhost:8888/product‐api/selectProductInfoById/1

predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ PrefixPath=/product‐api

更多详细用户请参考官网,提供了丰富的过滤器工厂

4.1 自定义过滤器工厂

过滤器工厂的实现思路和断言工厂类似,也可以参考着自定义自己的过滤器工厂,下面我们来实现一个记录整个过滤链执行时间的过滤器工厂类

过滤器操作类:

在查看源码过程中,发现其过滤器工厂返回过滤器操作类代码中,都是使用匿名内部类的方式,但是这样过滤器的执行顺序无法保证,只能按照加载顺序执行,所以这里我们将操作类单独定义,实现Ordered 接口,保证加载顺序优先

public class TimeMonitorGatewayFilter implements GatewayFilter, Ordered {

    private static final String COUNT_START_TIME = "countStartTime";
private AbstractNameValueGatewayFilterFactory.NameValueConfig config; public TimeMonitorGatewayFilter(AbstractNameValueGatewayFilterFactory.NameValueConfig config) {
this.config = config;
} @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain
chain) {
//获取配置文件yml中的
// filters:
// ‐ TimeMonitor=enabled,true
String name = config.getName();
String value = config.getValue(); if (value.equals("false")) {
return null;
}
//在请求中记录开始时间
exchange.getAttributes().put(COUNT_START_TIME,
System.currentTimeMillis()); //then方法相当于aop的后置通知一样,当整个过滤链执行完毕时 ,将调用此方法,
return chain.filter(exchange).then(Mono.fromRunnable(new Runnable() {
@Override
public void run() {
//结束时间
Long startTime = exchange.getAttribute(COUNT_START_TIME);
//获取开始时间 并计算差值
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
sb.append(" params:").append(exchange.getRequest().getQueryParams());
//打印执行时间
System.out.println(sb.toString());
}
}
}));
} /**
* 数字越小 Spring 加载此类越优先
* @return
*/
@Override
public int getOrder() {
return -100;
}
}

此类在执行链开始时执行,并记录开始时间,并定义了结束过滤链结束时,计算差值

过滤器工厂类:

和断言工厂一样, 也是使用指定的后缀,来确定配置文件中的配置方式,必须为"GatewayFilterFactory" 结尾

并继承了 AbstractNameValueGatewayFilterFactory 类, 可以接受配置文件中的 name,value 形式的参数

但是本例中只使用 value来定义

@Component
public class TimeMonitorGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override
public GatewayFilter apply(NameValueConfig config) {
return new TimeMonitorGatewayFilter(config) ;
}
}

yaml配置文件:

接受到的参数 : name为enabled ,value为true,但是上面的代码中 只用到了 true参数,含义为开启此功能

predicates:
‐ Query=company,product
filters:
‐ TimeMonitor=enabled,true

测试: 调用本网关,[127.0.0.1:9527/payment/lb?name=10](http://127.0.0.1:9527/payment/lb?name=10)

打印日志信息: /payment/lb: 8ms params:{name=[10]}

4.3 自定义全局过滤器

GateWay 框架中,还有一种特殊的过滤器, 为全局过滤器,只要是被拦截的请求,都会被执行,上面的负载均衡功能就是

LoadBalancerClientFilter 全局过滤器起的作用

其他全局过滤器使用,请查看官网: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#global-filters

同样,我们也可以自定义全局过滤器:

@Component
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进来");
//获取 url上第一个 uname param
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
} return chain.filter(exchange);
} /**
* 过滤链的排序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}

上面的过滤器中,进行了简单的鉴权操作,若请求参数中没有username,则拒绝转发,

SpringCloud 网关组件Gateway的更多相关文章

  1. 小D课堂 - 新版本微服务springcloud+Docker教程_6-02 springcloud网关组件zuul

    笔记 2.SpringCloud的网关组件zuul基本使用     简介:讲解zuul网关基本使用 1.加入依赖 2.启动类加入注解 @EnableZuulProxy         默认集成断路器 ...

  2. SpringCloud系列之网关(Gateway)应用篇

    @ 目录 前言 项目版本 网关访问 鉴权配置 限流配置 前言 由于项目采用了微服务架构,业务功能都在相应各自的模块中,每个业务模块都是以独立的项目运行着,对外提供各自的服务接口,如没有类似网关之类组件 ...

  3. SpringCloud Alibaba(三) - GateWay网关

    1.基本环境搭建 1.1 依赖 <!-- Gatway 网关会和springMvc冲突,不能添加web依赖 --> <dependency> <groupId>or ...

  4. 微服务架构案例(05):SpringCloud 基础组件应用设计

    本文源码:GitHub·点这里 || GitEE·点这里 更新进度(共6节): 01:项目技术选型简介,架构图解说明 02:业务架构设计,系统分层管理 03:数据库选型,业务数据设计规划 04:中间件 ...

  5. Java生鲜电商平台-深入理解微服务SpringCloud各个组件的关联与架构

    Java生鲜电商平台-深入理解微服务SpringCloud各个组件的关联与架构 概述 毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术.不过大多数讲解还停留 ...

  6. SpringCloud及其组件详解

    SpringCloud及其组件详解 1.Spring Cloud 1.1 Spring Cloud和Dubbo的区别图解 1.2 微服务的技术栈 2.Spring Cloud 概述 2.1 Sprin ...

  7. 如何设计一个亿级网关(API Gateway)?

    1.背景 1.1 什么是API网关 API网关可以看做系统与外界联通的入口,我们可以在网关进行处理一些非业务逻辑的逻辑,比如权限验证,监控,缓存,请求路由等等. 1.2 为什么需要API网关 RPC协 ...

  8. 高质量API网关组件实现

    PI网关组件的作用? 1.网关直接代替MVC当中的Controller层,减少编码量提高开发效率 2.统一API接口的出入参格式,提高API的友好性 3.自动检测API接口规范,提高接口的质量 4.统 ...

  9. 包容网关 Inclusive Gateway

    包容网关 Inclusive Gateway 作者:Jesai 2018年3月25日 22:59:56 什么是包容网关? 包容网关(Inclusive Gateway)就是并行网关(Parallel ...

  10. 并行网关 Parallel Gateway

    并行网关 Parallel Gateway 作者:Jesai 2018年3月25日 00:26:21 前言: 做工作流时间长后,慢慢的就会发现,很多客户会需要会签的功能,会签的情况也有很多种,实现的方 ...

随机推荐

  1. Xmind永久会员版本

    Xmind软件不要多介绍了思维导图最好用的软件 PJ后可以直接使用高级版本功能如图 使用方式 下载我们提供的版本和.dll即可如图 点击Xmind安装默认C盘不可以自定义位置 安装完成后进入patch ...

  2. OpenIM Open Source Instant Messaging Project Docker Compose Deployment Guide

    The deployment of OpenIM involves multiple components and supports various methods including source ...

  3. TienChin 渠道管理-配置字典常量

    在字典管理当中添加渠道状态 channel_status:渠道状态 分别为: 正常,键值为1,回显样式为 success 禁用,键值为0,回显样式为 info !> 有个注意点:Vue3 当中 ...

  4. 21.11 Python 使用CRC图片去重

    使用CRC32还可实现图片去重功能,如下FindRepeatFile函数,运行后通过对所有文件做crc校验并将校验值存储至CatalogueDict字典内,接着依次提取CRC特征值并将其存储至Cata ...

  5. 字节码编程,Javassist篇五《使用Bytecode指令码生成含有自定义注解的类和方法》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 到本章为止已经写了四篇关于字节码编程的内容,涉及了大部分的API方法.整体来说对 J ...

  6. Windows系统phpstudy+PbootCMS搭建网站记录

    环境 Windows 10 phpstudy v8.1           下载地址:https://www.xp.cn/download.html PbootCMS v3.2.4  下载地址:htt ...

  7. Linux 环境下使用 sqlplus 访问远程 Oracle 数据库

    自己最近需要在 Oracle 生产环境检查一些数据,但是目前大多数的生产环境,出于安全考虑,不会提供图形界面让你使用类似 Navicat 工具让你直接访问数据库,网上找了很多资料,大部分都比较过时或者 ...

  8. 程序员减少BUG的两个小妙招!

    原创:陶朱公Boy(微信公众号ID:taozhugongboy),欢迎分享,转载请保留出处. ​ 点评: 我们说衡量一个程序员水平的高低往往有很多因素,但有一个因素至关重要即代码质量. 如果程序员写的 ...

  9. Pandas resample数据重采样

    随机抽样,是统计学中常用的一种方法,它可以帮助我们从大量的数据中快速地构建出一组数据分析模型.在 Pandas 中,如果想要对数据集进行随机抽样,需要使用 sample() 函数. sample() ...

  10. BZOJ3364 Distance Queries 距离咨询 题解

    原题 简化题意:有一棵 \(n\) 个点的树, \(q\) 组询问,每次询问回答两点间的距离. 令 \(dis[i][j]\) 表示 \(i\) 到 \(j\) 的距离,根节点为 \(rt\) ,则有 ...