SpringCloud Gateway 修改响应体(统一前端响应格式)
在全网找了好几天,又去springcloud的issue中翻阅,也没有真正说该怎么写的,都是各种方式操作流,要么就是说参考ModifyRequestBodyGatewayFilterFactory。。版本:springboot 2.0.6springcloudFinchley.SR2hutool4.4.2lombok主要参考:ModifyRequestBod...
在全网找了好几天,又去springcloud的issue中翻阅,也没有真正说该怎么写的,都是各种方式操作流,要么就是说参考ModifyRequestBodyGatewayFilterFactory。。
======================
2020年4月13日22:17:49
虽然可以统一响应格式,但是我仔细思考后想到,已经有了HTTP状态码,前端或者客户端本身已经可以根据状态码进行判断处理,而一些校验性判断可使用 hibernate-validator,其他业务性 例如异常可自定义一个业务异常,抛出业务异常并使用HTTP状态码,像一些例如:空指针、角标越界完全可以在代码中避免 应在开发阶段避免,不会再测试或生产中出现,现在出现一个问题是,例如使用 RestTemplate 调用第三方服务时,第三方会根据不同的厂家返回不同的码值,而不管是什么 如果抛出异常就是RestClientException,返回前端时就会是500 嵌套异常,此时应将最内层嵌套分解出来,并返回前端。
仅个人想法
======================
版本:
springboot 2.0.6
springcloud Finchley.SR2
hutool 4.4.2
lombok
主要参考:ModifyRequestBodyGatewayFilterFactory 这个类
package com.gitee.cashzhang27.ftf.common.core.entity;
import com.gitee.cashzhang27.ftf.common.core.constants.enums.ErrorCode;
import java.io.Serializable;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Cash Zhang
* @version v1.0
* @since 2019/01/28 21:47
*/
@Data
@Accessors(chain = true)
public class Response<T> implements Serializable {
private static final long serialVersionUID = 5005318709330004756L;
/**
* 状态码
*/
private Integer code;
/**
* 描述
*/
private String msg;
/**
* 结果集
*/
private T data;
public static <T> Response<T> ok() {
return restResult(ErrorCode.SUCCESS, null);
}
public static <T> Response<T> ok(T data) {
return restResult(ErrorCode.SUCCESS, data);
}
public static <T> Response<T> ok(ICode iCode, T data) {
return restResult(iCode, data);
}
public static <T> Response<T> failed(ICode iCode) {
return restResult(iCode, null);
}
public static <T> Response<T> failed(int code, String msg) {
return restResult(code, msg, null);
}
private static <T> Response<T> restResult(ICode iCode, T data) {
return restResult(iCode.getCode(), iCode.getMsg(), data);
}
private static <T> Response<T> restResult(Integer code, String msg, T data) {
Response<T> apiResult = new Response<>();
apiResult.setCode(code);
apiResult.setMsg(msg);
apiResult.setData(data);
return apiResult;
}
}
package com.gitee.cashzhang27.ftf.common.core.entity;
/**
* 用于返回状态码及描述
*
* @author Cash Zhang
* @version v1.0
* @since 2019/01/28 21:48
*/
public interface ICode {
/**
* 获取状态码
*
* @return 状态码
*/
Integer getCode();
/**
* 获取描述
*
* @return 描述
*/
String getMsg();
}
package com.gitee.cashzhang27.ftf.gateway.filter;
import static com.gitee.cashzhang27.ftf.gateway.configuration.SwaggerProvider.API_URI;
import static org.springframework.cloud.gateway.filter.NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
import cn.hutool.json.JSONUtil;
import com.gitee.cashzhang27.ftf.common.core.entity.Response;
import org.apache.commons.lang.StringEscapeUtils;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultClientResponse;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Cash Zhang
* @version v1.0
* @since 2019/03/16 19:20
*/
@Component
public class ResponseGlobalFilter implements GlobalFilter, Ordered {
private static final String SUCCESS_PREFIX = "{\"code\":20000,\"msg\":\"success\",\"data\":";
private static final String SUCCESS_SUFFIX = "}";
@Override
public int getOrder() {
// -1 is response write filter, must be called before that
return WRITE_RESPONSE_FILTER_ORDER - 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(
exchange.getResponse()) {
@Override
@SuppressWarnings("unchecked")
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
String originalResponseContentType = exchange
.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
//explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)'
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter,
ExchangeStrategies
.withDefaults());
//TODO: flux or mono
Mono modifiedBody = clientResponse.bodyToMono(String.class).flatMap(originalBody -> {
if (originalResponse.getStatusCode() != null) {
if (originalResponse.getStatusCode().is2xxSuccessful()) {
// Swagger 请求不做包装
// 自定义 Response 实体不做包装
if (API_URI.equals(request.getURI().getPath()) || originalBody
.startsWith(StringEscapeUtils.unescapeJavaScript(SUCCESS_PREFIX))) {
return Mono.just(originalBody);
}
if (!JSONUtil.isJson(originalBody)) {
originalBody = JSONUtil.toJsonStr(Response.ok(originalBody));
} else {
originalBody = StringEscapeUtils.unescapeJavaScript(SUCCESS_PREFIX) + originalBody
+ SUCCESS_SUFFIX;
}
} else {
originalBody = JSONUtil.toJsonStr(originalBody);
}
}
return Mono.just(originalBody);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,
exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
long contentLength1 = getDelegate().getHeaders().getContentLength();
Flux<DataBuffer> messageBody = outputMessage.getBody();
//TODO: if (inputStream instanceof Mono) {
HttpHeaders headers = getDelegate().getHeaders();
if (/*headers.getContentLength() < 0 &&*/ !headers
.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody
.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
}
// }
//TODO: use isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
@Override
public Mono<Void> writeAndFlushWith(@NonNull
Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body)
.flatMapSequential(p -> p));
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
public class ResponseAdapter implements ClientHttpResponse {
private final Flux<DataBuffer> flux;
private final HttpHeaders headers;
public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
this.headers = headers;
if (body instanceof Flux) {
flux = (Flux<DataBuffer>) body;
} else {
flux = ((Mono) body).flux();
}
}
@Override
public @NonNull
Flux<DataBuffer> getBody() {
return flux;
}
@Override
public @NonNull
HttpHeaders getHeaders() {
return headers;
}
@Override
public @NonNull
HttpStatus getStatusCode() {
return null;
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public @NonNull
MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
}
}
package com.gitee.cashzhang27.ftf.common.security.exception;
import com.gitee.cashzhang27.ftf.common.core.constants.enums.ErrorCode;
import com.gitee.cashzhang27.ftf.common.core.entity.Response;
import com.gitee.cashzhang27.ftf.common.core.exception.StrategyException;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*
* @author Cash Zhang
* @version v1.0
* @since 2019/01/31 20:29
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
*
* @param binder 绑定器
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
}
/**
* GlobalException.
*
* @param e Exception
* @return Response
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Response handleGlobalException(Exception e) {
log.error("全局异常 ex={}", e.getMessage(), e);
return Response.failed(ErrorCode.GLOBAL_EXCEPTION.getCode(), e.getMessage());
}
/**
* MethodArgumentNotValidException
*
* @param e MethodArgumentNotValidException
* @return Response
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Response handleBodyValidException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
log.error("参数校验异常,ex = {}", fieldErrors.get(0).getDefaultMessage());
return Response.failed(ErrorCode.VALIDATION_EXCEPTION.getCode(),
fieldErrors.get(0).getDefaultMessage());
}
/**
* BadCredentialsException.
*
* @param e IllegalArgumentException
* @return Response
*/
@ExceptionHandler(BadCredentialsException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Response handleArgumentException(BadCredentialsException e) {
log.error("凭证错误异常 ex={}", e.getMessage(), e);
return Response.failed(ErrorCode.BAD_CREDENTIALS.getCode(), e.getMessage());
}
}
更多推荐
所有评论(0)