本文仅用于记录自己学习Spring Cloud Gateway,若介绍有误,还请手下留情,敬请批评指正,遵循共同学习进步的原则。
一、项目搭建gateway
建module
引入依赖pom
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--服务注册发现consul discovery,网关也要注册进服务注册中心统一管控-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 指标监控健康检查的actuator,网关是响应式编程删除掉spring-boot-starter-web dependency-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
导入配置yml consul服务注册
server: port: 9527 spring: application: name: cloud-gateway #以微服务注册进consul或nacos服务列表内 cloud: consul: #配置consul地址 host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name}
二、路由映射
1.不用feign 配置网关
业务模块测试接口
@RestController
public class PayGateWayController
{
@Resource
PayService payService;
@GetMapping(value = "/pay/gateway/get/{id}")
public ReturnData<Pay> getById(@PathVariable("id") Integer id)
{
Pay pay = payService.getById(id);
return ReturnData.success(pay);
}
@GetMapping(value = "/pay/gateway/info")
public ReturnData<String> getGatewayInfo()
{
return ReturnData.success("gateway info test:"+ IdUtil.simpleUUID());
}
}
加接口 路由端口为8001
@RestController public class PayGateWayController { @Resource PayService payService; @GetMapping(value = "/pay/gateway/get/{id}") public ReturnData<Pay> getById(@PathVariable("id") Integer id) { Pay pay = payService.getById(id); return ReturnData.success(pay); } @GetMapping(value = "/pay/gateway/info") public ReturnData<String> getGatewayInfo() { return ReturnData.success("gateway info test:"+ IdUtil.simpleUUID()); } }
yml配置
server:
port: 9527
spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
consul: #配置consul地址
host: localhost
port: 8500
discovery:
prefer-ip-address: true
service-name: ${spring.application.name}
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
2.用feign 配置网关
业务模块测试接口
@RestController
public class PayGateWayController
{
@Resource
PayService payService;
@GetMapping(value = "/pay/gateway/get/{id}")
public ReturnData<Pay> getById(@PathVariable("id") Integer id)
{
Pay pay = payService.getById(id);
return ReturnData.success(pay);
}
@GetMapping(value = "/pay/gateway/info")
public ReturnData<String> getGatewayInfo()
{
return ReturnData.success("gateway info test:"+ IdUtil.simpleUUID());
}
}
暴露接口模块部分需要加及接口
@FeignClient(value = "cloud-gateway") //对应的服务名字
/**
* GateWay进行网关测试案例01
* @param id
* @return
*/
@GetMapping(value = "/pay/gateway/get/{id}")
public ReturnData getById(@PathVariable("id") Integer id);
/**
* GateWay进行网关测试案例02
* @return
*/
@GetMapping(value = "/pay/gateway/info")
public ReturnData<String> getGatewayInfo();
调用接口
@RestController
public class OrderGateWayController
{
@Resource
private PayFeignApi payFeignApi;
@GetMapping(value = "/feign/pay/gateway/get/{id}")
public ReturnData getById(@PathVariable("id") Integer id)
{
return payFeignApi.getById(id);
}
@GetMapping(value = "/feign/pay/gateway/info")
public ReturnData<String> getGatewayInfo()
{
return payFeignApi.getGatewayInfo();
}
}
三、其他高级特性
1.Route用微服务名字动态获取服务的URI
yml配置更改
spring: application: name: cloud-gateway #以微服务注册进consul或nacos服务列表内 cloud: consul: #配置consul地址 host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: # - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 # predicates: # - Path=/pay/** - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由 - id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
服务主启动类需要加以达到动态刷新
@SpringBootApplication
@MapperScan("com.lllmark.cloud.mapper")
@EnableDiscoveryClient
@RefreshScope
public class Main8001 {
public static void main(String[] args) {
SpringApplication.run(Main8001.class,args);
System.out.println("Main8001 start!");
}
}
2.Predicates配置
(1) After 超过该时间放开接口
between 一定时间段内可以访问
before 在此时间之前可以访问
注:时间格式由ZonedDateTime类得出
spring: application: name: cloud-gateway #以微服务注册进consul或nacos服务列表内 cloud: gateway: routes: # - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 # predicates: # - Path=/pay/** - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由 # - After=2024-08-10T16:53:27.605069400+08:00[Asia/Shanghai] # - Between=2024-08-10T16:53:27.605069400+08:00[Asia/Shanghai],2024-08-10T16:56:17.605069400+08:00[Asia/Shanghai] - Before=2024-08-10T16:57:00.605069400+08:00[Asia/Shanghai]
(2)Cookie
routes: - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由 - Cookie=username,pigbd- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
(3)Header
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
(4)Host
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Host=**.baidu.com
(5)Path
predicates:
- Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由
(6)query
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
(7)RemoteAddr
predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由 - RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。
(8) Method
predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由 - Method=POST,GET #限制路径请求方式,只能用GET/POST
3.自定义Predicates
规则类名需为 自定义名称+RoutePredicateFactory
后 extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
后编写构造方法
public MyRoutePredicateFactory(){ super(MyRoutePredicateFactory.Config.class); }
编写配置
@Validated //验证 public static class Config{ public Config() { } @Setter@Getter@NotEmpty private String userType; //用户类型 会员/非会员 }
重写shortcutFieldOrder
@Override public List<String> shortcutFieldOrder() { return Collections.singletonList("userType"); //配置内部类的变量可以为多个 }
重写apply test方法返回值为控制是否可以访问该uri
@Override public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config) { return new Predicate<ServerWebExchange>() { @Override public boolean test(ServerWebExchange serverWebExchange) { String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType"); //查询有无此参数,没有返回false if (userType == null) { return false; } //查询有无此参数,没有返回是否符合文件的配置 if (userType.equalsIgnoreCase(config.getUserType())){ return true; } return false; } }; }
四.Filter 过滤器
1.Request相关组
配置
- id: pay_routh3 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- AddRequestHeader=X-Request-lllmark1,lllmarkValue1 #添加请求头值
- AddRequestHeader=X-Request-lllmark2,lllmarkValue2
- RemoveRequestHeader=sec-fetch-site #删除请求头
- SetRequestHeader=sec-fetch-mode,Update #修改请求头
测试代码:
@GetMapping(value = "/pay/gateway/filter")
public ReturnData<String> getGatewayFilter(HttpServletRequest request)
{
String result = "";
Enumeration<String> headers = request.getHeaderNames();
while(headers.hasMoreElements())
{
String headName = headers.nextElement();
String headValue = request.getHeader(headName);
System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue);
if(headName.equalsIgnoreCase("X-Request-lllmark1")
|| headName.equalsIgnoreCase("X-Request-lllmark2")) {
result = result+headName + "\t " + headValue +" ";
}
}
return ReturnData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}
2.RequestParameter
配置
filters:
- AddRequestParameter=customerId,9527001 # 新增请求参数Parameter:k ,v
- RemoveRequestParameter=customerName # 删除url请求参数customerName
测试代码:
@GetMapping(value = "/pay/gateway/filter")
public ReturnData<String> getGatewayFilter(HttpServletRequest request)
{
String result = "";
Enumeration<String> headers = request.getHeaderNames();
while(headers.hasMoreElements())
{
String headName = headers.nextElement();
String headValue = request.getHeader(headName);
System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue);
if(headName.equalsIgnoreCase("X-Request-lllmark1")
|| headName.equalsIgnoreCase("X-Request-lllmark2")) {
result = result+headName + "\t " + headValue +" ";
}
}
System.out.println("=============================================");
String customerId = request.getParameter("customerId");
System.out.println("request Parameter customerId: "+customerId);
String customerName = request.getParameter("customerName");
System.out.println("request Parameter customerName: "+customerName);
System.out.println("=============================================");
return ReturnData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}
3.ResponseHeader
配置
filters:
- AddResponseHeader=X-Response-lllmark, add # 新增请求参数X-
- SetResponseHeader=Date,2099-11-11 # 设置回应头Date值为2099-11-11
- RemoveResponseHeader=Content-Type # 将默认自带Content-Type回应属性删除
@GetMapping(value = "/pay/gateway/filter")
public ReturnData<String> getGatewayFilter(HttpServletRequest request)
{
String result = "";
Enumeration<String> headers = request.getHeaderNames();
while(headers.hasMoreElements())
{
String headName = headers.nextElement();
String headValue = request.getHeader(headName);
System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue);
if(headName.equalsIgnoreCase("X-Request-lllmark1")
|| headName.equalsIgnoreCase("X-Request-lllmark2")) {
result = result+headName + "\t " + headValue +" ";
}
}
System.out.println("=============================================");
String customerId = request.getParameter("customerId");
System.out.println("request Parameter customerId: "+customerId);
String customerName = request.getParameter("customerName");
System.out.println("request Parameter customerName: "+customerName);
System.out.println("=============================================");
return ReturnData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now());
}
4. 前缀和路径相关
配置
(1) 原访问路径 /pay/gateway/filter
现访问路径 /gateway/filter
- id: pay_routh3 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
# - Path=/pay/gateway/filter/** # 原路径
- Path=/gateway/filter/** # 过滤后的路径
filters:
- PrefixPath=/pay #过滤的路径
(2) 原访问路径/pay/gateway/filter
现访问路径 /XYZ/abc/filter
{}为占位符可改变,如{abc} ,但是需要相对应
- id: pay_routh3 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/XYZ/abc/{segment} # 断言,路径相匹配的进行路由
filters:
- SetPath=/pay/gateway/{segment}
(3)请求转发,重定向某个界面
此处访问 /pay/gateway/filter,跳转到www.baidu.com
- id: pay_routh3 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- RedirectTo=302, http://www.baidu.com
5.其他
Default Filter 此处添加到所有的接口,即全局配置
6.自定义过滤器
(1) 自定义全局Filter
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
private static final String BEGIN_VISIT_TIME = "begin_visit_name";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.现记录访问接口的开始时间
exchange.getAttributes().put(BEGIN_VISIT_TIME,System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
if (beginVisitTime != null){
log.info("访问接口主机:"+exchange.getRequest().getURI().getHost());
log.info("访问接口主机:"+exchange.getRequest().getURI().getPort());
log.info("访问接口主机:"+exchange.getRequest().getURI().getPath());
log.info("访问接口主机:"+exchange.getRequest().getURI().getRawQuery());
log.info("访问接口主机:" + (System.currentTimeMillis() - beginVisitTime) + "毫秒");
log.info("==============");
System.out.println();
}
}));
}
/**
* 数字越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
配置:
- id: pay_routh3 #pay_routh2
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
(2) 自定义条件Filter
@Component
@Slf4j
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
public MyGatewayFilterFactory(){
super(MyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
System.out.println("进入了网关过滤器" + config.getStatus());
if (request.getQueryParams().containsKey("lllmark")){
return chain.filter(exchange);
}else {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
@Override
public List<String> shortcutFieldOrder() {
List<String> list = new ArrayList<String>();
list.add("status");
return list;
}
public static class Config{
@Getter@Setter
private String status;
}
}
配置
- id: pay_routh3 #路由的ID(类似mysql主键ID),没有固定规
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由
filters:
- My=lllmark
备注配置中My=lllmark 其中的My 由 MyGatewayFilterFactory分割My + GatewayFilterFactory
在自定义时候也需要按照此 自定义配置名+GatewayFilterFactory方式来命名