当前位置: 首页 / 技术干货 / 正文
gateway介绍(四)Filter过滤器

2022-11-02

过滤器 请求

  Filter过滤器

  gateway中过滤器主要分为网关(路由)过滤器和全局过滤器两个,网关(路由)过滤器主要是个某个服务指定的过滤器,全局过滤器主要是给所有的路由设置的

  4.1 网关过滤器

  网关过滤器用于拦截并通过责任链处理web请求,修改http的请求传入数据和http响应的传出数据,可以实现AOP处理我们的与应用无关的操作,比如安全控制等,主要包含路径过滤器,header过滤器,参数过滤器,body过滤器,状态过滤器,会话过滤器,重定向过滤器,重试过滤器,限流RateLimiter过滤器,熔断器过滤器

  4.1.1 Path过滤器

  path过滤器主要是和请求路径相关的一些过滤

  4.1.1.1 RewritePathGatewayFilter

  这个过滤器的主要作用是重写路径, 可以将我们的请求地址从A重写为B再进行访问

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/abc/order/** #断言的条件是请求的地址符合这个表达式,注意格式为Path=/abc/order/**
filters:
- RewritePath=/abc(?/?.*), $\{segment} # 将/abc/order/**重写为/order/**,可以帮我们去掉前缀

  4.1.1.2 PrefixPathGatewayFilter

  这个过滤器的主要作用是给对应路由的添加一个统一的请求前缀地址

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/** #断言的条件是请求的地址符合这个表达式
filters:
- PrefixPath=/order #网关内部实际的访问地址要在上面的path前面添加这个前缀,需要注意,拼接完之后的地址需要在对应的服务中真实存在,否则会提示404,比如我们请求网关localhost:8080/1实际访问的是对应服务的/order/1这个地址

  4.1.1.3 StripPrefixGatewayFilter

  这个过滤器的主要作用是将用户的实际请求地址跳过一部分后进行重写转发,这样我们可以要求用户多传递某些路径,我们在内部裁剪后才访问,可以不暴露真实地址

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/abc/def/order/** #断言的条件是请求的地址符合这个表达式
filters:
#作用是将真实的请求地址的前面两层路径去掉,比如实际访问的地址是 /abc/def/order/1,会被修改为/order/1,想去掉几层写几
- StripPrefix=2

  4.1.1.4 SetPathGatewayFilter

  这个过滤器的作用是将请求路径从A转成B,并且可以将A中的某个参数的值填写到B地址中

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/abc/def/aaa/{segment} # 请求网关的时候必须匹配当前的路径格式, 最后的{segment}想当于 pathvariable
filters:
#将上面的请求地址转成下面的格式, 并将对应的参数设置到这里来,参数名和参数名对应即可
- SetPath=/order/{segment}

  4.1.1.5 RedirectToGatewayFilter

  重定向过滤器,向客户端返回指定的重定向地址

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/abc/def/aaa/{segment} # 请求网关的时候必须匹配当前的路径格式
filters:
- RedirectTo=302, https://www.baidu.com #设置重定向,返回状态码302,重定向到https://www.baidu.com

  4.1.2 参数过滤器

  4.1.2.1 AddRequestParameter

  这个过滤器的作用是网关会向下游服务添加某个参数,这样可以在不需要客户端传递某个参数的情况下传递额外参数,比如当前网关的标识等

spring:
cloud:
gateway:
routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates:
- Host: {segment}.myhost.org #必须是通过某个域名请求的才可以访问
filters:
- AddRequestParameter=foo, bar-{segment} #向下游添加一个名字叫foo,值为bar-{segment}的内容的数据,这个数据可以写死,也可以通过表达式获取,类似于上面pathvariable,根据实际情况决定

  4.1.2.2 RemoveRequestParameter

  这个的作用是将客户端传递的某个参数移除,不传递到下游服务

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/test3333 # 请求网关的时候必须匹配当前的路径格式
filters:
- RemoveRequestParameter=name #删除客户端传递的名字叫name的参数

  4.1.3 状态过滤器

  4.1.3.1 SetStatusGatewayFilter

  这个过滤器的主要作用是返回全局的状态码,不管是什么结果都返回指定的状态码

routes:
- id: 08consumer-eureka-feign # 当前路由策略的唯一ID,可以随便写
uri: lb://08CONSUMER-EUREKA-FEIGN #lb开头代表是负载均衡,意味着需要从注册中心获取数据
predicates: #配置断言, 符合下面断言的请求会转发到上面的url,断言很多种条件
- Path=/** # 请求网关的时候必须匹配当前的路径格式
filters:
#设置统一的状态码,不管怎么着都会返回404,注意要查看网络请求返回的状态码,不要只看页面,这里也可以写常量的名字
- SetStatus=404

  4.1.4 Header头部过滤器

  这些过滤器的操作都是和header相关的,包括添加,删除值等

  4.1.4.1 AddRequestHeaderGatewayFilter

  这个过滤器是向请求头中添加header,并传递到下游服务

filters:
- AddRequestHeader=X-Request-red, blue #向下游服务添加名字叫X-Request-red值为blue的请求头,这个头中的数据也可以向上面的过滤器一样,来自于其他配置参数的pathvariable

  4.1.4.2 AddResponseHeaderGatewayFilter

  这个过滤器的主要作用是向客户端的响应添加响应头的

filters:
- AddResponseHeader=foo, bar-{segment} #向客户端添加一个名字叫foo, 值叫bar-{segment}的数据,{segment}的值取决于配置的其他数据,类似于上面的的过滤器的pathvariable

  4.1.4.3 RemoveRequestHeader

  此过滤器的作用是将客户端传递的某个请求头删除,不向下游服务传递

filters:
- RemoveRequestHeader=X-Request-Foo #移除名字叫X-Request-Foo的请求头

  4.1.4.4 RemoveResponseHeader

  这个过滤器的作用是将下游服务返回给我们的某个请求头删除,不返回给客户端

filters:
- RemoveResponseHeader=X-Response-Foo #从下游服务中删除X-Response-Foo头数据

  4.1.4.5 SetRequestHeaderGatewayFilter

  这个过滤器的作用是设置某个客户端传递的请求头的数据,是替换掉用户传递的某个头,而不是添加

filters:
- SetRequestHeader=X-Request-Red, Blue #将请求头中X-Request-Red的值设置为Blue,不会添加

  4.1.4.6 SetResponseHeaderGatewayFilter

  这个过滤器的作用是将下游服务返回的响应头数据替换掉

filters:
- SetResponseHeader=X-Response-Red, Blue #将下游服务返回的名字叫X-Response-Red的响应头数据设置为Blue返回给客户端

  4.1.4.7 RequestRateLimiter

  限流的过滤器,可以帮我们实现限流,使用的是居于令牌桶算法实现的限流措施,需要用到redis,当请求超过限制的流量的时候会返回HTTP 429 - Too Many Requests

  4.1.4.7.1 依赖


org.springframework.boot
spring-boot-starter-data-redis-reactive

  4.1.4.7.2 限流配置

  此处已全局的默认过滤器为设置,默认过滤器可以参考下面

  稳定速率是通过在replenishRate(补充令牌速度) 和 burstCapacity(令牌桶容量)中设置相同的值来实现的。可通过设置burstCapacity高于replenishRate来允许临时突发流浪。在这种情况下,限流器需要在两次突发之间留出一段时间(根据replenishRate),因为连续两次突发将导致请求丢失 (HTTP 429 - Too Many Requests).

  要限制每秒一个请求,可以将replenishRate设置为目标请求数,requestedTokens设置目标的时间秒数,burstCapacity为replenishRate * requestedTokens。如:设置replenishRate=1, requestedTokens=60 和 burstCapacity=60,就是限制每分钟1个请求.

spring:
cloud:
gateway:
default-filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 #允许每个用户每秒执行多少请求,而不丢弃任何请求。这是令牌桶的填充速率
redis-rate-limiter.burstCapacity: 1 #允许一秒钟内执行的最大请求数。这是令牌桶可以保存的令牌数。将此值设置为零将阻止所有请求。
redis-rate-limiter.requestedTokens: 1 #每个请求消耗多少个令牌,默认是1
key-resolver: "#{@myKeyResolver}" #这个必须要配置,否则返回403

  4.1.4.7.3 myKeyResolver

  myKeyResolver 的作用是从spring bean中找到一个名字叫做myKeyResolver的对象,这个对象会返回一个数据,让我们区分是不是来自于同一个用户的请求

@Bean
KeyResolver myKeyResolver() {
//通过获取用户的ip地址来判断是不是一个用户,根据实际情况,比如可以通过某个头或者token或者参数等等来区分,只要在这里自己返回即可
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
//return exchange->Mono.just(exchange.getRequest().getQueryParams().getFirst("canshu")); //按照某个参数限流
}

  4.2 默认过滤器

  如果想给所有的请求设置默认的网关路由过滤器,则可以通过下面的配置进行设置

spring:
cloud:
gateway:
default-filters: #下面设置的就是默认的过滤器,可以是springcloud中支持的gatewayfilter中的任意一些
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin

  4.3 Body过滤器

  这个过滤器的主要作用是处理请求正文和响应正文的数据,body过滤器只能通过Java代码的方式进行配置

  body过滤器

image-20220419023813220

  4.3.1 ModifyRequestBodyGatewayFilter

  这个过滤器的作用是处理请求正文,将用户传递的正文数据转换为其他的内容,可以是数据内容发生变化,也可以是类型发生变化

  方法展示

image-20220419023528315

  大体代码,根据实际需求来编写

  如果客户端传过来的数据是空的,我们需要返回 Mono.empty()

  空数据处理

image-20220711150738090

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_request_obj", r -> r.path("/user")//设置匹配的地址
.filters(f -> f
//.prefixPath("/httpbin")//设置前缀
// .redirect(302,"http://www.baidu.com")//重定向
//.addRequestHeader("dadas","dadasd")//添加请求头
// 更多操作查看代码api
//处理修改请求正文
//参数1 客户端输入的数据类型
//参数2 网关期望转换后的目标类型
//参数3, 用户传递的数据的格式
//参数4 如何转换数据到目标类型
.modifyRequestBody(String.class, User.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> {
try {
return Mono.just(objectMapper.readValue(s, User.class));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return Mono.empty();
}))
.uri("lb://08consumer-eureka-feign"))//设置目标服务的地址,可以使用lb://
.build();
}

 

  4.3.2 ModifyResponseBodyGatewayFilter

  这个过滤器的作用是修改结果的数据, 操作和上面的请求正文的一样,必须java编写

  只是方法由modifyRequestBody变成modifyResponseBody而已

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_request_obj", r -> r.path("/user")//设置匹配的地址
.filters(f -> f
//.prefixPath("/httpbin")//设置前缀
// .redirect(302,"http://www.baidu.com")//重定向
//.addRequestHeader("dadas","dadasd")//添加请求头
// 更多操作查看代码api
//自定义处理响应正文
//参数1,下游服务给我们返回的数据格式
//参数2 我们想给用户返回的数据格式
//参数3 我们返回的content-type
//参数4 如何将下游服务的结果转换为我们想要的及饿哦过
.modifyResponseBody(User.class,String.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> {
try {
return Mono.just(objectMapper.writeValueAsString(s));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return Mono.empty();
}))
.uri("lb://08consumer-eureka-feign"))//设置目标服务的地址,可以使用lb://
.build();
}

image-20220419032151802

  4.4 全局过滤器

  全局过滤器和网关过滤器一样,但是全局过滤器不需要通过配置文件或者配置类进行设置,它作用在所有路由上面,经过GatewayFilterAdapter包装为GatewayFilterChain责任链,它主要将我们的请求转换为真实的请求地址并进行访问,不需要经过配置,会自动在系统启动的时候进行加载

  4.4.1 ForwardRoutingFilter

  转发过滤器,当前过滤器的主要作用是将请求转发给网关本身,而不是其他服务,只需要将路由的url设置为forward:///localendpoint格式即可

  当请求进来时,ForwardRoutingFilter 会查看一个URL,该URL为 exchange 属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该 url 的 scheme 是 forward(例如:forward://localendpoint),那么该Filter会使用Spirng的 DispatcherHandler 来处理这个请求。该请求的URL路径部分,会被forward URL中的路径覆盖掉。而未修改过的原始URL,会被追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 属性中

 

spring:
application:
name: juejin-gateway
cloud:
gateway:
routes:
- id: forward_sample
uri: forward:///globalfilters #将请求转发到自己网关内的这个地址
order: 10000
predicates:
- Path=/globalfilters
filters:
- PrefixPath=/application/gateway

  当我们访问网关的 http://127.0.0.1:8080/application/gateway/globalfilters这个地址的时候(假设网关是http://127.0.0.1:8080, ForwardRoutingFilter 判断有 forward:// 前缀( Scheme ),过滤处理,将请求转发给 DispatcherHandler,DispatcherHandler 匹配并转发到当前网关实例本地接口 application/gateway/globalfilters ,需要通过 PrefixPathGatewayFilterFactory 将请求重写路径,以匹配本地 API ,否则 DispatcherHandler 转发会失败

  4.4.2 LoadBalancerClientFilter

  这个过滤器的主要作用是进行负载均衡的,当我们的url是lb://myservice格式的时候,会从注册中心找地址进行访问,当前方式是阻塞的同步方式

  当请求进来时,LoadBalancerClientFilter 会查看一个URL,该URL为 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该 url 的 scheme 是 lb,(例如:lb://myservice ),那么该Filter会使用Spring Cloud的 LoadBalancerClient 来将 myservice 解析成实际的host 和 port ,并替换掉原本 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性的值。而原始 url 会追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 属性中。该过滤器还会查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 属性,如果发现该属性的值是 lb ,也会执行相同逻辑。

  注意服务找不到会提示503

  4.4.3 ReactiveLoadBalancerClientFilter

  和LoadBalancerClientFilter一样的作用,只不过是非阻塞的方式,只需要在配置中通过spring.cloud.loadbalancer.ribbon.enabled=fasle来停用上面的方式即可自动开启非同步的

  4.4.4 NettyRoutingFilter

  NettyRoutingFilter ,Netty 路由网关过滤器。其根据 http:// 或 https:// 前缀( Scheme )过滤处理,使用基于 Netty 实现的 HttpClient 请求后端 Http 服务。

  当请求进来时,NettyRoutingFilter 会查看一个URL,该URL是 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果该 url 的 scheme 是 http 或 https ,那么该Filter会使用 Netty 的 HttpClient 向下游的服务发送代理请求。获得的响应将放在 exchange 的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 属性中,以便在后面的 Filter 里使用。(有一个实验性的过滤器: WebClientHttpRoutingFilter 可实现相同功能,但无需Netty)

  4.4.5 NettyWriteResponseFilter

  NettyWriteResponseFilter 用于将代理响应写回网关的客户端侧,所以该过滤器会在所有其他过滤器执行完成后才执行,并且执行的条件是 exchange 中 ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR 属性的值不为空,该值为 Netty 的 Connection 实例。(有一个实验性的过滤器: WebClientWriteResponseFilter 可实现相同功能,但无需Netty)

  4.4.6 RouteToRequestUrlFilter

  这个过滤器是根据当前的请求地址找到匹配的路由设置,然后转换出真实的请求url

  当请求进来时,RouteToRequestUrlFilter 会从 exchange 中获取 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 属性的值,该值是一个 Route 对象。若该对象不为空的话,RouteToRequestUrlFilter 会基于请求 URL 及 Route 对象里的 URL 来创建一个新的 URL。新 URL 会被放到 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性中

  4.4.7 Websocket Routing Filter

  该过滤器的作用与 NettyRoutingFilter 类似。当请求进来时,WebsocketRoutingFilter 会查看一个URL,该URL是 exchange 中 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性的值,如果该 url 的 scheme 是 ws 或者 wss,那么该Filter会使用 Spring Web Socket 将 Websocket 请求转发到下游。

  另外,如果 Websocket 请求需要负载均衡的话,可为URL添加 lb 前缀以实现负载均衡,例如 lb:ws://serviceid

  4.4.8 Gateway Metrics Filter

  想要启用Gateway Metrics Filter,需在项目中添加 spring-boot-starter-actuator 依赖,然后在配置文件中配置 spring.cloud.gateway.metrics.enabled 的值为true。该过滤器会添加名为 gateway.requests 的时序度量(timer metric),其中包含以下标记

QQ截图20221102151522

  4.5.1 自定义过滤器

public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {

public MyGatewayFilterFactory() {
super(Config.class);
}

/**
*
* @param config 我们自己定义的config类的对象,在使用的时候需要传递进来
* @return
*/
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
//获取到请求相关的数据,都会被封装到builder对象中,根据实际业务来处理即可,如果需要数据可以传递过来
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
return chain.filter(exchange.mutate().request(builder.build()).build());
};
}

public static class Config {
//如果需要自定义的config,在这里可以定义自己需要的数据,然后传递过啦
}

}

  4.5.2 使用自定义过滤器

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_request_obj", r -> r.path("/user")//设置匹配的地址
.filters(f -> f
//.prefixPath("/httpbin")//设置前缀
// .redirect(302,"http://www.baidu.com")//重定向
//.addRequestHeader("dadas","dadasd")//添加请求头
// 更多操作查看代码api
//处理修改请求正文
//参数1 客户端输入的数据类型
//参数2 网关期望转换后的目标类型
//参数3, 用户传递的数据的格式
//参数4 如何转换数据到目标类型
.modifyRequestBody(String.class, User.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> {
try {
return Mono.just(objectMapper.readValue(s, User.class));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return Mono.empty();
})
//添加过滤器,可以添加自己的定义的过滤器, 在这里可以创建并传递自己需要的config对象进去
.filter(new MyGatewayFilterFactory().apply(new MyGatewayFilterFactory.Config()))
//自定义处理响应正文
//参数1,下游服务给我们返回的数据格式
//参数2 我们想给用户返回的数据格式
//参数3 我们返回的content-type
//参数4 如何将下游服务的结果转换为我们想要的及饿哦过
.modifyResponseBody(User.class, String.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> {
try {
return Mono.just(objectMapper.writeValueAsString(s));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return Mono.empty();
}))
.uri("lb://08consumer-eureka-feign"))//设置目标服务的地址,可以使用lb://
.build();
}

  自定义网关过滤器

image-20220420000802612

  4.6 自定义全局过滤器

  全局过滤器可以通过实现GlobalFilter和Ordered接口来实现并添加@Compoment, 也可以直接通过@Bean注解声明一个GlobalFilter返回一个匿名对象,全局过滤器作用在所有的过滤器,不需要指定路由,只要对象创建即可

  4.6.1 自定义过滤器

  此方式通过创建类实现接口来实现过滤器

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.err.println("全局过滤器执行了");
//执行下一个过滤器
return chain.filter(exchange);
}

/**
* 优先级,数字越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}

  4.6.2 匿名对象的方式

  下面的是匿名过滤器的

@Bean
@Order(-1)//指定优先级,不指定的情况下默认优先级是int的最大值,当过滤器的order一样大的时候,会按照创建的顺序来执行,这个顺序只在匿名过滤器之间有效,不会和其他过滤器一起比较顺序
public GlobalFilter MyFilter() {
return ((exchange, chain) -> {
System.err.println("匿名自定义的过滤器执行了了");
return chain.filter(exchange);
});
}

好程序员公众号

  • · 剖析行业发展趋势
  • · 汇聚企业项目源码

好程序员开班动态

More+
  • HTML5大前端 <高端班>

    开班时间:2021-04-12(深圳)

    开班盛况

    开班时间:2021-05-17(北京)

    开班盛况
  • 大数据+人工智能 <高端班>

    开班时间:2021-03-22(杭州)

    开班盛况

    开班时间:2021-04-26(北京)

    开班盛况
  • JavaEE分布式开发 <高端班>

    开班时间:2021-05-10(北京)

    开班盛况

    开班时间:2021-02-22(北京)

    开班盛况
  • Python人工智能+数据分析 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2020-09-21(上海)

    开班盛况
  • 云计算开发 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2019-07-22(北京)

    开班盛况
IT培训IT培训
在线咨询
IT培训IT培训
试听
IT培训IT培训
入学教程
IT培训IT培训
立即报名
IT培训

Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号