Gateway+nacos动态网关配置

springboot使用Gateway搭建网关服务及Nacos实现动态路由 springboot使用Gateway搭建网关服务及实现动态路由 阅读详情

1.添加依赖

<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2.nacos配置内容

1.新建配置
2.Data Id:butool-cloud-gateway-router
3.Group: butool-cloud
4.配置格式: JSON
5.配置内容
//配置不需要加注释
//获取路由对象,被网关反序列化成List<RouteDefinition>
[
    {
        "id":"butool-cloud-test",   //路由配置主键唯一,自定义即可
        "order":0, //优先级越小代表的优先级越高
        "predicates":[ //路由匹配
            {
              "args":{
                  "pattern":"/api/butool-cloud-test/**"  //先映射到网关api/butool-cloud-test 工程路径前缀  server.servlet.context-path
              },
              "name":"Path"  //路径匹配
              }
        ],
        "uri":"lb://butool-test", ////转发到哪个微服务serviceid  ’lb://’开头 spring.application.name
        "filters":[
            {
                "name":"HeaderToken" //header携带token验证过滤器,寻找HeaderTokenGatewayFilterFactor,GatewayFilterFactor前面的名称
            },
            {
               "name":"StripPrefix", //跳过前缀过滤器
               "args":{
                    "parts":"1" //跳过1个前缀,往后的路径实现转发
               }     
            }
        ]
    }
]

3.bootstrap.yal

server:
  port: 9001
  servlet:
    context-path: /butool
spring:
  application:
    name: butool-gateway
  cloud:
    nacos:
      # 服务注册发现
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
        server-addr: 127.0.0.1:8848
        # server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 集群方式服务器地址
        namespace: fdf5864c-f4da-47c3-9ef6-898a60cfff32
        # 接入到springBootAdmin
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator
  main:
    allow-bean-definition-overriding: true  # 因为将来会引入很多依赖, 难免有重名的 bean
# 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听
nacos:
  gateway:
    route:
      config:
        data-id: butool-cloud-gateway-router
        group: butool-cloud
​
# 暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
​

4.配置类

package cn.butool.config;
​
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
​
/**
 * 配置类:读取Nacos相关的配置项,用于配置监听器
 */
@SuppressWarnings("all")
@Configuration
public class GatewayConfig {
    /**
     * 读取配置的超时时间
     */
    public static final long DEFAULT_TIMEOUT = 30000;
    /**
     * Nacos 服务器地址
     */
    public static String NACOS_SERVER_ADDR;
​
    /**
     * Nacos 命名空间
     */
    public static String NACOS_NAMESPACE;
    /**
     * nacos 配置列表中的dataid
     */
    public static String NACOS_ROUTE_DATE_ID;
    /**
     * nacos分组id
     */
    public static String NACOS_GROUP_ID;
​
    @Value("${spring.cloud.nacos.discovery.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr) {
        NACOS_SERVER_ADDR = nacosServerAddr;
    }
​
    @Value("${spring.cloud.nacos.discovery.namespace}")
    public void setNacosNamespace(String nacosNamespace) {
        NACOS_NAMESPACE = nacosNamespace;
    }
​
    @Value("${nacos.gateway.route.config.data-id}")
    public void setNacosRouteDateId(String nacosRouteDateId) {
        NACOS_ROUTE_DATE_ID = nacosRouteDateId;
    }
​
    @Value("${nacos.gateway.route.config.group}")
    public void setNacosGroupId(String nacosGroupId) {
        NACOS_GROUP_ID = nacosGroupId;
    }
}
​

5.事件推送 Aware:

package cn.butool.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
​
import java.util.List;
​
/**
 * 事件推送 Aware: 动态更新路由网关 Service
 * */
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
​
    /** 写路由定义 */
    private final RouteDefinitionWriter routeDefinitionWriter;
    /** 获取路由定义 */
    private final RouteDefinitionLocator routeDefinitionLocator;
​
    /** 事件发布 */
    private ApplicationEventPublisher publisher;
​
    public DynamicRouteServiceImpl(RouteDefinitionWriter routeDefinitionWriter,
                                   RouteDefinitionLocator routeDefinitionLocator) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
    }
​
    @Override
    public void setApplicationEventPublisher(
            ApplicationEventPublisher applicationEventPublisher) {
        // 完成事件推送句柄的初始化
        this.publisher = applicationEventPublisher;
    }
​
    /**
     * 从nacos读取路由配hi,写道gateway中
     * <h2>增加路由定义</h2>
     * */
    public String addRouteDefinition(RouteDefinition definition) {
​
        log.info("gateway add route: [{}]", definition);
​
        // 保存路由配置并发布
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        // 发布事件通知给 Gateway, 同步新增的路由定义
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
​
        return "success";
    }
​
    /**
     * <h2>更新路由</h2>
     * */
    public String updateList(List<RouteDefinition> definitions) {
​
        log.info("更新网关路由: [{}]", definitions);
​
        // 先拿到当前 Gateway 中存储的路由定义
        List<RouteDefinition> routeDefinitionsExits =
                routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
        if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
            // 清除掉之前所有的 "旧的" 路由定义
            routeDefinitionsExits.forEach(rd -> {
                deleteById(rd.getId());
                log.info("清除掉之前所有的 旧的 路由定义: [{}]", rd);
            });
        }
​
        // 把更新的路由定义同步到 gateway 中
        definitions.forEach(definition -> updateByRouteDefinition(definition));
        return "success";
    }
​
    /**
     * <h2>根据路由 id 删除路由配置</h2>
     * */
    private String deleteById(String id) {
​
        try {
            log.info("要删除的路由id: [{}]", id);
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            // 发布事件通知给 gateway 更新路由定义
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "删除成功";
        } catch (Exception ex) {
            log.error("删除网关路由失败: [{}]", ex.getMessage(), ex);
            return "删除失败";
        }
    }
​
    /**
     * <h2>更新路由</h2>
     * 更新的实现策略比较简单: 删除 + 新增 = 更新
     * */
    private String updateByRouteDefinition(RouteDefinition definition) {
​
        try {
            log.info("更新网关路由: [{}]", definition);
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception ex) {
            return "更新失败,没有查到更新的网关路由id: " + definition.getId();
        }
​
        try {
            this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "成功";
        } catch (Exception ex) {
            return "更新路由失败!";
        }
    }
}

6.通过Nacos下发的动态配置,监听nacos中路由配置

package cn.butool.config;
​
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
​
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
​
/**
 * 通过Nacos下发的动态配置,监听nacos中路由配置
 */
@Slf4j
@Component
//再另外一个Bean初始化之后再去初始化当前类
@DependsOn({"gatewayConfig"})
@SuppressWarnings("all")
public class DynamicRouteServiceImplNacos {
​
    private ConfigService configService;
​
    private final DynamicRouteServiceImpl dynamicRouteService;
​
    public DynamicRouteServiceImplNacos(DynamicRouteServiceImpl dynamicRouteService) {
        this.dynamicRouteService = dynamicRouteService;
    }
​
    /**
     * 初始化ConfigService
     *
     * @return
     */
    private ConfigService initConfigService() {
        try {
            Properties properties = new Properties();
            properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
            properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);
            return configService = NacosFactory.createConfigService(properties);
        } catch (Exception ex) {
            log.error("初始化配置服务:[{}]", ex.getMessage());
            return null;
        }
    }
​
    /**
     * bean在容器中构造完成之后会立即执行当前的init方法
     * 加载路由信息,注册监听器
     */
    @PostConstruct
    public void init(){
        log.info("网关路由初始化...");
​
        try {
            //初始化nacos配置客户端
            configService = initConfigService();
            if(configService==null){
                log.error("初始化配置服务异常,配置服务是null!");
                return;
            }
            //通过 nacos config 并指定路由配置路径去获取路由配置
            String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATE_ID,
                    GatewayConfig.NACOS_GROUP_ID,
                    GatewayConfig.DEFAULT_TIMEOUT);
            log.info("当前网关配置信息:[{}]:",configInfo);
            //反序列化
            List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
            if(CollectionUtil.isNotEmpty(routeDefinitions)){
                //新增
                for (RouteDefinition routeDefinition : routeDefinitions) {
                    log.info("初始化 路由定义对象 信息:[{}]:",routeDefinition);
                    dynamicRouteService.addRouteDefinition(routeDefinition);
                }
​
            }
        } catch (Exception e) {
            log.error("网关路由初始化失败:[{}]",e.getMessage());
        }
        //设置监听器
        dynamicRouteServiceImplNacosByListener(GatewayConfig.NACOS_ROUTE_DATE_ID,GatewayConfig.NACOS_GROUP_ID);
    }
​
    /**
     * 监听 Nacos下发的动态路由配置
     *
     * @param dataId
     * @param group
     */
    private void dynamicRouteServiceImplNacosByListener(String dataId, String group) {
        try {
            //给Nacos config 客户端增加一个监听器
            configService.addListener(dataId, group, new Listener() {
                /**
                 * 自己提供线程池执行操作
                 * @return
                 */
                @Override
                public Executor getExecutor() {
                    return null;
                }
​
                /**
                 * 监听器收到接收到配置变更信息
                 * @param configInfo nacos 中最新配置信息
                 */
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("接收的配置信息:[{}]", configInfo);
                    List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                    dynamicRouteService.updateList(routeDefinitions);
                    log.info("更新后的路由配置信息:[{}]", routeDefinitions.toString());
                }
            });
        } catch (Exception e) {
            log.error("监听 Nacos下发的动态路由配置异常:[{}]", e.getMessage());
        }
    }
}
​

7.局部过滤器

7.1过滤器实现

package cn.butool.filter;
​
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
/**
 * header携带token验证过滤器
 */
public class HeaderTokenGateWayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //从HttpHeader中寻找key为token,value为butool的键值对
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if("butool".equals(token)){
            return chain.filter(exchange);
        }
        // 标记此次请求没有权限,并且结束这次请求
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        //请求结束
        return exchange.getResponse().setComplete();
    }
​
    /**
     * 优先级
     * @return
     */
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE+2;
    }
}

7.2工厂实现类

package cn.butool.filter.factory;
​
import cn.butool.filter.HeaderTokenGateWayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
​
@Component
public class HeaderTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        return new HeaderTokenGateWayFilter();
    }
}

7.3配置文件中进行配置

//注意命名规则 
"filters":[
            {
                "name":"HeaderToken" //header携带token验证过滤器,寻找HeaderTokenGatewayFilterFactor,GatewayFilterFactor前面的名称
            },
            {
               "name":"StripPrefix", //跳过前缀过滤器
               "args":{
                    "parts":"1" //跳过1个前缀,往后的路径实现转发
               }     
            }
        ]

7.4局部过滤器步骤

1.新建过滤器实现
2.新建工厂类实现
3.配置文件中进行配置

8.缓存请求body的全局过滤器

package cn.butool.constan;
​
/**
 * 网关常量定于
 */
@SuppressWarnings("all")
public class GatewayConstant {
    /**登录的url*/
    public static final String LOGIN_URL = "/butool/login";
​
    /** 注册的url*/
    public static final String REGISTER_URL = "/butool/register";
​
    /** 去授权中心拿到登录 token 的 uri 格式化接口 */
    public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT =
            "http://%s:%s/butool-cloud-Authority-center/authority/token";
​
    /** 去授权中心注册并拿到 token 的 uri 格式化接口 */
    public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT =
            "http://%s:%s/butool-cloud-Authority-center/authority/register";
​
​
}
​


package cn.butool.filter;
​
import cn.butool.constan.GatewayConstant;
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.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
​
/**
 * <h1>全局缓存请求Body过滤器</h1>
 * 将请求数据缓存方便后面的过滤器拿取
 * spring WebFlux
 */
@Slf4j
@Component
public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered {
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange
                .getRequest()
                .getURI()
                .getPath();
        boolean isLoginOrRegister =
                path.contains(GatewayConstant.LOGIN_URL) || path.contains(GatewayConstant.REGISTER_URL);
        // 如果没有请求头或者不是登录注册接口,不用去缓存了,往下走下面的过滤器
        if (null == exchange.getRequest().getHeaders().getContentType() || !isLoginOrRegister) {
            return chain.filter(exchange);
        }
        return DataBufferUtils
                .join(exchange.getRequest().getBody()) //获取请求数据
                .flatMap(dataBuffer -> {
                    // 确保数据缓冲区不被释放, 必须要 DataBufferUtils.retain
                    DataBufferUtils.retain(dataBuffer);
                    // defer、just 都是去创建数据源, 得到当前数据的副本
                    Flux<DataBuffer> cachedFlux = Flux.defer(() ->
                            Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                    // 重新包装 ServerHttpRequest, 重写 getBody 方法, 能够返回请求数据
                    ServerHttpRequest mutatedRequest =
                            new ServerHttpRequestDecorator(exchange.getRequest()) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return cachedFlux;
                                }
                            };
                    // 将包装之后的 ServerHttpRequest 向下继续传递
                    return chain.filter(exchange.mutate().request(mutatedRequest).build());
                });
    }
​
    /**
     * 下面过滤器要拿到当前过滤器缓存内容优先级要比当前返回数值要大
     *
     * @return
     */
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 1;
    }
}
​

9.全局登录、鉴权过滤器

9.1注入RestTemplate对象

package cn.butool.conf;
​
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
​
/**
 * 网关需要注入到容器中的bean
 */
@Configuration
public class GatewayBeanConf {
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

​

9.2鉴权授权登陆注册接口常量

package cn.butool.constan;
​
/**
 * 网关常量定于
 */
@SuppressWarnings("all")
public class GatewayConstant {
    /**登录的url*/
    public static final String LOGIN_URL = "/butool/login";
    public static final String LOGINL = "/authority/login";
​
    /** 注册的url*/
    public static final String REGISTER_URL = "/butool/register";
    public static final String REGISTER = "/authority/register";
​
    /** 去授权中心拿到登录 token 的 uri 格式化接口 */
    public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT =
            "http://%s:%s/butool-cloud-Authority-center/authority/token";
​
    /** 去授权中心注册并拿到 token 的 uri 格式化接口 */
    public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT =
            "http://%s:%s/butool-cloud-Authority-center/authority/register";
​
​
}

​

9.3登录鉴权过滤器

package cn.butool.filter;
​
import cn.butool.constan.GatewayConstant;
import cn.butool.constant.CommonConstant;
import cn.butool.util.TokenParseUtil;
import cn.butool.vo.JwtToken;
import cn.butool.vo.LoginUserInfo;
import cn.butool.vo.UserRegisterInfo;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
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.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpEntity;
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.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
​
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
​
/**
 * <h1>全局登录、鉴权过滤器</h1>
 * spring WebFlux
 */
@Slf4j
@Component
public class GlobalLoginOrRegisterFilter implements GlobalFilter, Ordered {
    /**
     * 注册中心客户端, 可以从注册中心中获取服务实例信息
     */
    private final LoadBalancerClient loadBalancerClient;
    /**
     * 发送http请求
     */
    private final RestTemplate restTemplate;
​
    public GlobalLoginOrRegisterFilter(LoadBalancerClient loadBalancerClient,
                                       RestTemplate restTemplate) {
        this.loadBalancerClient = loadBalancerClient;
        this.restTemplate = restTemplate;
    }
​
    /**
     * 登录注册鉴权
     * 1.如果时登录注册,则去授权中心拿到token 并返回给客户端
     * 2.如果是访问其他服务,则鉴权,没权限返回 401
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //1.如果是登录
        if(request.getURI().getPath().contains(GatewayConstant.LOGIN_URL)){
            //去授权中心拿token
            String token = getTokenFromAuthorityCenter(request, GatewayConstant.AUTHORITY_CENTER_TOKEN_URL_FORMAT);
            // header   中不能设置 null
            response.getHeaders()
                    .add(CommonConstant.JWT_USER_INFO_KEY,null==token?"null":token);
            response.setStatusCode(HttpStatus.OK);
            return response.setComplete();
        }else if(request.getURI().getPath().contains(GatewayConstant.REGISTER_URL)){
            //去授权中心拿token
            String token = getTokenFromAuthorityCenter(request, GatewayConstant.AUTHORITY_CENTER_REGISTER_URL_FORMAT);
            // header   中不能设置 null
            response.getHeaders()
                    .add(CommonConstant.JWT_USER_INFO_KEY,null==token?"null":token);
            response.setStatusCode(HttpStatus.OK);
            return response.setComplete();
        }
        // 3.访问其他服务,则鉴权,校验是否能从token中解析出用户信息
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(CommonConstant.JWT_USER_INFO_KEY);
        LoginUserInfo loginUserInfo = null;
        try {
            loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);
        } catch (Exception ex) {
            log.error("parse user info from token error: [{}]", ex.getMessage(), ex);
        }
        if(null == loginUserInfo){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //解析通过则放行
        return chain.filter(exchange);
    }
​
    /**
     * <h2>从授权中心获取 Token</h2>
     */
    private String getTokenFromAuthorityCenter(ServerHttpRequest request, String uriFormat) {
​
        // service id 就是服务名字, 负载均衡
        ServiceInstance serviceInstance = loadBalancerClient.choose(
                CommonConstant.AUTHORITY_CENTER_SERVICE_ID
        );
        log.info("Nacos Client Info: [{}], [{}], [{}]",
                serviceInstance.getServiceId(), serviceInstance.getInstanceId(),
                JSON.toJSONString(serviceInstance.getMetadata()));
​
        String requestUrl = String.format(
                uriFormat, serviceInstance.getHost(), serviceInstance.getPort()
        );
        JwtToken token = null;
        if(uriFormat.contains(GatewayConstant.LOGINL)){
            LoginUserInfo requestBody = JSON.parseObject(
                    parseBodyFromRequest(request), LoginUserInfo.class
            );
            log.info("login request url and body: [{}], [{}]", requestUrl,
                    JSON.toJSONString(requestBody));
​
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            token = restTemplate.postForObject(
                    requestUrl,
                    new HttpEntity<>(JSON.toJSONString(requestBody), headers),
                    JwtToken.class
            );
        }else if(uriFormat.contains(GatewayConstant.REGISTER)){
            UserRegisterInfo requestBody = JSON.parseObject(
                    parseBodyFromRequest(request), UserRegisterInfo.class
            );
            log.info("注册请求url地址和请求body: [{}], [{}]", requestUrl,
                    JSON.toJSONString(requestBody));
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            token = restTemplate.postForObject(
                    requestUrl,
                    new HttpEntity<>(JSON.toJSONString(requestBody), headers),
                    JwtToken.class
            );
        }
        if (null != token) {
            return token.getToken();
        }
        return null;
    }
​
    /**
     * 从post请求中获取请数据
     *
     * @param request
     * @return
     */
    private String parseBodyFromRequest(ServerHttpRequest request) {
        // 获取请求体
        Flux<DataBuffer> body = request.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
​
        // 订阅缓冲区去消费请求体中的数据
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            // 一定要使用 DataBufferUtils.release 释放掉, 否则, 会出现内存泄露
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
​
        // 获取 request body
        return bodyRef.get();
    }
​
    /**
     * 下面过滤器要拿到当前过滤器缓存内容优先级要比当前返回数值要大
     *
     * @return
     */
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE + 2;
    }
}

9.4配置登录请求过滤器

package cn.butool.config;
​
import cn.butool.constan.GatewayConstant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
/**
 * 配置登录请求转发规则
 */
@Configuration
public class RouteLocatorConfig {
    /**
     * port
     */
    public static String port;
    @Value("${server.port}")
    public void setNacosGroupId(String port) {
        port = port;
    }
​
    /**
     * 使用代码定义路由规则,再网关层面拦截下登录和注册
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator loginRouteLocator(RouteLocatorBuilder builder){
        // 手工定义gateway需要指定id、path和url
        return builder.routes()
                .route(
                        "butool-cloud-authority-center",
                        r->r.path("/butool"+GatewayConstant.LOGIN_URL,
                                "/butool"+GatewayConstant.REGISTER_URL)
                .uri("http://localhost:"+port+"/")
        ).build();
    }
}
​

10.全局接口耗时过滤器

package cn.butool.filter;
​
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
import java.util.concurrent.TimeUnit;
​
/**
 * <h1>全局接口耗时日志过滤器</h1>
 * */
@Slf4j
@Component
public class GlobalElapsedLogFilter implements GlobalFilter, Ordered {
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
​
        // 前置逻辑
        StopWatch sw = StopWatch.createStarted();
        String uri = exchange.getRequest().getURI().getPath();
​
        return chain.filter(exchange).then(
                // 后置逻辑
                Mono.fromRunnable(() ->
                        log.info("[{}] elapsed: [{}ms]",
                                uri, sw.getTime(TimeUnit.MILLISECONDS)))
        );
    }
    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }
}

11.注册登录测试

### 获取 Token -- 登录功能实现
POST http://127.0.0.1:9001/butool/manage/login
Content-Type: application/json
​
{
  "loginType": 1,
  "mobile": "18033664455",
  "password":"0192023a7bbd73250516f069df18b500",
  "expire":0
}
​
### 注册用户并返回 Token -- 注册功能实现
POST http://127.0.0.1:9001/butool/manage/register
Content-Type: application/json
​
{
  "loginType": 1,
  "mobile": "18033664455",
  "nickname": "busl",
  "password":"0192023a7bbd73250516f069df18b500",
  "Expire":0
}


Gateway基于Nacos动态路由实现 Gateway Nacos 动态路由 阅读详情

相关推荐

【1.3 API服务网关(SpringCloudGateway)】Nacos下发路由配置实现Spring Cloud Gateway的动态路由

Spring Cloud Gateway作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,需要实现Spring Cloud Gateway动态路由配置。

奶爸的编程之路,也就一周冷个三天 2103

nacos网关

总之,这段代码设置了Spring Cloud Gateway 的跨域规则,并定义了一条路由规则,将所有以 "/wemedia" 开头的请求转发到了 "leadnews-wemedia" 这个服务集群,并在转发之前删除掉请求路径的第一个前缀。这段配置看起来是用于配置某个应用的配置中心的动态配置刷新(Dynamic Configuration Refresh)的一部分。当配置中心的配置发生变化时,带有这个标志的配置文件可以被动态地刷新,应用程序不需要重新启动即可应用新的配置。账号密码均为nacos。

weixin_67573348的博客 1186

数码管动态扫描避坑指南:如何用188芯片实现50Hz无闪烁显示?

本文深入解析188数码管动态扫描技术,提供工业级无闪烁显示方案。通过优化轮询扫描机制、硬件电路设计和软件架构,实现50Hz高刷新率显示,解决亮度不均和鬼影问题,适用于医疗设备和工业控制面板等高要求场景。

hhh00的博客 974

微服务项目搭建:Nacos以及网关的配置

本文主要记录微服务项目结构搭建里的nacos和gateway

Java_Zzm的博客 1599

Windows系统监控实战:Grafana+Prometheus+node_exporter保姆级配置指南(含常见问题解决)

本文提供了一份详尽的Windows系统监控实战指南,详细讲解了如何从零部署Grafana、Prometheus和node_exporter(实际为windows_exporter)监控栈。内容涵盖核心组件认知、分步配置解析、高级生产环境考量,并重点针对配置过程中常见的“No Data”问题提供了系统性的排查手册,旨在帮助用户快速搭建稳定可靠的系统资源监控平台。

weixin_29197835的博客 1007

ros_control硬件接口实战:从零构建机器人控制框架

本文详细介绍了ros_control硬件接口在机器人控制框架中的核心作用与实践方法。通过解析硬件接口类型、实现标准接口、构建主控制循环及自定义功能扩展,帮助开发者从零构建高效的机器人控制系统,提升硬件与ROS的协同效率。

lstm7chronicler的博客 498

spring cloud gateway整合sentinel + nacos 实现网关限流和持久化动态配置

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。 从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流: route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组 Sentinel 1.6.0 引入了 Sentinel A

qq_33210338的博客 8020

【机器学习实战中阶】音乐流派分类-自动化分类不同音乐风格

数据下载地址:https://www.kaggle.com/datasets/andradaolteanu/gtzan-dataset-music-genre-classification/data背景音乐。专家们长期以来一直在尝试理解声音以及是什么使一首歌曲与另一首不同。如何可视化声音。是什么使一个音调与另一个不同。这个数据集希望能提供实现这些目标的机会。内容原始流派 - 包含10种流派,每种流派有100个音频文件,每个文件长度为30秒(著名的GTZAN数据集,声音的MNIST)

2143

网关Gateway nacos 实现动态路由的配置

首先加入spring-boot-starter-actuator的依赖,然后在配置文件中暴露gateway即可 management: endpoints: web: exposure: include: "*" 之后就可以监控gateway 的各个节点信息了,具体访问如下所示 ID HTTP Method Description globalfilters GET 展示所有的全局过滤器 routefilters GET

架构师的成长之路的博客 3749

HFSS实战:手把手教你设计微带发夹线滤波器(附完整参数计算流程)

本文详细介绍了使用HFSS设计微带发夹线滤波器的完整流程,包括参数计算、HFSS建模和优化策略。通过实战案例,帮助读者掌握微波滤波器设计的核心技巧,特别是奇偶模阻抗计算和HFSS仿真中的关键设置,适用于通信系统设计和射频工程应用。

weixin_29251567的博客 440

ubuntu20.04安装Qt5.15.2并配置环境

qt官方取消了离线安装的方式,所以推荐在线安装,当然,如果想要自己通过源码编译也可以,参考官方文档:https://wiki.qt.io/Building_Qt_5_from_Git。 在线安装: 1.到官方网站下载:wget https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run 2.修改文件权限:chmod 777 qt-unified-linux-x64-online.run

f120854632的博客 8806

Nacos结合Gateway配置动态网关

Nacos结合Gateway配置动态网关

lrclrk的博客 770

Excel数据清洗实战:8个必学函数搞定90%的脏数据问题(附案例)

本文通过一个完整的销售数据清洗案例,详细讲解了如何组合使用TRIM、CLEAN、SUBSTITUTE等8个核心Excel函数,构建自动化清洗流水线,高效解决日常办公中90%的脏数据问题,从而将时间留给更有价值的分析工作。

weixin_29215391的博客 582

(新)Gateway网关+基于Nacos配置动态路由

无论是还是都支持自定义,只不过编码方式、使用方式略有差别。自定义不是直接实现,而是实现。@Component@Override@Override// 获取请求// 编写过滤器逻辑System.out.println("过滤器执行了");// 放行注意:该类的名称一定要以为后缀!spring:cloud:gateway:- PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器@Component。

· 1729

《AI大模型评测与智能测试》

第三篇“实践蓝图”(第6-7章)着眼于测试领域AI应用,重点介绍了RAG、AI Agent开发及构建端到端智能测试系统的方法。本书聚焦AI时代的“质量”核心,以“AI赋能测试”(AI For Testing)与“AI模型评测”(Testing For AI)为双轮驱动。内容不仅深入剖析如何科学度量AI大模型的内在质量,还系统讲解如何利用AI赋能传统软件测试,帮助读者贯通从模型原理、评测到应用开发的全链路知识。

酌沧 1138

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Gateway 网关 (一) Nacos 动态配置路由

1、准备一个Nacos环境跟一个普普通通的Spring Cloud项目 2、导包,我这里是把Nacos的注册中心跟配置中心都搞了,动态配置的话主要是用到配置中心,主要是这几个包,部分工具类的包或者是spring cloud的其他包就忽略了。 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-s

qq_33601179的博客 8736

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

SpringCloud Gateway 和 Nacos 动态网关的相关配置

pom及spring-cloud、spring-cloud-alibaba版本 <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-boot.version>2.3.2.RELEASE</sp

z69183787的专栏 2238

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Java 程序员第 45 阶段06:网关统一路由大模型接口,配合 Nacos 配置治理,Gateway 动态路由:基于 Nacos 配置实时刷新路由

Gateway 启动时,`RouteLocator` 会把所有 `RouteDefinition` 转成 `Route` 并缓存为 `Flux<Route>`。Spring Cloud 的 `@RefreshScope` 配合 `ContextRefresher`,能在收到 `/actuator/refresh` 或 Nacos 推送后,**销毁并重建被该注解标记的 Bean**,从而让 `@Value`、`@ConfigurationProperties` 拿到新值。

224

Spring Cloud Alibaba微服务实战:Gateway统一网关配置与Nacos动态路由落地

Spring Cloud Alibaba微服务实战:用Gateway统一网关配合Nacos动态路由,通过lb://服务名实现负载均衡转发。本文提供可复制的pom依赖、application.yml路由配置与StripPrefix避坑要点,并演示停实例验证动态感知。模型联调可用TaoToken统一管理密钥,详见官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 。

weixin_42601608的博客 141

SpringCloudAlibaba 五、 Gateway 网关实现/ 服务转发/ 动态配置/ 参数过滤 / 自定义异常 / 整合swagger2 /整合sentinel(注册中心为 Nacos)

一、Gateway 网关描叙及说明 1、微服务网关描叙 微服务网关是整个微服务API请求的入口,可以实现日志拦截、权限控制、解决跨域问题、 限流、熔断、负载均衡、黑名单与白名单拦截、授权等。 2、Gateway与Zuul的区别? Zuul网关属于netfix公司开源的产品属于第一代微服务网关 Gateway属于SpringCloud自研发的第二代微服务网关 相比来说SpringCloudGatew...

ws - 兮的博客空间 4817

nacos动态网关gateway+docker部署

首先启动nacos,在docker中安装好,启动nacos服务 nacos的管理页面: nacos默认端口是8848 编写网关服务: nacos-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-insta...

你一个人,就不想到别处看看? ---- 我怕一转身连你也不见了...... 3074

网关Gateway基本配置实战:TaoToken统一Key接入Nacos动态路由

Spring Cloud Gateway 网关配置实战:用 application.yml 骨架接入 Nacos 服务发现与动态路由,并统一持有 TaoToken Key 转发 AI 请求。含依赖、路由断言、StripPrefix 与 curl 验证步骤,快速排查 404 与实例找不到问题。官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end

weixin_35006125的博客 182

NACOS 热更新与Gateway 网关的路由配置更新的处理

摘要:本文介绍了基于NACOS实现Gateway网关动态路由配置的方法。通过在NACOS中创建gateway-routes.json配置文件,利用SyncDynamicRouter类监听配置变更并实时更新路由。关键实现包括:1)使用NacosConfigManager管理配置;2)通过Listener监听配置变化;3)采用RouteDefinitionWriter动态更新路由。该方法无需重启服务即可实现路由配置的热更新,支持灵活添加新模块和业务属性,有效提升了网关的灵活性和可用性。核心代码展示了配置读取、路

RR1335 之代码才是动力源 422

Nacos - 动态路由配置:Spring Cloud Gateway 实战

本文介绍了如何利用Nacos实现Spring Cloud Gateway的动态路由配置。首先概述了Spring Cloud Gateway的核心功能和Nacos作为动态服务发现与配置管理平台的优势,强调两者结合可实现路由规则的动态加载与更新。接着详细说明了集成环境准备,包括JDK版本、Nacos Server启动步骤以及必要的Maven依赖配置。文章通过清晰的步骤指导,帮助开发者快速掌握这一关键技术组合,构建灵活可维护的微服务API网关系统。

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

Spring Cloud Alibaba系列-第11节-GateWay网关,路由,nacos注册,动态路由

1. 网关的介绍 1.1 API网关的作用 修改前以如上图所示:模拟客户端请求到微服务会出现如下问题: 客户端发送不同的请求到微服务,需要知道不同的微服务的服务地址 多次发送请求,会增加网络传输 鉴权等会分布在每个微服务中处理,重复操作 后端微服务中,可能采用不同的协议,客户端需要按协议适配 如上图所示:在客户端与微服务中增加网关层,作用如下: API网关可以把后端的多个服务进行整合,提供唯一的业务接口(请求转发),客户端只需要调用这个接口即可完成数据获取与展示。 针对所有的请求进行统一

皮卡肉丘 1290

学习笔记—微服务—技术栈实践(4)—网关+Nacos的动态路由

这样,网关可以根据 Nacos 配置的变化自动刷新路由规则,实现动态路由配置的功能。Spring Gateway的动态路由功能允许根据特定的规则在运行动态配置路由,而无需重新启动应用,比较常见的方式是通过数据库、配置中心或者API来加载或更新路由规则,于此,介绍以下采用配置中心实现动态路由的方式。那么,很显然,靠着网关在配置文件里面配置好的路由路径是比较死板的,动态路由是一个很重要的东西,以此减少对代码的修改。之前已经对网关的简单使用有了一个初步的了解,并且对于配置中心对配置的管理也有了一个简单的了解。

qq_34257208的博客 990
上一篇: 搭建Springboot admin监控服务器
下一篇: Docker安装MinIO详细步骤
别浪呀
博客等级 码龄8年 1万+粉丝 260原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值