Gateway+nacos动态网关配置

  • Post author:
  • Post category:其他


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
}



版权声明:本文为adminBfl原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。