nacos(七): gateway(单体)

这篇文章将从gateway的搭建、自动路由匹配、路由数组、跨域和路由过滤器五个方面对gateway项目展开讨论。

1、gateway的搭建

gateway的项目基本的搭建过程与消费者的搭建过程基本一致,细节部分可参考《nacos(四): 创建第一个消费者Conumer(单体)》

搭建完成后,在pom.xml中引入网关需要用到的依赖项,如下:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <version>0.0.1-SNAPSHOT</version>
    <artifactId>gateway</artifactId>
    <packaging>jar</packaging>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
    </properties>
    <dependencies>

        \<!-- 服务发现 --\>
\<dependency\>
\<groupId\>com.alibaba.cloud\</groupId\>
\<artifactId\>spring-cloud-starter-alibaba-nacos-discovery\</artifactId\>
\</dependency\>
\<!--消费者--\>
\<dependency\>
\<groupId\>org.springframework.cloud\</groupId\>
\<artifactId\>spring-cloud-starter-loadbalancer\</artifactId\>
\<version\>3.1.5\</version\>
\</dependency\>
\<!--⽹关--\>
\<dependency\>
\<groupId\>org.springframework.cloud\</groupId\>
\<artifactId\>spring-cloud-starter-gateway\</artifactId\>
\</dependency\>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.gateway.GatewayApplication</mainClass>
                    <skip>false</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

注意:在gateway项目中,不能引入spring mvc或者start web类的关于web的依赖,否则会报错"Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway."。

另外,gateway的版本也需要进行匹配,在前几篇文章中,我们也一再强调微服务项目对于库的版本要求非常精细。版本稍微不一致可能会报出奇怪的问题,比如"Error processing condition on org.springframework.cloud.gateway.config.GatewayAutoConfiguration.propertiesRouteDefinitionLocator"。

接下来,在application.yml中,开启自动路由匹配:

复制代码
server:
  port: 8087

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
      config:
        server-addr: 127.0.0.1:8848
        file-extension: properties
        username: nacos
        password: nacos
loadbalancer:
nacos:
enabled: true
gateway:
discovery:
locator:
enabled: true

  application:
    name: gateway

最后,我们在启动类上添加@EnableDiscoveryClient注解。

复制代码
@SpringBootApplication
@EnableDiscoveryClientpublic class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

配置完成后,启动gateway,可以看到nacos的管理后台里gateway已经注册成功。

2、路由自动匹配

在文章的第1部分里,application.yml中开启的路的自动匹配功能。

如个例子:

当前在nacos中有一个product1服务,服务提提供了/hello的地址。

通过gateway,我们可以访问http://127.0.0.1:8087/product1/hello,通过gateway访问到product1的hello地址。如下图:

其中:

127.0.0.1:8087:分别是gateway的IP地址和端口号;

product1: 是注册在nacos的里的服务名;

/hello:则是product1服务提供的资源地址。

3、路由数组

在application.yml中配置routes配置节,可开启路由数组,如下

复制代码
server:
  port: 8087

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
      config:
        server-addr: 127.0.0.1:8848
        file-extension: properties
        username: nacos
        password: nacos
    loadbalancer:
      nacos:
        enabled: true
    gateway:
      discovery:
        locator:
          enabled: true
routes: - id: product1_hello uri: lb://product1 # 指的是从nacos中按照名称获取微服务,并遵循负载均衡策略 order: 1 # 路由优先级 数字越低优先级越高 predicates: # 断言 - Path= /p1/\*\* # 当请求路径满⾜Path指定的规则时,才进⾏路由换发 filters: - StripPrefix=1 # 拼接好url之后去掉1层路径也就是p1 - id: order_route uri: lb://server-order order: 1 predicates: - Path=/order/\*\* filters: - StripPrefix=1 - AddRequestHeader=msg,abc

  application:
    name: gateway

上面的这个配置文件例子中,路由数组里配置了两个路由。各项配置的具体配置的意义,看文件中的注释。其中- StripPrefix=1配置项需要注意。

这一项的意思是,当用户访问http://127.0.0.1:8087/p1/hello,因为/p1/路径触发了第一个路由规则,路由去掉p1这一项,重新拼接为lb://product1/hello去获取访问的结果。

断言proeicates还有如果规则可以使用:

过滤器filters还有如下规则可以使用:

4、跨域

可以通过对application.yml的配置进行跨域项的设置,如下:

复制代码
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些⽹站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求⽅式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

5、路由过滤器

gateway也可以自定义自己的filter,下面用两个例子来说明过如何自定义filter。

示例一:判断请求参数中是否有authorization, authorization参数值是否为admin。如果同时满⾜则放⾏,否则拦截。

复制代码
@Component
@Order(-1) // 用来控制优先级 数字越小优先级越高
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().get("authorization").get(0);
        if (token.equals("admin")){
            //            放行
            return chain.filter(exchange);
        }
        //        拦截 禁止访问
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
}

示例二:判断请求参数中是否有token,如果没有则拦截并报错。

复制代码
@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest req= exchange.getRequest();
        ServerHttpResponse resp= exchange.getResponse();

        MultiValueMap<String, String> params= req.getQueryParams();
        if(!params.containsKey("token")){
            //输出错误信息
            Map<String, Object> map = new HashMap<>();
            map.put("msg", "Not Logged in!!!!");
            map.put("code", 4000);

 /*
            //3.3作JSON转换
           byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8
            );
            //3.4调用bufferFactory方法,生成DataBuffer对象
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
*/

            //4.调用Mono中的just方法,返回要写给前端的JSON数据
            DataBuffer buffer =resp.bufferFactory().wrap("error".getBytes(StandardCharsets.UTF_8));

            return resp.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

本文关于gateway的内容到这里就结束了,下一篇我们将一起讨论哨兵sentinel的使用: )