使用Gateway的时候,时长会有一些需求,要放在Gateway中去处理,如果一些登陆校验,权限校验之类的,从而降低一些应用服务器的压力。这个时候就需要在Gateway项目中自定义Filte去处理。
步骤
创建自定义filter的步骤
-
创建XxxGatewayFilterFactory,继承AbstractGatewayFilterFactory,如CheckLoginGatewayFilterFactory,必须以GatewayFilterFactory为结尾
-
重写apply方法
-
通过@Bean的方式将自定义的Filter添加到spring容器中,在类上加@Service的方式无法添加
-
在配置中引入自定义Filter,名称为XxxGatewayFilterFactory中的Xxx,如CheckLogin,
-
如果需要增加一些配置,用于Filter的逻辑判断,如官方的PrefixPathGatewayFilte中可以配置prefilx的值,那么可以在当前类创建一个静态内部类,然后声明在类上,如
public class RejectUrlGatewayFilterFactory extends AbstractBaseCheckGatewayFilterFactory<RejectUrlGatewayFilterFactory.Config>
- 写入到配置文件中,由于我们不引入Order,所以默认我们写的自定义Filter的Order都是0,引入多个filter时,执行顺序按配置的顺序执行
示例
普通的自定义filter
比如校验请求的header中是否包含必须的自定义的参数
创建CheckHeaderParamGatewayFilterFactory,然后创建一个Config,用来配置需要被校验的header,
然后继承AbstractGatewayFilterFactory,重写applay方法,apply方法中的Config参数就是我们自己定义的Config
public class CheckHeaderParamGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckHeaderParamGatewayFilterFactory.Config> {
@Override
public GatewayFilter apply(Config config) {
return null;
}
@Data
public static class Config {
/**
* 要校验的header的key,用逗号分隔
*/
private String header;
}
}
编写处理逻辑
@Slf4j
public class CheckHeaderParamGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckHeaderParamGatewayFilterFactory.Config> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String[] checkHeaderArr = getCheckHeaderArr(config);
// 没有配置,进入下一个filter
if (checkHeaderArr == null){
log.info("check header config is null");
return chain.filter(exchange);
}
log.info("requestUri:【{}】 校验 header", exchange.getRequest().getURI().toString());
// 获取请求的所有header
HttpHeaders headers = exchange.getRequest().getHeaders();
for (String headerName : checkHeaderArr) {
if (StringUtils.isBlank(headerName)){
continue;
}
// 如果要被校验的header不存在,直接编写响应信息给接口调用方
if (!headers.containsKey(headerName)) {
return writeParamErrorResponse(exchange, headerName + "不能为空");
}
}
log.info("requestUri:【{}】 校验 header 通过", exchange.getRequest().getURI().toString());
// 校验通过,进入下一个filter
return chain.filter(exchange);
};
}
/**
* 处理失败时的返回
*/
private Mono<Void> writeParamErrorResponse(ServerWebExchange exchange, String message) {
ApiResponse<Void> response = new ApiResponse<>();
response.setResultCode("PA_PARAM_ERROR");
response.setMessage(message);
return writeErrorResponse(exchange, response);
}
/**
* 处理失败时的返回
*/
private Mono<Void> writeErrorResponse(ServerWebExchange exchange, Object responseBody) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.OK);
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
log.info("校验失败:{}", responseBody);
return bufferFactory.wrap(objectMapper.writeValueAsBytes(responseBody));
} catch (JsonProcessingException e) {
log.error("json转换异常", e);
return bufferFactory.wrap(new byte[0]);
}
}));
}
/**
* 从Config中获取配置
*/
private String[] getCheckHeaderArr(Config config) {
String[] checkHeaderArr;
String header = config.getHeader();
if (StringUtils.isBlank(header)) {
checkHeaderArr = null;
} else {
checkHeaderArr = header.split(",");
}
return checkHeaderArr;
}
@Data
public static class Config {
/**
* 要校验的header的key,用逗号分隔
*/
private String header;
}
}
将filter注册到Spring容器中
@Configuration
public class GatewayFilterConfig {
@Bean
public CheckHeaderParamGatewayFilterFactory checkHeaderParamGatewayFilterFactory(){
return new CheckHeaderParamGatewayFilterFactory();
}
}
在配置文件中配置的模式
yaml
filters:
- CheckHeaderParam=token,visitorId
本人项目中采用在nacos中通过json的模式进行动态路由的配置,json中的对应格式为
"filters": [
{
"name": "CheckHeaderParam",
"args":{
"header":"token,visitorId"
}
}
]
继承了Spring中某些特殊filter
比如某些场景下,我们需要对某个路由中的响应做数据类型转换,这个时候,可以直接继承Spring的ModifyResponseBodyGatewayFilterFactory
@Slf4j
public class ConvertInsurBankResponseGatewayFilterFactory extends ModifyResponseBodyGatewayFilterFactory {
public ConvertInsurBankResponseGatewayFilterFactory(List<HttpMessageReader<?>> messageReaders, Set<MessageBodyDecoder> messageBodyDecoders, Set<MessageBodyEncoder> messageBodyEncoders) {
super(messageReaders, messageBodyDecoders, messageBodyEncoders);
}
@Autowired
private InsurBankCodeMappingConfig insurBankCodeMappingConfig;
@Override
public GatewayFilter apply(Config config) {
// 创建一个新的Config
Config conf = new Config();
// 处理类型转换
conf.setRewriteFunction(InsurBankResponse.class, ApiResponse.class,(serverWebExchange, response) -> {
Optional<InsurBankResponse> resp = Optional.ofNullable(response);
ApiResponse<Object> apiResponse = new ApiResponse<>();
apiResponse.setBody(resp.map(InsurBankResponse::getData).orElse(null));
apiResponse.setResultCode(resp.map(InsurBankResponse::getCode).map(e->insurBankCodeMappingConfig.getCodeMapping(e)).orElse(StringUtils.EMPTY));
apiResponse.setMessage(resp.map(InsurBankResponse::getMsg).orElse(StringUtils.EMPTY));
log.info("InsurBankResponse:【{}】, ApiResponse:【{}】",response,apiResponse);
return Mono.just(apiResponse);
});
// 将新创建的config传入
return super.apply(conf);
}
}
添加到Spring容器
@Bean
public ConvertInsurBankResponseGatewayFilterFactory convertInsurBankResponseGatewayFilterFactory(ServerCodecConfigurer codecConfigurer, Set<MessageBodyDecoder> bodyDecoders,
Set<MessageBodyEncoder> bodyEncoders) {
return new ConvertInsurBankResponseGatewayFilterFactory(codecConfigurer.getReaders(), bodyDecoders, bodyEncoders);
}
在配置文件中配置的模式
yaml
filters:
- ConvertInsurBankResponse
json
"filters": [
{
"name": "ConvertInsurBankResponse",
}
]