SpringBoot统一API响应实现

1、技术概述

​ 普通的接口仅仅将业务数据裸着返回给客户端是远远不够的,需要给业务数据穿一层衣服,封装起来再返回给客户端,在封装时还要附上一些附加信息一起返回。通过Spring AOP机制,统一封装Restful API的响应数据。

2、详细实现

2.1 Result 实体类

@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);
    }
}

2.2 ResultStatus 枚举类

@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;
    }
}

2.3 http异常处理

@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);
    }
}

2.4 ResultException 异常类

@Data
@AllArgsConstructor
public class ResultException extends Exception{

    ResultStatus status;
}

2.5 @JsonResponseBody 注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ResponseBody
public @interface JsonResponseBody {

}

2.6 全局统一返回(关键)

@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);
    }
}

3、问题与解决

  • 问题: 实现ResponseBodyAdvice接口,统一拦截接口返回数据时,Controller返回值是String 类型时异常
  • 解决:

方式一

就是我们在我们自己写的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);
    }
}

4、总结

​ 之前只是对SpringBoot以及相关技术栈进行了初步学习,还没有真正的实操过,而这次通过所学技术栈配合阿里云服务实现了后端绝大部分的功能,虽然说有很多不足之处,细节处理的不够好,但还是感觉颇有成就感,毕竟也是第一次搞这种大的。

5、参考博客

Spring Boot 无侵入式 实现API接口统一JSON格式返回

你可能感兴趣的:(软工实践,spring,boot,java,spring)