微服务网关-Spring Cloud Gateway

目录

一、什么是网关?

二、Spring Cloud Gateway 原理

三、构建简单的网关项目

四、路由谓词工厂

五、网关集群

六、动态网关

七、网关解决跨域

八、网关实现限流

九、设置黑白名单


一、什么是网关?

        网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。使用在不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。与网桥只是简单地传达信息不同,网关对收到的信息要重新打包,以适应目的系统的需求。同层--应用层。-----摘自《百度百科》

二、Spring Cloud Gateway 原理

参考官网spring-cloud-gateway

三、构建简单的网关项目

pom文件如下

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xiaojie</groupId>
    <artifactId>xiaojie-gateway</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
    </parent>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2021.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2021.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.0.2</version>
        </dependency>
        <!-- Feign Client for loadBalancing -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>3.0.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>3.0.2</version>
        </dependency>
        <!-- Feign Client for loadBalancing -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
            <version>3.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!-- mysql 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 阿里巴巴数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core -->
        <dependency>
            <groupId>com.github.vladimir-bukhtoyarov</groupId>
            <artifactId>bucket4j-core</artifactId>
            <version>6.0.1</version>
        </dependency>
    </dependencies>

</project>

启动类和简单api省略

四、路由谓词工厂

server:
  port: 80
spring:
  application:
    name: xiaojie-gateway
  cloud:
    gateway:
      routes:
#        - id: test_route
#          uri: https://www.baidu.com/
#          filters:
#            #这个意思是截取第一个前缀后访问 比如,请求/name/bar/foo,去除掉前面一个前缀之后,最后转发到目标服务的路径为/bar/foo
#            - StripPrefix=1
#          predicates:
#            - Path=/test #路径拦截
#             #ZonedDateTime dateTime = ZonedDateTime.now(); java8之后ZonedDateTime时间
#            - After=2021-07-14T09:51:55.052+08:00[Asia/Shanghai]
            #- Before=2021-07-15T09:51:55.052+08:00[Asia/Shanghai]
            #- Between=2021-07-14T09:51:55.052+08:00[Asia/Shanghai],2021-07-15T09:51:55.052+08:00[Asia/Shanghai]
            #- Cookie=xiaojie, [a-z]   #匹配cookie名字满足后面表达式的时候
            #- Header=X-Request-Id, \d+ # 匹配请求头中X-Request-Id 为数字的
            #- Host=**.xiaojie.com #匹配域名路由
            #- Method=GET,POST #拦截方法是get post 的请求
            #- Query=red, gree. #如果请求包含red其值与正则gree.表达式匹配的查询参数,则前面的路由匹配,因此green和greet将匹配
            #- RemoteAddr=192.168.1.1/24 #匹配ip
        #一下是权重路由匹配
        - id: weight_high
          uri: https://www.baidu.com
          predicates:
            - Weight=group1, 8
        - id: weight_low
          uri: https://www.sina.com
          predicates:
            - Weight=group1, 2


五、网关集群

通过Nginx转发到网关服务,网关经过轮训算法,或者其他的算法,转发到真实的服务器,进行服务的访问,下面开始配置。

网关端的配置文件

server:
  port: 82
####服务网关名称
spring:
  application:
    name: xiaojie-gateway
  cloud:
    gateway:
          ###路由策略
          routes:
            ###根据我们的服务名称查找地址实现调用
            - id: member
              #uri: http://127.0.0.1:8090
              uri: lb://xiaojie-member/ #服务的名称
              filters:
                - StripPrefix=1
              ###匹配规则
              predicates:
                - Path=/member/**
            #开启服务注册中心获取服务
          discovery:
            locator:
              enabled: true
    nacos:
      discovery:
        #注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #是否开启nacos注册
        enabled: true
        #账号
        username: xiaojie
        #密码
        password: nacos
        #命名空间 ,一定要对应上自己注册的服务的命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP

会员服务端的配置文件

spring:
  application:
     name: xiaojie-member
  cloud:
    nacos:
      config:
        #前缀
        prefix: xiaojie-member
        #地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #扩展名
        file-extension: yaml
        #命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP
        #开关
        enabled: true
        #动态刷新配置
        refresh-enabled: true
      discovery:
        #注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #是否开启nacos注册
        enabled: true
        #账号
        username: xiaojie
        #密码
        password: nacos
        #命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP

Nginx添加如下配置

upstream xiaojie_gateway{
		server 127.0.0.1:81;
		server 127.0.0.1:82;
	}
	
	server {
        listen       80;
        server_name  gateway.xiaojie.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            proxy_pass http://xiaojie_gateway;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }       
    }

在网关中添加以下filter

package com.xiaojie.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
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.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @Description: 配置token过滤器
 * @author: xiaojie
 * @date: 2021.07.14
 */
@Component
@Slf4j
public class TokenFilter implements GlobalFilter {
    @Value("${server.port}")
    private String serverPort;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取参数
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if(!StringUtils.hasText(token)){
            //token为空转发到其他的地址
            ServerHttpResponse response = exchange.getResponse();
            //
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            String msg = "token not is null ";
            DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        // 在请求头中存放serverPort serverPort
        ServerHttpRequest request = exchange.getRequest().mutate().header("serverPort", serverPort)
                .build();
        log.info("端口号是...............{}"+serverPort);
        return chain.filter(exchange.mutate().request(request).build());
    }
}

访问http://gateway.xiaojie.com/member/getMember?token=456 可以看到实现了集群效果。

六、动态网关

动态网管可以使用配置中心,Redis,和数据库(我用的方法)

直接上代码,其实就是增删改查

package com.xiaojie.entity;

import lombok.Data;

/**
 * @Description: 路由实体类
 * @author: xiaojie
 * @date: 2021.07.15
 */
@Data
public class Route {
    private Integer id;
    private String routeId; //路由id
    private String routeName;//路由名称
    private String routePattern;//路由转发地址,
    private Integer routeType;//路由类型1从注册中心获取,0-转发地址
    private String routeUrl;//转发到的地址
    private Integer isOpen;//是否开放0-否1-开放
    private Integer isUse;//是否可用0-否1-开放
}

package com.xiaojie.mapper;

import com.xiaojie.entity.Route;
import org.apache.ibatis.annotations.*;

import java.util.List;

/**
 * @Description:Mapper接口
 * @author: xiaojie
 * @date: 2021.07.15
 */
public interface RouteMapper {
    @Select("SELECT id,routeId,routeName,routePattern,routeType,routeUrl,is_open isOpen,is_use isUse FROM `tb_route` WHERE is_open=1 and is_use=1;")
    List<Route> selectAllRoute();

    @Insert("INSERT INTO tb_route(routeId,routeName,routePattern,routeType,routeUrl,is_open,is_use) " +
            "VALUES(#{routeId},#{routeName},#{routePattern},#{routeType},#{routeUrl},#{isOpen},#{isUse})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    Integer add(Route route);

    @Update("UPDATE tb_route set routePattern=#{routePattern},routeUrl=#{routeUrl} WHERE routeId=#{routeId}")
    Integer update(Route route);

    @Delete("DELETE FROM tb_route WHERE routeId=#{routeId}")
    Integer delete(String routeId);
}
package com.xiaojie.service.impl;

import com.xiaojie.entity.Route;
import com.xiaojie.mapper.RouteMapper;
import com.xiaojie.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description:
 * @author: xiaojie
 * @date: 2021.07.15
 */
@Service
@Slf4j
public class RouteServiceImpl  implements RouteService, ApplicationEventPublisherAware {
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    private RouteMapper routeMapper;
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    @Override
    public List<Route> getAll() {
        List<Route> routes = routeMapper.selectAllRoute();
        for (Route  route: routes){
            loadRoute(route);
        }
        return routes;
    }

    public void loadRoute(Route route) {
        RouteDefinition definition = new RouteDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        FilterDefinition filterDefinition = new FilterDefinition();
        Map<String, String> filterParams = new HashMap<>(8);
        URI uri = null;
        if (1==route.getRouteType()) {
            // 如果配置路由type为1的话 则从注册中心获取服务地址
            uri = UriComponentsBuilder.fromUriString(route.getRouteUrl()).build().toUri();
        } else {
            uri = UriComponentsBuilder.fromHttpUrl(route.getRouteUrl()).build().toUri();
        }

        // 定义的路由唯一的id
        definition.setId(route.getRouteId());
        predicate.setName("Path");
        //路由转发地址
        predicateParams.put("pattern", route.getRoutePattern());
        predicate.setArgs(predicateParams);
        filterDefinition.setName("StripPrefix");
        filterParams.put("_genkey_0", "1");
        filterDefinition.setArgs(filterParams);
        definition.setPredicates(Arrays.asList(predicate));
        definition.setFilters(Arrays.asList(filterDefinition));
        definition.setUri(uri);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
        log.info("路由刷新成功.............................");
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher=applicationEventPublisher;
    }

}

定义接口,当修改路由之后,需要刷新路由规则

  @RequestMapping("/refreshGw")
    public Object refreshGw() {
        return routeService.getAll();
    }

SQL如下

/*
 Navicat MySQL Data Transfer

 Source Server         : 本地
 Source Server Type    : MySQL
 Source Server Version : 80024
 Source Host           : localhost:3306
 Source Schema         : my_test

 Target Server Type    : MySQL
 Target Server Version : 80024
 File Encoding         : 65001

 Date: 15/07/2021 15:25:24
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_route
-- ----------------------------
DROP TABLE IF EXISTS `tb_route`;
CREATE TABLE `tb_route`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `routeId` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由id',
  `routeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由名称',
  `routePattern` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由转发地址',
  `routeType` tinyint NULL DEFAULT NULL COMMENT '路由类型1从注册中心获取,0-转发地址',
  `routeUrl` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '转发到的地址',
  `is_open` tinyint NULL DEFAULT NULL COMMENT '是否开放0-否1-开放',
  `is_use` tinyint NULL DEFAULT NULL COMMENT '是否可用0-否1-开放',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '动态网关表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_route
-- ----------------------------
INSERT INTO `tb_route` VALUES (1, 'member', 'xiaojie-member', '/member/**', 1, 'lb://xiaojie-member/', 1, 1);
INSERT INTO `tb_route` VALUES (3, 'baidu', 'xiaojie-baidu', '/bai/**', 0, 'http://www.baidu.com', 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

配置文件如下

server:
  port: 82
####服务网关名称
spring:
  application:
    name: xiaojie-gateway
  cloud:
    gateway:
          ###路由策略
                discovery:
                  locator:
                    enabled: true
    nacos:
      discovery:
        #注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #是否开启nacos注册
        enabled: true
        #账号
        username: xiaojie
        #密码
        password: nacos
        #命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    filters: stat
    initialSize: 10
    maxActive: 50
    maxOpenPreparedStatements: 20
    maxWait: 60000
    minEvictableIdleTimeMillis: 300000
    minIdle: 10
    password: root
    poolPreparedStatements: true
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: true
    timeBetweenEvictionRunsMillis: 60000
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    validationQuery: select 1

七、网关解决跨域

package com.xiaojie.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @Description: 解决微服务的跨域问题
 * @Author: yan
 * @Date: 2021/5/12 23:18
 * @return: null
 **/
@Component
public class CrossOriginFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = response.getHeaders();
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
        return chain.filter(exchange);
    }
}

八、网关实现限流

使用的算法是令牌桶 Redis+Lua

令牌流与令牌桶
系统会以一定的速度生成令牌,并将其放置到令牌桶中,可以将令牌桶想象成一个缓冲区(可以用队列这种数据结构来实现),当缓冲区填满的时候,新生成的令牌会被扔掉。 这里有两个变量很重要:

第一个是生成令牌的速度,一般称为 rate 。比如,我们设定 rate = 2 ,即每秒钟生成 2 个令牌,也就是每 1/2 秒生成一个令牌;
第二个是令牌桶的大小,一般称为 burst 。比如,我们设定 burst = 10 ,即令牌桶最大只能容纳 10 个令牌。
数据流
数据流是真正的进入系统的流量,对于接口来讲,如果平均每秒钟会调用2次,则认为速率为 2次/s 。

算法原理
系统接收到一个单位数据(对于网络传输,可以是一个包或者一个字节;对于微服务,可以是一个请求)后,从令牌桶中取出一个令牌,然后对数据或请求进行处理。如果令牌桶中没有令牌了,会直接将数据或者请求丢弃。当然,对于微服务,就不能是丢弃这么简单了:可以返回一个异常消息,用于提示用户其请求速率超过了系统限制。

有以下三种情形可能发生:

数据流的速率 等于 令牌流的速率。这种情况下,每个到来的数据包或者请求都能对应一个令牌,然后无延迟地通过队列;
数据流的速率 小于 令牌流的速率。通过队列的数据包或者请求只消耗了一部分令牌,剩下的令牌会在令牌桶里积累下来,直到桶被装满。剩下的令牌可以在突发请求的时候消耗掉。
数据流的速率 大于 令牌流的速率。这意味着桶里的令牌很快就会被耗尽。导致服务中断一段时间,如果数据包或者请求持续到来,将发生丢包或者拒绝响应。
比如前面举的例子,生成令牌的速率和令牌桶的大小分别为 rate = 2, burst = 10 ,则系统能承受的突发请求速率为 10次/s ,平均请求速率为 2次/s 。
摘自:https://blog.csdn.net/xgw1010/article/details/107595141

代码如下

package com.xiaojie.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * @Description:
 * @author: xiaojie
 * @date: 2021.07.16
 */
@Configuration
public class MyKeyResolver {

    /*
     *  ip限流 每一个ip只能在限流的规则内访问的规则,超出规则,进行限流
     * @todo
     * @author xiaojie
     * @date 2021/7/16 9:44
     * @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
     */
//    @Bean("ipKeyResolver")
//    @Primary
//    public KeyResolver ipKeyResolver() {
//        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
//    }
    /*
     *  用户限流 每一个用户的限流规则 访问时需要携带userId 参数
     * @todo
     * @author xiaojie
     * @date 2021/7/16 9:44
     * @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
     */
    @Bean("userKeyResolver")
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }


    /*
     * 接口限流 根据接口路径限流
     * @todo
     * @author xiaojie 
     * @date 2021/7/16 9:45 
     * @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
     */
//    @Bean("apiKeyResolver")
//    KeyResolver apiKeyResolver() {
//        return exchange -> Mono.just(exchange.getRequest().getPath().value());
//    }
}

路由配置如下

server:
  port: 82
####服务网关名称
spring:
  application:
    name: xiaojie-gateway
  cloud:
    gateway:
          ###路由策略
#          routes:
#            ###根据我们的服务名称查找地址实现调用
#            - id: member
#              #uri: http://127.0.0.1:8090
#              uri: lb://xiaojie-member/
#              filters:
#                - StripPrefix=1
#              ###匹配规则
#              predicates:
#                - Path=/member/*
                routes:
                  - id: requestratelimiter_route
                    uri: lb://xiaojie-member/
                    filters:
                      - name: RequestRateLimiter
                        args:
                          #允许用户每秒执行多少请求,而没有任何丢弃的请求。这是令牌桶填充的速率
                          redis-rate-limiter.replenishRate: 1
                          #允许用户在一秒内执行的最大请求数。这是令牌桶可以容纳的令牌数量。将此值设置为零会阻止所有请求。
                          redis-rate-limiter.burstCapacity: 1
                          #每个请求从存储桶中获取的令牌数量,默认为 1
                          redis-rate-limiter.requestedTokens: 1
                          key-resolver:
                            - "#{@userKeyResolver}" #根据用户限流
                      - StripPrefix=1
                    predicates:
                      - Path=/member/*
                #从注册中心获取服务
                discovery:
                  locator:
                    enabled: true
    nacos:
      discovery:
        #注册地址
        server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
        #是否开启nacos注册
        enabled: true
        #账号
        username: xiaojie
        #密码
        password: nacos
        #命名空间
        namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
        #分组
        group: DEV_GROUP
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    filters: stat
    initialSize: 10
    maxActive: 50
    maxOpenPreparedStatements: 20
    maxWait: 60000
    minEvictableIdleTimeMillis: 300000
    minIdle: 10
    password: root
    poolPreparedStatements: true
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: true
    timeBetweenEvictionRunsMillis: 60000
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    validationQuery: select 1
    #基于redis实现限流
  redis:
    host: 192.168.6.130
    password: xiaojie
    port: 6379

限流后返回429状态码

动态限流 基于Sentinel

九、设置黑白名单

package com.xiaojie.filter;


import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
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.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

/**
 * @Description: 黑名单
 * @author: xiaojie
 * @date: 2021.07.16
 */
@Component
@Slf4j
public class IpFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ipAddress = getIpAddress(request);
        log.info("获取的IP地址为{}",ipAddress);
        //实际生产可以基于redis,数据库实现记录
        if ("127.0.0.1".equals(ipAddress)){
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders httpHeaders = response.getHeaders();
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            String msg="已被加入黑名单";
            DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }
    /*
     * 获取用户真实ip 
     * @todo
     * @author xiaojie 
     * @date 2021/7/16 14:52
     * @return java.lang.String
     */
    public static String getIpAddress(ServerHttpRequest  request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }
}

参考:https://blog.csdn.net/lizz861109/article/details/108530364

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-prefixpath-gatewayfilter-factory

https://blog.csdn.net/qq_32652767/article/details/112257082

https://blog.csdn.net/xgw1010/article/details/107595141

感谢几位大佬

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熟透的蜗牛

永远满怀热爱,永远热泪盈眶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值