开发技能点|SpringGateway 网关

奈非框架简介 早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎
这些框架和Spring Cloud Alibaba的对应关系我们要知道
Nacos对应Eureka 都是注册中心
Dubbo对应ribbon+feign都是实现微服务间调用
Sentinel对应Hystrix都是项目限流熔断降级组件
Gateway对应zuul都是项目的网关
Gateway不是阿里巴巴的而是Spring提供的
什么是网关 "网关"网是网络,关是关口\关卡
关口\关卡的意思就是"统一入口"
网关:就是网络中的统一入口
程序中的网关就是微服务项目提供的外界所有请求统一访问的微服务项目
因为提供了统一入口之后,方便对所有请求进行统一的检查和管理
开发技能点|SpringGateway 网关
文章图片


网关的主要功能有

  • 将所有请求统一由经过网关
  • 网关可以对这些请求进行检查
  • 网关方便记录所有请求的日志
  • 网关可以统一将所有请求路由到正确的模块\服务上
路由的近义词就是"分配"
Spring Gateway简介 我们使用Spring Gateway作为当前项目的网关框架
Spring Gateway是Spring自己编写的,也是SpringCloud中的组件
SpringGateway官网
Spring Cloud Gateway
网关项目git地址
https://gitee.com/jtzhanghl/gateway-demo.git
简单网关演示 网关是一个我们创建的项目,不是一个需要安装的软件
网关也是当前微服务项目的一员,也要注册到Nacos,所以保证Nacos的运行
例如:
beijing和shanghai是编写好的两个项目
【开发技能点|SpringGateway 网关】gateway需要的yml文件配置
要想实现网关的路由效果需要修改yml文件如下
server: port: 9000 spring: application: name: gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: routes:# gateway开始配置路由信息 - id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** # 如果java访问这个数字元素的方式:spring.cloud.gateway.routes[0].predicates[0] # routes属性实际上是一个数组,yml文件中出现 "- ...."配置时表示当前配置时一个数组元素 - id: gateway-beijing # 这个配置指定这个路由的名称,这个名称和其他任何位置没有关联 # 只需要注意不能再和其他路由名称重复 # uri设置路由的目标 # lb是LoadBalance(负载均衡)的缩写,beijing是注册到nacos的服务名称 uri: lb://beijing # 我们需要设置一个条件,当访问路径满足特定条件是,使用当前路由规则 predicates: # predicates翻译为断言,所谓断言就是判断一个条件是否满足 # Path 是路径断言,意思是满足路径为XXX时使用这个路由 - Path=/bj/** # http://localhost:9000/bj/show 会路由到 9001/bj/show

内置断言 断言就是判断一个条件,如果条件满足就执行某个操作
predicates就是断言的意思
我们前面章节使用的Path就是内置断言中的一种,指访问的路径是否满足条件
除了路径断言之外,还有很多内置断言常见的内置断言列表
  • after
  • before
  • between
  • cookie
  • header
  • host
  • method
  • path
  • query
  • remoteaddr
时间相关
after,before,between
在指定时间之后,之前或之间
判断是否满足时间条件,如果满足才允许访问
我们先使用下面代码获得当前包含时区的系统时间表
ZonedDateTime.now()

使用After设置必须在指定时间之后访问
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - After=2022-06-24T15:30:30.999+08:00[Asia/Shanghai]

使用Before设置必须在指定时间之后访问
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Before=2022-06-24T15:34:00.999+08:00[Asia/Shanghai]

使用Between设置必须在指定时间之间访问
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Between=2022-06-24T15:34:00.999+08:00[Asia/Shanghai],2022-06-24T15:36:20.999+08:00[Asia/Shanghai]

要求指定参数
Query断言,要求必须包含指定的参数才能访问资源
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Query=name

内置过滤器 Gateway还提供的内置过滤器
不要和filter混淆
内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理
使用AddRequestParameter过滤器,想请求中添加参数
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Query=name filters: - AddRequestParameter=age,80

shanghai项目的控制器接收这个参数
@GetMapping("/show") public String show(String name,Integer age){ return "这里是上海!"+name+","+age; }

重启网关和shanghai项目
例如输入如下路径
http://localhost:9000/sh/show?name=tom
因为过滤器的存在,控制可以获取网关过滤器添加的参数值
其他内置过滤器和自定义过滤器的使用,可以查阅相关文档自己了解
动态路由 如果项目微服务数量多
那么gateway项目yml文件配置也会越来越冗余,维护的工作量也会越来越大
所谓我们希望能够根据固定特征自动的路由到每个微服务模块
这个功能就是SpringGateway的动态路由功能
只需要在配置文件中配置开启动态路由功能即可
spring: application: name: gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: # 开启Spring Gateway的动态路由功能 # 规则是根据注册到Nacos的项目名称作为路径的前缀,就可以访问到指定项目了 enabled: true

开启之后访问项目的格式以beijing为例
localhost:9000/beijing/bj/show

knife4j网关配置 我们希望配置网关之后,在使用knife4j测试时
就不来回切换端口号了
我们需要配置Knife4j才能实现
创建包xxx.gateway.config包
SwaggerProvider
@Component public class SwaggerProvider implements SwaggerResourcesProvider { /** * 接口地址 */ public static final String API_URI = "/v2/api-docs"; /** * 路由加载器 */ @Autowired private RouteLocator routeLocator; /** * 网关应用名称 */ @Value("${spring.application.name}") private String applicationName; @Override public List get() { //接口资源列表 List resources = new ArrayList<>(); //服务名称列表 List routeHosts = new ArrayList<>(); // 获取所有可用的应用名称 routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null) .filter(route -> !applicationName.equals(route.getUri().getHost())) .subscribe(route -> routeHosts.add(route.getUri().getHost())); // 去重,多负载服务只添加一次 Set existsServer = new HashSet<>(); routeHosts.forEach(host -> { // 拼接url String url = "/" + host + API_URI; //不存在则添加 if (!existsServer.contains(url)) { existsServer.add(url); SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setUrl(url); swaggerResource.setName(host); resources.add(swaggerResource); } }); return resources; } }

xxx.gateway.controller
SwaggerController类
@RestController @RequestMapping("/swagger-resources") public class SwaggerController { @Autowired(required = false) private SecurityConfiguration securityConfiguration; @Autowired(required = false) private UiConfiguration uiConfiguration; private final SwaggerResourcesProvider swaggerResources; @Autowired public SwaggerController(SwaggerResourcesProvider swaggerResources) { this.swaggerResources = swaggerResources; } @GetMapping("/configuration/security") public Mono securityConfiguration() { return Mono.just(new ResponseEntity<>( Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK)); } @GetMapping("/configuration/ui") public Mono> uiConfiguration() { return Mono.just(new ResponseEntity<>( Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK)); } @GetMapping("") public Mono swaggerResources() { return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); } }

xxx.gateway.filter
SwaggerHeaderFilter类
@Component public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { private static final String HEADER_NAME = "X-Forwarded-Prefix"; private static final String URI = "/v2/api-docs"; @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); if (!StringUtils.endsWithIgnoreCase(path,URI )) { return chain.filter(exchange); } String basePath = path.substring(0, path.lastIndexOf(URI)); ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build(); ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); return chain.filter(newExchange); }; } }

启动Nacos\Seata\Sentinel
启动cart\stock\order\business
在保证一般访问正常的情况下
再启动gateway
可以通过下面路径访问之前的各个模块的业务
http://localhost:10000/nacos-stock/doc.html
http://localhost:10000/nacos-cart/doc.html
http://localhost:10000/nacos-order/doc.html
http://localhost:10000/nacos-business/doc.html
如果不使用网关一切正常,但是启动网关访问失败的话,就是gateway项目配置问题
Gateway和SpringMvc依赖冲突问题和解决 网关依赖
org.springframework.cloud spring-cloud-starter-gateway

SpringMvc依赖
org.springframework.boot spring-boot-starter-web

这两个依赖在同一个项目中时,默认情况下启动会报错
SpringMvc依赖中自带一个Tomcat服务器
而Gateway依赖中自带一个Netty服务器
因为在启动服务时这个两个服务器都想启动,会因为争夺端口号和主动权而发生冲突
我们需要在yml文件中添加配置解决
spring: main: web-application-type: reactive


    推荐阅读