普通的接口仅仅将业务数据裸着返回给客户端是远远不够的,需要给业务数据穿一层衣服,封装起来再返回给客户端,在封装时还要附上一些附加信息一起返回。通过Spring AOP机制,统一封装Restful API的响应数据。
@Data
@AllArgsConstructor
@JSONType(orders = {"code", "message", "data"})
public class Result {
private int code;
private String message;
private Object data;
public static Result error(int code, String message) {
return new Result(code, message, null);
}
public static Result error(ResultStatus info) {
return new Result(info.getCode(), info.getMessage(), null);
}
public static Result success(Object obj) {
return new Result(200, "SUCCESS", obj);
}
}
@Getter
public enum ResultStatus {
PERMISSION_DENIED(1001, "Unable to authenticate"),
TOKEN_EXPIRE(1002, "token expire"),
INTERNAL_SERVER_ERROR(1003, "simple error");
// ...
private final int code;
private final String message;
ResultStatus(int code, String message) {
this.code = code;
this.message = message;
}
}
@RestController
@Slf4j
public class BasicErrorController implements ErrorController {
@RequestMapping(value = {"/error"})
public Object error(HttpServletResponse response,Exception ex) {
log.error(ex.getMessage(), ex);
int code = response.getStatus();
String message = HttpStatus.valueOf(response.getStatus()).getReasonPhrase();
return Result.error(code, message);
}
}
@Data
@AllArgsConstructor
public class ResultException extends Exception{
ResultStatus status;
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ResponseBody
public @interface JsonResponseBody {
}
@RestControllerAdvice
@Slf4j
public class JsonResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private static final Class<? extends Annotation> ANNOTATION_TYPE = JsonResponseBody.class;
/**
* 判断类或者方法是否使用了 @JsonResponseBody
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
}
/**
* 当类或者方法使用了 @JsonResponseBody 就会调用这个方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
response.getHeaders().add("Content-Type", "application/json");
return JSON.toJSONString(Result.success(body));
}
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
/**
* 提供对标准Spring MVC异常的处理
*
* @param ex the target exception
* @param request the current request
*/
@ExceptionHandler(Exception.class)
public final ResponseEntity<Result> exceptionHandler(Exception ex, WebRequest request) {
log.error("ExceptionHandler: {}", ex.getMessage());
if (ex instanceof ResultException) {
return handleResultException((ResultException) ex);
}
// TODO: 2019/10/05 galaxy 这里可以自定义其他的异常拦截
return handleException(ex, new HttpHeaders(), request);
}
/**
* 对ResultException类返回返回结果的处理
*/
protected ResponseEntity<Result> handleResultException(ResultException ex) {
Result body = Result.error(ex.getStatus());
return new ResponseEntity<>(body, HttpStatus.OK);
}
/**
* 异常类的统一处理
*/
protected ResponseEntity<Result> handleException(Exception ex, HttpHeaders headers, WebRequest request) {
Result body = Result.error(500,"Internal Server Error");
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
return new ResponseEntity<>(body, headers, status);
}
}
方式一
就是我们在我们自己写的beforeBodyWrite
的方法中,将返回值用 Result 包装过后再将Result 转为 String 类型进行返回。
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
response.getHeaders().add("Content-Type", "application/json");
return JSON.toJSONString(Result.success(body));
}
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
方式二
处理 spring 框架提供的转换器,如下代码中的方式,任选其一即可。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 第一种方式是将 json 处理的转换器放到第一位,使得先让 json 转换器处理返回值,这样 String转换器就处理不了了。
converters.add(0, new MappingJackson2HttpMessageConverter());
// 第二种就是把String类型的转换器去掉,不使用String类型的转换器
converters.removeIf(httpMessageConverter -> httpMessageConverter.getClass() == StringHttpMessageConverter.class);
}
}
之前只是对SpringBoot以及相关技术栈进行了初步学习,还没有真正的实操过,而这次通过所学技术栈配合阿里云服务实现了后端绝大部分的功能,虽然说有很多不足之处,细节处理的不够好,但还是感觉颇有成就感,毕竟也是第一次搞这种大的。
Spring Boot 无侵入式 实现API接口统一JSON格式返回