当服务返回错误时希望通过网关修改为统一的错误格式;对返回数据统一加解密等。
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.xxx.commons.model.response.ResponseEnum;
import com.xxx.commons.model.response.ServerResponseEntity;
import com.xxx.commons.utils.LogUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
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.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.annotation.NonNull;
import java.nio.charset.StandardCharsets;
@Component
@Slf4j
public class GatewayGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.currentTimeMillis();
ServerHttpRequest request = exchange.getRequest();
log.info("请求方法:{},请求路径:{}", request.getMethodValue(), request.getPath().value());
String mdcIds = request.getHeaders().getFirst(LogUtil.TRACE_ID);
if (StringUtils.isBlank(mdcIds)) {
LogUtil.setTraceId();
} else {
LogUtil.setTraceId(mdcIds);
}
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@NonNull
@Override
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
if (!(body instanceof Flux)) {
return super.writeWith(body);
}
@SuppressWarnings("unchecked")
Flux<? extends DataBuffer> fluxDataBuffer = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxDataBuffer.buffer()
.map(dataBuffer -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
DataBuffer responseBody = response.bufferFactory().join(dataBuffer);
if (statusCode == null || statusCode.isError()) {
log.error("内部服务异常响应:{}", StandardCharsets.UTF_8.decode(responseBody.asByteBuffer()));
ResponseEnum responseEnum = (statusCode != null && statusCode.is4xxClientError())
? ResponseEnum.NOT_FOUND_PATH : ResponseEnum.ERROR;
responseBody = response.bufferFactory().wrap(JSON.toJSONBytes(ServerResponseEntity.fail(responseEnum), JSONWriter.Feature.WriteMapNullValue));
response.setStatusCode(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
}
log.info("响应:{}", StandardCharsets.UTF_8.decode(responseBody.asByteBuffer()));
return responseBody;
}
)
);
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build())
.then(Mono.just(exchange).map(ex -> {
// 其他操作
return ex;
}))
.then(Mono.fromRunnable(() -> {
log.info("请求耗时:{}", System.currentTimeMillis() - startTime);
}));
}
@Override
public int getOrder() {
return -1;
}
}
Spring Cloud Getaway 3中不能使用@Order定义过滤器顺序必须实现Ordered接口并且getOrder()必须返回小于0的值否则不生效
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
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.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.annotation.NonNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 响应 Body 加密 过滤器
*
* @author xuxiaowei
* @see ServerHttpResponseDecorator
* @since 0.0.1
*/
@Slf4j
@Component
public class BodyEncryptionGlobalFilter implements GlobalFilter, Ordered {
/**
* 加密 过滤器 优先级
*
* 响应数据过滤器优先级需要小于 0,否则将无效
*/
@Override
public int getOrder() {
return -1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
ServerHttpResponseDecorator decorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@NonNull
@Override
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
@SuppressWarnings("unchecked")
Flux<? extends DataBuffer> fluxDataBuffer = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxDataBuffer.buffer()
.map(dataBuffer -> {
DataBuffer join = exchange.getResponse().bufferFactory().join(dataBuffer);
byte[] bytes = new byte[join.readableByteCount()];
join.read(bytes);
DataBufferUtils.release(join);
log.debug("加密前 body:{}", new String(bytes));
byte[] encryption;
try {
@SuppressWarnings("unchecked")
Map<String, Object> map = objectMapper.readValue(bytes, Map.class);
map.put("test", "数据已加密(仅演示,加密方式,自己实现)");
encryption = objectMapper.writeValueAsBytes(map);
} catch (IOException e) {
encryption = bytes;
log.error("数据类型不是 JSON,不加密", e);
}
log.debug("加密后 body:{}", new String(encryption, StandardCharsets.UTF_8));
return exchange.getResponse().bufferFactory().wrap(encryption);
})
);
}
};
return chain.filter(exchange.mutate().response(decorator).build());
}
}
参考代码
@Component
public class LogRespFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
//修改header
HttpHeaders httpHeaders = originalResponse.getHeaders();
httpHeaders.add("xxxxxx","aaaaaa");
//输出返回结果
if (body instanceof Flux) {
Mono<Void> newMono = super.writeWith(
DataBufferUtils.join(body)
.doOnNext(dataBuffer -> {
String respBody = dataBuffer.toString(StandardCharsets.UTF_8);
//输出body
logger.info("fgwResponse : body = {}", respBody);
})
);
//输出response,不包含body
logger.info("fgwResponse : resp = {}", JSON.toJSONString(exchange.getResponse()));
return newMono;
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
参考文章