Gateway

规则配置

Gateway也就是网关,当前端需要进行调用的时候,这个时候我们就能够使用Gateway,前端进行使用后端的业务的时候,我们就可以只用使用Gateway进行路由的请求,同时网关还可以进行负载均衡,流量控制,身份认证等等。

因为网关需要在微服务之间通过nacos获取服务的列表,同时进行路由的转发,这样我们只需要引入依赖即可。

XML 复制代码
<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-gateway</artifactId>
</dependency>

我们要是想用网关的话可以先进行创建模块

里面的yml文件

我们要想使用网关有两种方式第一种使用配置文件的方法实现:

XML 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: service-order
          uri: lb://service-order
          predicates:
            - Path=/api/order/**
        - id: service-product
          uri: lb://service-product
          predicates:
            - Path=/api/product/**

注意,为了实现负载均衡,新版本的idea需要独自引入依赖实现

XML 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

我们更新完之后,需要注意的地方就是我们这个访问路径固定是http://localhost/api/order所以我们还需要在业务的控制层,以及我们使用的Feign远程调用接口上也加上路径。@RequestMapping("/api/order"),值得注意的地方就是远程调用的接口不允许在类名上面添加,所以我们可以在方法路径上添加。

工作原理

网关的原理在于:当我们发送请求的时候,网关就会配置路由规则用来处理请求,路由规则的配置就需要我们进行配置,通过predicate断言机制进行配置路由,网关里面有一个HandlerMapping用来处理从哪里转请求,这一过程需要进行WebHandler进行路由的处理转发,转发的时候经过很多的Filter然后到达目的地,最终实现响应。

自定义断言工厂

注意路由的配置在yml文件当中是进行从上到下的,当然我们也可以使用order进行配置权重,让请求进行优先级的配置。

在我们的路由配置当中,我们可以设置很多的断言工厂也就是我们所需要的要求,当我们需要自定义配置的时候,我们就可以这样设置。

XML 复制代码
- id: bing-route
  uri: https://cn.bing.com/
  predicates:
    - name: Path
      args:
        patterns: /search
    - name: Query
      args:
        param: q
        regexp: haha
    - Vip=user,xianyu

注意:

我们先在配置文件里面进行配置,这样就可以先进行使用前的设置,然后就需要我们进行自定义的断言工厂进行配置

注意!!!

我们自定义的断言___RoutePredicateFactory前缀一定要和我们配置文件里面的name是一样的

java 复制代码
package com.kang.predicate;
​
import jakarta.validation.constraints.NotNull;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
​
import java.util.List;
import java.util.function.Predicate;
​
​
​
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {
    // 参数校验
    @Validated
    public static class Config {
        @NotNull
        private String param;
        @NotNull
        private String value;
​
        public @NotNull String getParam() {
            return param;
        }
        public void setParam(@NotNull String param) {
            this.param = param;
        }
        public @NotNull String getValue() {
            return value;
        }
        public void setValue(@NotNull String value) {
            this.value = value;
        }
    }
    // 指定我们使用自定义的参数和自定义的断言工厂
    public VipRoutePredicateFactory() {
        super(Config.class);
    }
​
    @Override//指定我们参数的顺序
    public List<String> shortcutFieldOrder() {
        return List.of("param", "value");
    }
​
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //localhost/search?q=haha&user=xianyu我们主要实现的就是这个逻辑
                ServerHttpRequest request = serverWebExchange.getRequest();
                // 判断参数的值是否等于我们指定的值
                return request.getQueryParams().getFirst(config.param).equals(config.value);
            }
        };
    }
}

当我们的断言工厂配置好之后,这样我们就可以进行我们自定义的搜索配置。

过滤器

过滤器在我们的网关的工作原理里面,它在请求的时候通过过滤器的进行修改,同样我们响应的时候一样倒序通过过滤器进行设置。

路径重写

过滤器里面有一种地址重写的方法,这样我们就不需要在微服务庞大的时候,在每一个控制台的路径进行重写。

这样我们就可以实现过滤器。同理当我们想要在响应头里面添加东西的时候也可以使用官方的自定义过滤器

java 复制代码
- AddResponseHeader=X-Response-Default-Foo, hello

这样我们就可以实现了

默认过滤器

当我们想要多个模块都能够进行配置,而我们又为了简单,这个时候就可以进行默认过滤器

java 复制代码
default-filters:
  - AddResponseHeader=X-Response-Default-Foo, hello

默认过滤器只要在yml文件里面进行简单的配置就可以实现我们特定过滤器实现的需求,并且是全局的。

Global Filter

复制代码
java 复制代码
package com.kang.filter;
​
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
​
@Slf4j
@Component
public class RtGlobalFilter implements GlobalFilter, Ordered {
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
​
        String uri = request.getURI().toString();
        log.info("请求【{ }】开始:时间:{} " + uri, System.currentTimeMillis());
​
​
        return chain.filter(exchange)
                .doFinally((result)->{
                    log.info("请求【{ }】结束:时间:{} " + uri, System.currentTimeMillis());
                });
​
    }
​
    @Override
    public int getOrder() {
        return 0;
    }
}

自定义过滤器

当我们想要在某个模块请求的时候,实现一个加密效果,所以我们就可以自己写一个拦截器,添加进我们想要的密钥。

我们写自定义过滤器的时候实际上跟之前写的Global过滤器是相差不大的

java 复制代码
package com.kang.filter;
​
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
import java.util.UUID;
​
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //需求的处理逻辑:添加一个一次性令牌,支持 uuid,jwt等各种格式
​
                return chain.filter(exchange).then(Mono.fromRunnable(
                        () -> {
                            ServerHttpResponse response = exchange.getResponse();
                            String value = config.getValue();
                            if("uuid".equals(config.getValue())){
                                value = UUID.randomUUID().toString();
                            }if ("jwt".equals(config.getValue())){
                                value = JwtUtils.generateToken(exchange.getRequest().getHeaders());
                            }
                            response.getHeaders().add(config.getName(),value);
                          
                        }
                ));
            }
        };
    }
}

我们的jwt令牌生成需要一个新的类

java 复制代码
package com.kang.filter;
​
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.http.HttpHeaders;
​
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
​
public class JwtUtils {
​
    private static final String SECRET_KEY = "mySecretKey"; // 实际项目中应该从配置文件读取
    private static final long EXPIRATION_TIME = 86400000; // 24小时 (毫秒)
​
    /**
     * 生成简单的JWT token
     * @param headers HTTP请求头
     * @return 生成的JWT token
     */
    public static String generateToken(HttpHeaders headers) {
        // 创建载荷
        Map<String, Object> claims = new HashMap<>();
        claims.put("tokenId", UUID.randomUUID().toString());
        claims.put("timestamp", System.currentTimeMillis());
​
        // 可以从请求头中提取一些信息加入到载荷中
        if (headers.getFirst("User-Agent") != null) {
            claims.put("userAgent", headers.getFirst("User-Agent"));
        }
​
        // 生成JWT token
        return Jwts.builder()
                .setClaims(claims)
                .setSubject("gateway-token")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
}

经过这样的设置就可以实现拦截器的功能了,当我们想换jwt的时候在配置文件当中替换即可。

跨域问题

前端发送请求的时候,经常会出现跨域问题,我们可以使用@CrossOrigin进行单体项目的解决,所以我们可以在网关上面解决跨域问题

当我们设置完之后,就可以在响应头里发现允许访问