开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第28天,点击查看活动详情
前言
我们在项目开发中,返回给前端的数据结构往往都会前后端提前确定好一个统一的结构,统一的数据结构有助于前后端交互,前端页面的展现也会更好,后端代码一旦发生异常也更容易定位到问题的出现点;下面我们就简单整理一下,看看如何做到统一的响应数据结构;
实现ResponseBodyAdvice
我们可以通过实现ResponseBodyAdvice来拦截所有响应结果,并对响应结果进行统一的包装:
@RestControllerAdvice
public class AwesomeResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 是否需要拦截
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, ClassselectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 针对拦截的响应结果进行处理
}
另外我们需要考虑一下,并不是所有的响应都是需要统一的数据结构的,比如支付宝回调的响应数据就是success,所以我们要设计一种可以灵活控制的方式来处理,这里我们想到了注解;我们可以设计一个注解@IgnoreResponseAdvice来决定是否包装响应数据,如果在Controller类或方法上加了@IgnoreResponseAdvice,那么就可以不包装响应数据,否则就必须给拦截到;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
boolean value() default true;
}
有了这个注解后,我们的supports方法就可以完善了:
public boolean supports(MethodParameter returnType, Class converterType) {
// 字符串直接返回
if (StringHttpMessageConverter.class.isAssignableFrom(converterType)) {
return false;
}
boolean ignore = false;
// 方法上有@IgnoreResponseAdvice注解就拦截
IgnoreResponseAdvice ignoreResponseAdvice =
returnType.getMethodAnnotation(IgnoreResponseAdvice.class);
if (Objects.nonNull(ignoreResponseAdvice)) {
ignore = ignoreResponseAdvice.value();
return !ignore;
}
// 判断类上面是否有@IgnoreResponseAdvice注解
Class<?> clazz = returnType.getDeclaringClass();
ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class);
RestController restController = clazz.getDeclaredAnnotation(RestController.class);
if (Objects.nonNull(ignoreResponseAdvice)) {
ignore = ignoreResponseAdvice.value();
} else if (Objects.isNull(restController)) {
ignore = true;
}
return !ignore;
}
确定数据结构
我们需要给自己的项目确定一个统一的数据结构:
@Data
@AllArgsConstructor
public class ResultWrap<T, M> {
//方便前端判断当前请求处理结果是否正常
private int code;
// 业务处理结果
private T data;
// 产生错误的情况下,提示用户信息
private String message;
// 产生错误情况下的异常堆栈,提示开发人员
private String error;
//发生错误的时候,返回的附加信息
private M metaInfo;
}
数据结构确定好后,我们需要多个好的工具方法来实现各种数据结构转换成ResultWrap的功能:
/**
* 成功带处理结果
*
* @param data
* @param <T>
* @return
*/
public static <T> ResultWrap success(T data) {
return new ResultWrap(HttpStatus.OK.value(), data, StringUtils.EMPTY, StringUtils.EMPTY, null);
}
/**
* 成功不带处理结果
*
* @return
*/
public static ResultWrap success() {
return success(HttpStatus.OK.name());
}
/**
* 失败
*
* @param code
* @param message
* @param error
* @return
*/
public static <M> ResultWrap failure(int code, String message, String error, M metaInfo) {
return new ResultWrap(code, null, message, error, metaInfo);
}
/**
* 失败
*
* @param code
* @param message
* @param error
* @param metaInfo
* @param <M>
* @return
*/
public static <M> ResultWrap failure(int code, String message, Throwable error, M metaInfo) {
String errorMessage = StringUtils.EMPTY;
if (Objects.nonNull(error)) {
errorMessage = toStackTrace(error);
}
return failure(code, message, errorMessage, metaInfo);
}
/**
* 失败
*
* @param code
* @param message
* @param error
* @return
*/
public static ResultWrap failure(int code, String message, Throwable error) {
return failure(code, message, error, null);
}
/**
* 失败
*
* @param code
* @param message
* @param metaInfo
* @param <M>
* @return
*/
public static <M> ResultWrap failure(int code, String message, M metaInfo) {
return failure(code, message, StringUtils.EMPTY, metaInfo);
}
/**
* 失败
*
* @param e
* @return
*/
public static ResultWrap failure(AwesomeException e) {
return failure(e.getCode(), e.getMsg(), e.getCause());
}
/**
* 失败
*
* @param e
* @return
*/
public static ResultWrap failure(RuntimeException e) {
return failure(500, "服务异常,请稍后访问!", e.getCause());
}
private static final String APPLICATION_JSON_VALUE = "application/json;charset=UTF-8";
/**
* 把结果写入响应中
*
* @param response
*/
public void writeToResponse(HttpServletResponse response) {
int code = this.getCode();
if (Objects.isNull(HttpStatus.resolve(code))) {
response.setStatus(HttpStatus.OK.value());
} else {
response.setStatus(code);
}
response.setContentType(APPLICATION_JSON_VALUE);
try (PrintWriter writer = response.getWriter()) {
writer.write(JsonUtil.obj2String(this));
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取异常堆栈信息
*
* @param e
* @return
*/
private static String toStackTrace(Throwable e) {
if (Objects.isNull(e)) {
return StringUtils.EMPTY;
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
e.printStackTrace(pw);
return sw.toString();
} catch (Exception e1) {
return StringUtils.EMPTY;
}
}
上面实现了各种成功返回值和失败返回值的封装方法,那么我们就可以开始完善响应数据拦截的逻辑了:
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, ClassselectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (Objects.isNull(body)) {
return ResultWrap.success();
}
if (body instanceof ResultWrap) {
return body;
}
return ResultWrap.success(body);
}
1.如果返回的值为
null,那么直接返回ResultWrap.success();2.如果返回值类型是
ResultWrap类型,那么直接返回就不用包装了;3.其他类型的数据,那么直接通过
ResultWrap.success()包装;