一、全局Token过滤器
在Spring Cloud Gateway中,实现全局过滤器的目的是对所有进入系统的请求或响应进行统一处理,比如添加日志、鉴权等。下面是如何创建一个全局过滤器的基本步骤:
步骤1: 创建过滤器类
首先,你需要创建一个实现了
GatewayFilter
接口或者继承AbstractGatewayFilterFactory
类的过滤器类。这里以实现GatewayFilter
接口为例,创建一个全局token过滤器。
java
/*
* Copyright (c) 2020, 2024, All rights reserved.
*
*/
package com.by.filter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSignerUtil;
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.http.HttpStatus;
import org.springframework.http.server.RequestPath;
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.util.List;
/**
* <p>Project: spring-could-alibaba-root - TokenFilter</p>
* <p>Powered by scl On 2024-04-26 17:41:24</p>
* <p>描述:<p>
*
* @author 孙臣龙 [1846080280@qq.com]
* @version 1.0
* @since 17
*/
@Component
@Slf4j
public class TokenFilter implements GlobalFilter {
@Value("${cn.smart.tokenx.key}")
private String key;
//设置白名单
private static List<String> whiteList = CollUtil.newArrayList("/api/user/login", "/api/user/register");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//判断如果包含白名单中的内容就放行
RequestPath path = request.getPath();
if (whiteList.contains(path)) {
return chain.filter(exchange);
}
//从请求头中获取token
List<String> tokens = request.getHeaders().get("token");
if (ObjUtil.isEmpty(tokens)) {
//设置状态码
log.debug("token为空");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//获取token
String token = tokens.get(0);
if (ObjUtil.isEmpty(token)) {
log.debug("token为空");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//全面增强
Boolean b = false;
try {
JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256(key.getBytes())).validateDate();
b = true;
} catch (Exception e) {
e.printStackTrace();
}
if (!b) {
log.debug("token无效");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
}
}
步骤2: 注册全局过滤器
为了让Spring Cloud Gateway应用识别并使用这个过滤器,你通常需要在配置类中注册它。但是,由于我们使用了@Component
注解,Spring会自动扫描并注册该Bean。
使用@Component
或下面代码,二选一。
java
@Configuration
public class FilterConfig {
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
}
二、局部过滤器接口耗时
1.找规律
局部过滤器命名规则 XXXGatewayFilterFactory, 必须以GatewayFilterFactory结尾。
XML
/* 注意名称约定
* AddRequestHeaderGatewayFilterFactory 配置的时候写的是 AddRequestHeader
* AddRequestParameterGatewayFilterFactory 配置的时候写的是 AddRequestParameter
* LogTimeGatewayFilterFactory 配置的时候写什么? LogTime
* */
spring.cloud.gateway.routes[0].filters[0] = LogTime=moreThen,500
2.接口耗时过滤器
java
/*
* Copyright (c) 2020, 2024, All rights reserved.
*
*/
package com.by.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* <p>Project: spring-could-alibaba-root - LogTimeGatewayFilterFactory</p>
* <p>Powered by scl On 2024-04-26 20:01:04</p>
* <p>描述:<p>
*
* @author 孙臣龙 [1846080280@qq.com]
* @version 1.0
* @since 17
*/
@Component
@Slf4j
//局部过滤器命名规则 XXXGatewayFilterFactory, 必须以GatewayFilterFactory结尾。还需要配置文件中进行配置
public class LogTimeGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public LogTimeGatewayFilterFactory() {
}
public GatewayFilter apply(final AbstractNameValueGatewayFilterFactory.NameValueConfig config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//获取配置文件中设置的参数
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
//将参数转换为int类型
Integer time = Integer.valueOf(value);
//设置开始时间
long startTime = System.currentTimeMillis();
//执行过滤器,并且记录请求耗时,通过调用then方法(类似于vue的then方法),记录请求耗时
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
if (costTime > time*1000) {
log.debug("请求地址:" + request.getPath() + ",请求耗时:" + costTime + "ms");
}
}));
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(LogTimeGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();
}
};
}
}
3.如何使用
三、全链路跟踪TraceId日志
1.创建全局过滤器,在请求头上带入traceId参数,穿透到下游服务.
java
/*
* Copyright (c) 2020, 2024, All rights reserved.
*
*/
package com.by.filter;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* <p>Project: spring-could-alibaba-root - TraceIdFilter</p>
* <p>Powered by scl On 2024-04-26 21:10:19</p>
* <p>描述:<p>
*
* @author 孙臣龙 [1846080280@qq.com]
* @version 1.0
* @since 17
*/
@Component
public class TraceIdFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求头
ServerHttpRequest request = exchange.getRequest();
//判断请求头中有没有traceId
List<String> list = request.getHeaders().get("traceId");
//如果存在就不做处理
if (ObjectUtil.isNotEmpty(list)){
return chain.filter(exchange);
}
//不存在就生成一个traceId
String treaId= IdUtil.simpleUUID();
//在请求头中添加一个traceId
ServerHttpRequest request1 = request.mutate().header("tracedId", treaId).build();
//因为请求头发生了变化,所以需要重新构建一个ServerWebExchange
ServerWebExchange exchange1 = exchange.mutate().request(request1).build();
return chain.filter(exchange1);
}
}
2.MDC原理
当请求来时生成一个traceId放在ThreadLocal里,然后打印时去取就行了。但在不改动原有输出语句的前提下自然需要日志框架的支持了。
MDC 介绍 MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
简而言之,MDC就是日志框架提供的一个InheritableThreadLocal,项目代码中可以将键值对放入其中,然后使用指定方式取出打印即可。
3.下游服务如何使用全链路跟踪Id
3.1在你要跟踪的模块中配置TraceId 过滤器
java
/*
* Copyright (c) 2020, 2024, All rights reserved.
*
*/
package com.by.filter;
import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* <p>Project: spring-could-alibaba-root - TraceIdFliter</p>
* <p>Powered by scl On 2024-04-26 21:20:15</p>
* <p>描述:<p>
*
* @author 孙臣龙 [1846080280@qq.com]
* @version 1.0
* @since 17
*/
@WebFilter
public class TraceIdFliter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String traceId = httpRequest.getHeader("traceId");
if (StringUtils.isEmpty(traceId)) {
traceId = IdUtil.fastSimpleUUID();
}
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
3.2 启动类开启ServletComponentScan扫描。
java
@SpringBootApplication
@ServletComponentScan
public class OpenApp {
public static void main(String[] args) {
SpringApplication.run(OpenApp.class, args);
}
}
3.3 配置文件配置日志输出格式
##日志输出格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%-5level) %clr([%X{traceId}]) %clr(${PID:-}) --- %clr(%logger{50}) - %m%n
4.Openfeign扩展
java
@Component
public class OpenFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String traceId = MDC.get(TraceIdFilter.MDC_TRACE_ID);
requestTemplate.header(TraceIdFilter.MDC_TRACE_ID, traceId);
}
}