网关:官方中文文档
系统的统一入口,为客户端提供统一的服务
创建gateway maven项目引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
添加配置文件:
server: port: 8070 spring: application: name: wdz-gateway cloud: gateway: routes: # 路由数组 - id: wdz_product # 当前路由的标识,唯一,默认是UUID,通常配置为项目名称 uri: http://localhost:8072 # 请求转发目标地址 order: 1 #路由优先级,数字越小优先级越高 predicates: # 断言 条件判断,返回值是Boolean 转发请求要求满足的条件 - Path=/product/** # 当请求路径满足Path指定的规则时,路由信息才会正常转发, # 如果直接配置 / 则不需要配置filters # 以上配置转发信息:localhost:8087/product/get/1 # ----> http://localhost:8072/product/get/1 filters: # 过滤器,在请求传递过程中,队请求做一些业务处理 - StripPrefix=1 # 在请求转发之前去掉一层路径 # StripPrefix配置转发信息:localhost:8087/product/get/1 # ----> http://localhost:8072/get/1
访问路径:
http://localhost:8070/product/query/1 就直接转发到 http://localhost:8072/query/1
集成nacos
引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency>启动类添加注解:@EnableDiscoveryClient
@SpringBootApplication @EnableDiscoveryClient public class GatewayApplication{ public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } }
修改配置文件:
server: port: 8070 spring: application: name: wdz-gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: enabled: true # 开启从nacos中获取信息 routes: # 路由数组 - id: wdz_product # 当前路由的标识,唯一,默认是UUID,通常配置为项目名称 # uri: http://localhost:8072 # 请求转发目标地址 # 集成nacos之后 uri: lb://wdz-product #lb:负载均衡, 请求的目标服务器名称 order: 1 #路由优先级,数字越小优先级越高 predicates: # 断言 条件判断,返回值是Boolean 转发请求要求满足的条件 - Path=/product/** # 当请求路径满足Path指定的规则时,路由信息才会正常转发, # 如果直接配置 / 则不需要配置filters # 以上配置转发信息:localhost:8087/product/get/1 #----> http://localhost:8072/product/get/1 filters: # 过滤器,在请求传递过程中,队请求做一些业务处理 - StripPrefix=1 # 在请求转发之前去掉一层路径 # StripPrefix配置转发信息:localhost:8087/product/get/1 # ----> http://localhost:8072/get/1
重启服务:访问地址变更:
http://localhost:8070/wdz-product/query/1
http://localhost:8070/:是网关地址
wdz-product: 要访问的目标服务名称spring.application.name
query/1:目标服务中的接口
routes:路由数组数据配置默认gateway的,可直接通过上边路径访问,但是不够灵活,如果多条path配置就只能手动配置了,日常开发中建议使用配置,这样更方便的去管理服务
概念:
执行流程:
断言:predicates
用于进行条件判断,只有断言都返回true,才会真正的转发路由
断言:
1:继承AbstractRoutePredicateFactory 2:配置+RoutePredicateFactory 命名规则 配置是指在yml文件断言下配置的参数 配置文件:
自定义代码:
@Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> { // 构造 public AgeRoutePredicateFactory() { super(AgeRoutePredicateFactory.Config.class); } // 读取配置文件中的参数值,赋值到配置类的属性中 @Override public List<String> shortcutFieldOrder() { List<String> strings = Arrays.asList("age"); return strings; } // 断言逻辑 @Override public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) { return new Predicate<ServerWebExchange>() { @Override public boolean test(ServerWebExchange serverWebExchange) { // 接收前端传入的参数:age String age = serverWebExchange.getRequest().getQueryParams().getFirst("age"); if (!StringUtils.isEmpty(age)){ int i = Integer.parseInt(age); if (i == config.getAge()){ return true; }else { return false; } } return false; } }; } // 用于接收 配置文件中对应的参数 @Data @NoArgsConstructor public static class Config { private int age; } }
http://localhost:8070/product/query/1?age=18
请求路径要满足Path 才能进入断言
过滤器:
作用:在请求传递的过程中,对请求和响应做一些业务处理
生命周期:
路由之前(pre):这种过滤器在请求路由之前调用,可利用这个过滤器实现身份验证、通过某些条件选择服务、记录调试信息等
路由之后(post):这种过滤器是在路由到微服务之后执行,这种过滤器可用来为响应添加标准的http header,收集统计信息和指标,将响应从微服务发送给客户端等
分类:
从作用范围可分为:
GatewayFilter:应用到单个路由或者一个分组的路由上
GlobalFilter:应用到所有的路由上
自定义过局部滤器:
规则与断言格式基本一致
继承 AbstractGatewayFilterFactory
命名规则:配置+GatewayFilterFactory
package com.wdz.cloud.gateway.filter;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* 自定义局部过滤器
*/
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog","cacheLog");
}
@Override
public GatewayFilter apply(Config config) {
GatewayFilter filter = new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("-------------过滤器参数值:" + config.toString());
if (config.isCacheLog()){
System.out.println("缓存日志开启");
}
if (config.isConsoleLog()){
System.out.println("控制台日志开启");
}
return chain.filter(exchange);
}
};
return filter;
}
@Data
@NoArgsConstructor
public static class Config {
private boolean consoleLog;
private boolean cacheLog;
}
}
自定义全局过滤器:
package com.wdz.cloud.gateway.filter;
import org.apache.commons.lang3.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;
/**
* 自定义全局过滤器
* 要求:
* 必须实现GlobalFilter, Ordered,并实现接口
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isEmpty(token)){
System.out.println("---------认证失败------");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
这样就完成简单的全局过滤器,如果未认证则返回httpstatus=401
跨域问题处理:
方式一:配置文件方式
spring:
cloud:
gateway:
globalcors: # 全局跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
cors-configurations:
'[/**]': # 拦截所有请求
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8999"
- "http://localhost:8888"
allwedMethods: # 允许的跨域的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowedCredentials: true # 是否允许携带cookie
maxAge: 360000 # 跨域检测有效期
方式二 :java代码方式
package com.wdz.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* 跨域配置
*/
@Configuration
public class CorsConfig {
/**
* 这里为支持的请求头,如果有自定义的header字段请自己添加
*/
private static final String ALLOWED_HEADERS = "X-Requested-With, Content-Type,device ,Authorization, credential, X-XSRF-TOKEN, token, Admin-Token, App-Token";
private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD";
private static final String ALLOWED_ORIGIN = "*";
private static final String ALLOWED_EXPOSE = "*";
private static final String MAX_AGE = "3600L";
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Credentials", "true");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}