Administrator
发布于 2023-04-26 / 15 阅读
0
0

微服务(八)——统一网关Gateway

网关的作用

网关的作用主要有:

  • 对用户请求做身份认证、权限校验
  • 将用户请求路由到微服务,并实现负载均衡
  • 对用户请求做限流

在SpringCloud中网关的实现主要包括两种:

  • zuul,基于servlet实现,属于阻塞式编程。
  • gateway,基于Spring5中提供的WebFlux,属于响应式编程的实现,性能更好。更推荐。

搭建网关

第一步,创建一个网关服务,引入依赖

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.lsy.demo</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
</project>

第二步,编写路由配置及nacos地址

server:
  port: 10010 #网关端口
spring:
  application:
    name: gateway #服务名称
  cloud:
    nacos:
      server-addr: localhot:8848 #nacos地址
    gateway:
      routes: #网关路由配置
        - id: user-service #路由id,自定义,只要唯一即可
          uri: lb://userservice #路由的目标地址 lb代表负载均衡 后面跟服务名称  还有一种固定地址的方式http://localhost:8081,不推荐
          predicates: #路由断言 也就是判断请求是否符合路由规则的条件
            - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**

启动服务,可以看到,gateway已经注册到了nacos

接下来,使用网关请求试一下,可以看到,访问成功

路由断言工厂

我们再配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。这样的断言工厂在SpringCloudGateway中还有十几个,见下图

路由过滤器

路由过滤器GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。

和路由断言工厂一样,Spring中提供了许多过滤器工厂GatewayFilterFactory,见下图

我们测试一下AddRequestHeader

配置文件添加过滤器,可以指定服务过滤或全局过滤两种,全局过滤对所有服务生效。可以配置多个过滤器,过滤器会链式调用。

server:
  port: 10010 #网关端口
spring:
  application:
    name: gateway #服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 #nacos地址
      discovery:
        username: nacos
        password: nacos
    gateway:
      routes: #网关路由配置
        - id: user-service #路由id,自定义,只要唯一即可
          uri: lb://userservice #路由的目标地址 lb代表负载均衡 后面跟服务名称  还有一种固定地址的方式http://localhost:8081,不推荐
          predicates: #路由断言 也就是判断请求是否符合路由规则的条件
            - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
          filters: #针对指定服务过滤
            - AddRequestHeader=myhead,zhangsan
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
      default-filters: #全局过滤
        - AddRequestHeader=myhead,lisi

获取请求头参数,打印

package cn.lsy.user.web;

import cn.lsy.user.config.PatternProperties;
import cn.lsy.user.pojo.User;
import cn.lsy.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private PatternProperties patternProperties;

//    @Value("${pattern.dateformat}")
//    private String dateformat;

    @GetMapping("prop")
    public PatternProperties prop() {
        return patternProperties;
    }

    @GetMapping("now")
    public String now() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
    }

    /**
     * @param id 用户id
     * @return 用户信息
     */
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "myhead", required = false) String myhead) {
        System.out.println("myhead: " + myhead);
        return userService.queryById(id);
    }
}

测试,这里注意,要通过网关调用http://localhost:10010/user/{id},直接调用localhost:8081服务是无效的

输出zhangsan,说明成功经过了过滤器添加了请求头,并且是以指定服务下的过滤器为准。

全局过滤器

测试一下,自定义一个类实现GlobalFilter,如果请求参数带token,则放行,否则返回401

package cn.lsy.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
//@Order(123)
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        String token = queryParams.getFirst("token");
        if(token.equals("admin123")){
            return chain.filter(exchange);
        }
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

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

order用于定义过滤器的顺序,值越小,越早执行,可以添加@Order注解或者实现Ordered接口,重启网关服务测试

错误token:返回401

正确输入token:返回数据

过滤器执行顺序

默认过滤器和路由过滤器都是GatewayFilter,而全局过滤器是GlobalFilter,GlobalFilter会通过一个过滤器适配器GatewayFilterAdapter适配成GatewayFilter。

所以他们可以放到一个过滤器链中排序。当order值不一样时,执行顺序是从小到大。order值一样时,按照defaultFilter->局部路由过滤器->全局过滤器。

跨域问题

跨域主要指的是域名不一致,包括域名不同和端口不同等。跨域问题主要是由浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。一般是通过CORS解决。gateway网关内部已经替我们做好CORS的内部逻辑了,我们只要做一些配置就可以。

CORS跨域主要配置几个参数:

  • 允许哪些域名跨域
  • 允许哪些请求头
  • 允许哪些请求方式
  • 是否允许使用cookie
  • 有效期是多久


评论