gateway应用(1)

1 简介

简单理解---业务服务的统一入口,方便实现,服务路由,安全,限流,过滤,黑白名单,证书加密解密,服务降级/熔断,灰度,等等

2 介绍

  1. Predicate(断言):如果请求路径与断言相匹配则进行路由。(路径匹配是常见断言)
  2. Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
  3. Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,路由成功,则可以执行pre Filter ,服务响应回数据,可以执行 Post Filter。

3 注意事项

3.1

spring-boot-starter-parent和spring-cloud-starter-gateway版本要一致,否则可能报错

4 基本路由实战

4.1 pom文件

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gateway</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>gateway</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2.1.3.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>${spring-cloud.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${spring-cloud.version}</version>
        </dependency>
    </dependencies>

配置文件

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
     # config:
     #   server-addr: localhost:8848 # Nacos 服务器地址
    gateway:
      routes:
        - id: goods-server #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://goods-server      #匹配后提供服务的路由地址,lb负载
          predicates:
            - Path=/goods/**     # 断言,路径相匹配的进行路由
        - id: order-server
          uri: lb://order-server
          predicates:
            - Path=/order/**
            #- Path=/goods/,/goods/** 多个可以逗号隔开
      enabled: true #开启网关,默认true 如果程序引用了spring-cloud-starter-gateway,但不希望启用网关 设置为false

4.2 测试路由转发

另外一个 goods 服务,直接访问 http://localhost:8080/goods/good/list 即可出现数据

直接访问gateway服务 http://localhost/goods/good/list 自己就会转发到goods服务上

4.3 StripPrefix

有一种场景,前端会在所有的接口前加一个api。前端请求的路径是 http://localhost/api/goods/good/list 。实际上请求 http://localhost/goods/good/list 怎么办

StripPrefix=1的意思就是去掉路径上第一个,也就是api将会去掉。 当我们访问。http://localhost/api/goods/good/list 它会自动变成 http://localhost/goods/good/list

5 限流

添加 pom坐标

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

注意点:

配置文件中 redis-rate-limiter.replenishRate ,redis-rate-limiter.burstCapacity等同于配置bean中的 RedisRateLimiter,但是同时存在时,只有配置文件的配置生效。

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
     # config:
     #   server-addr: localhost:8848 # Nacos 服务器地址
    gateway:
      routes:
        - id: goods-server #路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://goods-server      #匹配后提供服务的路由地址
          predicates:
            - Path=/goods/**     # 断言,路径相匹配的进行路由
          filters:
            - name: RequestRateLimiter
              args:
                #spel表达式 它会去容器查找名称为hostKeyResolver 的Bean。
                key-resolver: '#{@hostKeyResolver}'
                #针对同一个key,允许的每秒请求数,不包括被抛弃的请求。这实际是令牌桶填充率。
                redis-rate-limiter.replenishRate: 1
                #针对同一个key,一秒内允许的最大请求数。这实际是令牌桶可容纳的最大令牌数。若设为0,则拒绝所有请求。
                redis-rate-limiter.burstCapacity: 1

package com.order.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

import java.util.Objects;

/**
 * @创建人 赵伟
 * @创建时间 2024/3/18
 * @描述
 */
@Slf4j
@Configuration
public class RouteConfig {
/*    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getQueryParams().getFirst("userId")
            //exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
        );
    }*/
/*    *//**
     * 根据 路径 进行限流
     *
     * @return KeyResolver
     *//*
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getPath()).toString());
    }*/
    /**
     * 根据 HostAddress 进行限流
     *
     * @return KeyResolver
     */
    @Bean
    public KeyResolver hostKeyResolver() {
        return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostString());
    }
    /**
     * Redis 令牌桶 限流
     *   这个等同于配置文件中的 redis-rate-limiter.replenishRate,redis-rate-limiter.burstCapacity
     *   同时存在时,加载的是配置文件中的配置
     * @return RedisRateLimiter
     */
    @Bean RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(1, 1);
    }
}

6黑白名单

/**
 * @描述 禁用ip
 */
public class BlackIpFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        return 0;
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String remoteIp = GatewayUtil.getRemoteIp(request);
        //查询 ip接口-缓存等实现
        if (true) {
            //返回禁用提示码
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
}

7 权限或token/续时/传递用户信息/串行/

package com.order.filter;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.order.entity.MyUser;
import com.order.utils.GatewayUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @创建人 赵伟
 * @创建时间 2024/3/20
 * @描述 禁用ip
 */
@Component //需要将实现设置为Spring的组件
public class BlackIpFilter implements GlobalFilter, Ordered {
    /**
     * 顺序执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //1 ip黑名单
        String remoteIp = GatewayUtil.getRemoteIp(request);
        //查询 ip接口-缓存等实现
        if (false) {
            //返回禁用提示码
            return loseToken(exchange,response);
          //  return exchange.getResponse().setComplete();
        }

        //2 OPTIONS 不允许
        if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
            //返回禁用提示码
            exchange.getResponse().setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
            return exchange.getResponse().setComplete();
        }



        //4 忽略的url
        String pass = "/aaa/getToken|/aaa/updatePwd";
        String uri = request.getURI().toString();
        if (isPass(pass, uri)) {
            return chain.filter(exchange);
        }

        //5 校验token
        List<String> tokenList = request.getHeaders().get("token");
        if (CollectionUtil.isEmpty(tokenList)) {
            return loseToken(exchange,response);
        }

        //6 根据token 获取redis用户信息判断是否失效,
        //注意多端系统生成token规则
        String token = tokenList.get(0);
        //自己写逻辑
        MyUser user = queryCacheByToken(token);
        if (ObjectUtil.isNull(user)) {
            return loseToken(exchange,response);
        }

        //7 已经获取到用户了,判断用户状态,是够禁用/用户密码强制多少天修改。 登录接口也要实现,
        boolean forbidden = false;//禁用
        if (forbidden) {
            return loseToken(exchange,response);
        }
        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
        //put是覆盖
      //  queryParams.put("userId", Collections.singletonList("123"));
        // add则不会覆盖,而是继续添加
     //   queryParams.add("userId","123");

        //3 设置请求头加串行id
        ServerHttpRequest.Builder mutate = request.mutate();
        //不存在新增,存在即修改
        mutate.header("serialId", UUID.randomUUID().toString());


        //8 赋值用户信息 可用jwt加密 子服务 通过拦截器 在解析,放到ThreadLocal
         //请求头中文乱码
      //  mutate.header("userInfo", String.valueOf(user));
        String s = Base64.getEncoder().encodeToString(String.valueOf(user).getBytes());
        mutate.header("userInfo", s );
        ServerHttpRequest build = mutate.build();
        exchange.mutate().request(build).build();

        //9 token续时
        //刷新token失效时间 是否每次请求都要续时,还是先获取判断小于一定时间在续时
      //  redisTemplate.setExpire(token, 60 * 60 * 24);

        return chain.filter(exchange);
    }

    /**
     * 假的获取缓存用户信息
     * @param token
     * @return
     */
    private MyUser queryCacheByToken(String token) {
        MyUser myUser = new MyUser();
        myUser.setUserId("123");
        myUser.setUserName("小明");
        myUser.setUpdatePasswordTime(new Date());
        return myUser;
    }

    /**
     * 失效token
     * @param exchange
     * @param response1
     * @return
     */
    private Mono<Void> loseToken(ServerWebExchange exchange,ServerHttpResponse response1) {
        // 设置响应的Content-Type头并指定编码为UTF-8
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
        JSONObject responseBody = new JSONObject();
        responseBody.put("result", Boolean.FALSE);
        responseBody.put("msg", "您当前登录状态已失效,请重新登录");
        responseBody.put("status", 400);
        responseBody.put("isSuccess",  Boolean.FALSE);
        String s = JSON.toJSONString(responseBody);
        byte[] bytes = new String(s.getBytes(), StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        return  exchange.getResponse().writeWith(Flux.just(buffer));


/*
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code","401");
        jsonObject.put("message","非法请求");
        byte[] datas = JSON.toJSONString(jsonObject).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(datas);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));*/


    }

    /**
     * 是否放行的url
     * @param passString
     * @param requestURI
     * @return
     */
    public static boolean isPass(String passString, String requestURI) {
        if (StringUtils.isNotBlank(passString)) {
            String[] split1 = passString.split("\\|");
            for (int i = 0; i < split1.length; i++) {
                String s = split1[i];
                if (!StringUtils.isBlank(s) && StringUtils.containsIgnoreCase(requestURI, s)) {
                    return true;
                }
            }
        }
        return false;
    }

}
相关推荐
珍珠是蚌的眼泪14 小时前
微服务_入门2
网关·微服务·gateway·远程调用·feign
铁板鱿鱼14015 小时前
统一网关--gateway(仅供自己参考)
gateway
bug菌¹15 小时前
滚雪球学SpringCloud[4.2讲]: Zuul:Netflix API Gateway详解
spring·spring cloud·gateway
炸裂狸花猫2 天前
Kubernetes从零到精通(12-Ingress、Gateway API)
容器·kubernetes·gateway
云来喜4 天前
关于Spring Cloud Gateway中 Filters的理解
java·spring boot·gateway
小小小小关同学4 天前
【Gateway】网关服务快速上手
java·开发语言·gateway
小小小小关同学4 天前
【Gateway】Gateway Filter Factories
网络·gateway
两仪式quq4 天前
服务网关Gateway快速入门
服务器·网络·gateway
szc17676 天前
Gateway学习笔记
笔记·学习·gateway
huaqianzkh9 天前
什么是API网关(API Gateway)?
架构·gateway