gateway+resilience4j+consul

1.pom文件

		<org.springframework.boot.version>2.2.2.RELEASE</org.springframework.boot.version>
        <org.springframework.boot.maven.version>2.1.0.RELEASE</org.springframework.boot.maven.version>
        <org.springframework.cloud.version>Hoxton.SR1</org.springframework.cloud.version>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--    降级熔断  resilience4j  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>
        <!--resilience4j-spring-boot2中包含了resilience4j 的所有功能  -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
    </dependencies>

2.配置yml文件

gateway:
  matcher:
    whitelist: #gateway白名单
      - /**/api-docs # swagger
      - /consul-service/**
      - /**/**.css
      - /**/*.js

server:
  port: 80

logging:
  file: './log/gateway.log'
  level:
    root: ${LOG_LEVE:info}

spring:
  application:
    name: gateway
  cloud:
    consul:
      enabled: true
      host: localhost
      port: 8500
      discovery:
        enabled: true # 启用服务发现
        prefer-ip-address: true # 表示注册时使用IP而不是hostname
        register: true    #是否将自身服务注册到consul中
        deregister: true # 服务停止时取消注册
        hostname: localhost
        health-check-path: /actuator/health  #consul默认健康检查地址,可以通过management进行修改
        health-check-interval: 15s #健康检查间隔,默认10s
        health-check-critical-timeout: 30s # 设置健康检查失败多长时间后,取消注册
        service-name: ${spring.application.name}
        instance-id: ${spring.application.name}-${spring.cloud.client.ip-address}-${server.port}  # 服务id
        tags: gatewayDemo

    gateway:
      globalcors: #配置跨域请求
        cors-configurations:
          '[/**]':
            allowed-origins: "*" #允许来源
            allowed-headers: "*" #允许的请求头
            allow-credentials: true #允许证书
            allowed-methods: #允许的请求方式
              - "*"
            maxAge: 18000 #预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: api
          uri: lb://api
          predicates:
            - Path=/api/**
          filters:
#            - StripPrefix=1  #前缀, 在当前路径匹配中表示去掉第一个前缀 /data-service2
      default-filters: #全局过滤,可以配置全局过滤,也可以给具体的路由配置过滤
        - StripPrefix=1  #前缀, 在当前路径匹配中表示去掉第一个前缀 /consul-service
        - name: CircuitBreaker #使用resilience4j断路器
          args:
            name: fallback  #自定义断路器配置
            fallbackUri: forward:/circuitBreaker/fallback #异常跳转 resilience4j中使用的fallbackMethod优先级大于这个

resilience4j:
  retry: #请求重试
    retry-aspect-order: 396 # 表示Retry优先级(级别高于比如ratelimiter bulkhead timelimiter) 值越小 优先级 越高
    configs:
      myConfig: # 设置组名 可以配置不同的 Retry 策略,给不同的策略分别取一个名字, default 就是一个 Retry 策略的名字 默认名称就是default
        maxRetryAttempts: 5 # 表示最大重试次数
        waitDuration: 500 # 表示下一次重试等待时间,最小为100 ms
        eventConsumerBufferSize: 10 #重试事件缓冲区大小
        enableExponentialBackoff: true #是否开启指数退避抖动算法,当一次调用失败后,如果在相同的时间间隔内发起重试,有可能发生连续的调用失败,因此可以开启指数退避抖动算法
        exponentialBackoffMultiplier: 1.1 # 间隔乘数(场景: 正好每次间隔为1的时候卡顿 它就有用了 间隔就变了 例如 1 1.1 1.21....)
        enableRandomizedWait: false #下次重试的时间间隔是否随机
        retryExceptions: #需要重试的异常
          - java.lang.Exception
        ignoreExceptions: #忽略的异常
          - java.lang.IllegalStateException
    instances:
      retryA:
        baseConfig: myConfig
  circuitbreaker: #断路器
    circuit-breaker-aspect-order: 397
    configs: #通用配置
      myConfig: # 断路器策略的命名 断路器系统默认配置
        ringBufferSizeInClosedState: 5 #断路器关闭状态下,环形缓冲区的大小
        ringBufferSizeInHalfOpenState: 3 #断路器处于 HalfOpen 状态下,环形缓冲区的大小
        failureRateThreshold: 50 #失败率,错误率达到或高于该值则进入open状态
        slowCallDurationThreshold: 60s #慢调用阀值,请求执行的时间大于该值时会标记为慢调用
        slowCallRateThreshold: 100 #慢调用熔断阀值,当慢调用率达到或高于该值时,进入open状态
        slidingWindowSize: 100 #状态滚动收集器大小,close状态时收集多少请求状态,用于计算失败率。
        registerHealthIndicator: true #是否开启健康检测
        #状态收集器类型
        #COUNT_BASED:根据数量计算,slidingWindowSize为次数
        #TIME_BASED:根据时间计算,slidingWindowSize为秒数
        slidingWindowType: COUNT_BASED
        minimumNumberOfCalls: 10 #计算错误率的最小请求数,不足最小调用次数不会触发任何变化。
        automaticTransitionFromOpenToHalfOpenEnabled: false #是否自动进入halfOpen状态,默认false-一定时间后进入halfopen,ture-需要通过接口执行。
        permittedNumberOfCallsInHalfOpenState: 10 #进入halfOpen状态时,可以被调用次数,就算这些请求的失败率,低于设置的失败率变为close状态,否则变为open。
        waitDurationInOpenState: 60s #open状态变为half状态需要等待的时间,即熔断多久后开始尝试访问被熔断的服务。
        eventConsumerBufferSize: 10 #事件缓冲区大小
#        recordFailurePredicate: com.kittlen.cloud.config.RecordFailurePredicate #什么样的异常会被认定为请求失败 ??
        recordExceptions: #被计为失败的异常集合,默认情况下所有异常都为失败。
          - java.lang.Exception
        ignoreExceptions: #不会被计为失败的异常集合,优先级高于recordExceptions。
          - java.lang.IllegalStateException
    instances: #实例化熔断器,用于aop式调用
      backendA:
        baseConfig: myConfig
        minimumNumberOfCalls: 3 #计算错误率的最小请求数,不足最小调用次数不会触发任何变化。
        waitDurationInOpenState: 6s #open状态变为half状态需要等待的时间,即熔断多久后开始尝试访问被熔断的服务。

3.配置全局异常

package cloud.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import util.JsonUtil;

import java.nio.charset.StandardCharsets;
import java.util.Map;

import static org.springframework.web.reactive.function.server.RequestPredicates.all;

/**
 * @author kittlen
 * @version 1.0
 * @date 2021/7/7 0007
 */
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
    public JsonExceptionHandler(ErrorAttributes errorAttributes,
                                ResourceProperties resourceProperties,
                                ErrorProperties errorProperties,
                                ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties,
                applicationContext);
    }

    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Throwable error = super.getError(request);
        Map<String, Object> errorAttributes = super.getErrorAttributes(request, includeStackTrace);
        log.error("网关出现异常了,异常为:{} ,异常类型为:{},请求为:{}", error.getMessage(), error.getCause(), JsonUtil.defaultMapper().toJson(errorAttributes));
        return GlobalExceptionHandler.errorMap((Integer) errorAttributes.get("status"));
    }

    /**
     * 指定响应处理方法为JSON处理的方法
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        // 根据客户端 `accpet` 请求头决定返回什么资源
        return RouterFunctions.route(all(), this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的HttpStatus
     *
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return (int) errorAttributes.get("code");
    }

}

package cloud.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;

/**
 * 自定义全局异常处理
 *
 * @author kittlen
 * @version 1.0
 * @date 2021/7/7 0007
 */
@Slf4j
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {
    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfig(ServerProperties serverProperties,
                              ResourceProperties resourceProperties,
                              ObjectProvider<List<ViewResolver>>
                                      viewResolversProvider,
                              ServerCodecConfigurer
                                      serverCodecConfigurer,
                              ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers =
                viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

package cloud.config;


import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
 * 全局的的异常
 *
 */
@Slf4j
public class GlobalExceptionHandler {

	private final static String SERVER_ERROR_TXT = "服务器内部错误";
	private final static String ARGUMENTS_ERROR_TXT = "参数错误";
	private final static String SERVICE_NOT_FOUND_TXT = "找不到服务";
	private final static String BAD_REQUEST_TXT = "错误的请求";


	public static Map<String,Object> errorMap(Integer status){
		Map<String,Object> map=new HashMap<>();
		if(status==null){
			map.put("code",500);
			map.put("message",SERVER_ERROR_TXT);
		}else {
			map.put("code",status);
			switch (status) {
				case 500:
					map.put("message",SERVER_ERROR_TXT);
					break;
				case 404:
					map.put("message",SERVICE_NOT_FOUND_TXT);
					break;
				case 400:
					map.put("message",ARGUMENTS_ERROR_TXT);
					break;
			}
		}
		return map;
	}

}

4.配置Resilience4j作为断路器

package cloud.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * gateway使用resilience4j作为断路器时进行的配置
 *
 * @author kittlen
 * @version 1.0
 * @date 2021/7/8 0008
 */
@Slf4j
@Configuration
public class Resilience4jConfiguration {

    /**
     * @param circuitBreakerRegistry
     * @return
     */
    @Bean
    public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer(CircuitBreakerRegistry circuitBreakerRegistry) {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .circuitBreakerConfig(circuitBreakerRegistry.getConfiguration("myConfig").get())//通过名字来获取
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build()).build());//设置超时时长,默认单位为秒(s)
    }
}

package cloud.controller;

import entities.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/** 降级熔断
 * @author kittlen
 * @version 1.0
 * @date 2021/7/7 0007
 */
@RestController
@RequestMapping("/circuitBreaker")
public class FallbackController {

    public static int count=1;

    @RequestMapping("/fallback")
    public Result fallback() {
        return Result.error("服务暂时不可用");
    }
}

5.各种gateway过滤器

1.输出body
package com.kittlen.cloud.filter;

import com.kittlen.cloud.dto.ReleaseMatcherByAntPathMatcher;
import entities.Result;
import com.kittlen.cloud.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import util.JsonUtil;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author kittlen
 * @version 1.0
 * @date 2021/7/6 0006
 */
@Slf4j
@Component
public class MainFilter implements GlobalFilter, Ordered {

    public static final String TOKEN_HEADER_TAG = "access_token";
    public static final String TOKEN_PARAM = "token";

    @Autowired
    TokenService tokenService;

    @Autowired
    ReleaseMatcherByAntPathMatcher releaseMatcher;

    /**
     * 过滤器核心方法
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String method = request.getMethodValue();//请求方式
        if (HttpMethod.POST.matches(method)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        String bodyString = new String(bytes, StandardCharsets.UTF_8);
                        logtrace(exchange, bodyString);
                        exchange.getAttributes().put("POST_BODY", bodyString);
                        DataBufferUtils.release(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory()
                                    .wrap(bytes);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest)
                                .exchange());
                    });
        } else if (HttpMethod.GET.matches(method)) {
            Map m = request.getQueryParams();
            logtrace(exchange, m.toString());
        }
        return chain.filter(exchange);
    }

    /**
     * 优先级 越小优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

    public Mono<Void> out(ServerHttpResponse response,String msg) {
        Result result = Result.error("鉴权失败");
        byte[] bits = JsonUtil.toJson(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 日志信息
     *
     * @param exchange
     * @param param    请求参数
     */
    private void logtrace(ServerWebExchange exchange, String param) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String path = serverHttpRequest.getURI().getPath();
        String method = serverHttpRequest.getMethodValue();
        String headers = serverHttpRequest.getHeaders().entrySet()
                .stream()
                .map(entry -> "            " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                .collect(Collectors.joining("\n"));
        log.info("\n" + "----------------             ----------------             ---------------->>\n" +
                        "HttpMethod : {}\n" +
                        "Uri        : {}\n" +
                        "Param      : {}\n" +
                        "Headers    : \n" +
                        "{}\n" +
                        "\"<<----------------             ----------------             ----------------"
                , method, path, param, headers);

    }

    public String getToken(ServerHttpRequest request) {
        List<String> list = request.getHeaders().get(TOKEN_HEADER_TAG);
        if (list == null || list.isEmpty()) {
            list = request.getHeaders().get(TOKEN_PARAM);
        }
        if (list == null || list.isEmpty()) {
            return "";
        }
        return list.get(0);
    }

}

2.Headers中添加属性
package com.kittlen.cloud.filter;

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

/**
 * @author kittlen
 * @version 1.0
 * @date 2021/9/17 0017
 */
public class HeadersFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest host = request.mutate().headers(headers -> {
            headers.add("auth", "kittlen");//headers中添加属性
            headers.remove("name");//headers中删除属性
        }).build();
        ServerWebExchange build = exchange.mutate().request(host).build();
        return chain.filter(build);
    }

    @Override
    public int getOrder() {
        return 4;
    }
}

3.白名单
package com.kittlen.cloud.filter;

import com.kittlen.cloud.dto.ReleaseMatcherByAntPathMatcher;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author kittlen
 * @version 1.0
 * @date 2021/9/17 0017
 */
@Slf4j
public class WhiteListFilter implements GlobalFilter, Ordered {

    @Autowired
    ReleaseMatcherByAntPathMatcher releaseMatcher;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //白名单
        if (releaseMatcher.release(request.getURI().getPath())) {
            log.info("地址在白名单");
            return chain.filter(exchange);
        }
        log.info("不在白名单,进行其他判断");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

package com.kittlen.cloud.config;

import com.kittlen.cloud.dto.ReleaseMatcherByAntPathMatcher;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * @author kittlen
 * @version 1.0
 * @date 2021/7/14 0014
 */
@Configuration
@ConfigurationProperties(prefix = "gateway.matcher")
public class MatcherConfig {
    
    private List<String> whitelist;

    public void setWhitelist(List<String> whitelist) {
        this.whitelist = whitelist;
    }

    @Bean
    public ReleaseMatcherByAntPathMatcher releaseMatcherByAntPathMatcher() {
        return new ReleaseMatcherByAntPathMatcher(whitelist);
    }
}

package com.kittlen.cloud.dto;

import org.springframework.util.AntPathMatcher;
import util.StringUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/** 利用AntPathMatcher利进行判断
 * @author kittlen
 * @version 1.0
 * @date 2021/7/14 0014
 */
public class ReleaseMatcherByAntPathMatcher {

    private static AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 必须是在yml文件gateway的routes中配置了的地址进行访问时才会进行地址拦截过滤和白名单
     * **代表多级地址(任意地址的任意子地址)
     * *代表单级地址或任意字符(任意地址)
     */
    //如:/consul-feign/**/describe/* 请求的地址为consul-feign开头倒数第二级地址为describe,结尾为任意
    private List<String> urls;

    public List<String> getUrls() {
        return urls;
    }

    public void setUrls(List<String> urls) {
        this.urls = urls;
    }


    public ReleaseMatcherByAntPathMatcher() {
    }

    public ReleaseMatcherByAntPathMatcher(List<String> urls) {
        if (urls == null) {
            this.urls = new ArrayList<>();
        } else {
            this.urls = urls;
        }
    }

    public ReleaseMatcherByAntPathMatcher(List<String> urls, List<Pattern> Patterns) {
        this.urls = urls;
    }

    public boolean release(String path) {
        if (urls == null || urls.isEmpty()|| StringUtil.isEmpty(path)) {
            return false;
        }
        if (!"/".equals(path.substring(0, 1))) {
            path = "/" + path;
        }
        for (int i = 0; i < urls.size(); i++) {
            if(antPathMatcher.match(urls.get(i), path)){
                return true;
            }
        }
        return false;
    }


}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值