springboot-Rest接口返回统一格式数据

springboot-Rest接口返回统一格式数据

为什么要统一格式?

我们使用SpringBoot编写接口的时候,最好是返回一个统一格式的JSON,该格式包含错误码,附带信息,以及携带的数据。这样前端在解析的时候就能统一解析,同时携带错误码可以更加容易的排查错误。

定义统一格式ApiResult

public class ApiResult {
    /**
     * 错误码,对应{@link ErrorCode},表示一种错误类型
     * 如果是成功,则code为200
     */
    private int code;
    /**
     * 对错误的具体解释
     */
    private String message;
    /**
     * 返回的结果包装在value中,value可以是单个对象
     */
    private final Object value;
	//忽略getter和setter,以及构造函数
}

其中ErrorCode可以这样定义

public enum ErrorCode {
    SUCCESS(200,"成功"),
    NO_PERMISSION(211,"权限不足"),
    SERVER_ERROR(10000,"服务器异常"),
    AUTH_ERROR(10001,"认证失败"),
    PARAMS_ERROR(10002,"参数错误"),
    JSON_PARSE_ERROR(10003,"Json解析错误"),
    ILLEAGAL_STRING(15001,"非法字符串"),
    UNKNOW_ERROR(16000,"未知错误");


    private int code;
    private String msg;

    ErrorCode(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

如何统一返回?

方式一:在每一个函数末尾,都调用ApiResult的构造函数,然后返回,即如下

@RequestMapping("/test")
public ApiResult test(){
	String result = "hello world";
	return ApiResult.success(result);
}

但是这种方式太麻烦了,我们希望这种包装是透明的,即我们想直接返回对象,然后框架帮我们做一层包装。因此看方式二

方式二:在接口方法返回之后,在序列化器对该结果进行序列化之前,我们可以使用ResponseBodyAdvice在这两个阶段之间对结果进行操作,因此我们可以使用该Adivice在序列化之前,拦截返回结果,对结果进行包装,然后再将包装后的结果返回,最后序列化成对应的JSON字符串。
以下是具体实践

@ControllerAdvice(annotations = RestController.class)
public class ApiResultHandler implements ResponseBodyAdvice {

    private ThreadLocal<ObjectMapper>  mapperThreadLocal = ThreadLocal.withInitial(ObjectMapper::new);

    private static final Class[] annos = {
            RequestMapping.class,
            GetMapping.class,
            PostMapping.class,
            DeleteMapping.class,
            PutMapping.class
    };

    /**
     * 对所有RestController的接口方法进行拦截
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        AnnotatedElement element = returnType.getAnnotatedElement();
        return Arrays.stream(annos).anyMatch(anno -> anno.isAnnotation() && element.isAnnotationPresent(anno));
    }

    @Override
    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        Object out;
        ObjectMapper mapper = mapperThreadLocal.get();
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if(body instanceof ApiResult){
            out = body;
        }
        else if (body instanceof ErrorCode){
            ErrorCode errorCode = (ErrorCode) body;
            out = new ApiResult(errorCode.getCode(),errorCode.getMsg(),null);
        }
        else if (body instanceof String){
            ApiResult result = ApiResult.valueOf(body);
            try {
                //因为是String类型,我们要返回Json字符串,否则SpringBoot框架会转换出错
                out = mapper.writeValueAsString(result);
            } catch (JsonProcessingException e) {
                out = ApiResult.errorOf(ErrorCode.JSON_PARSE_ERROR,e.getMessage());
            }
        }
        else{
            out = ApiResult.valueOf(body);
        }
        return out;
    }

}

总结

这时候你再直接返回对象,你会发现该对象已经自动包装成ApiResult了。思考一下,如果发生异常了怎么办呢?发生异常其实也可以使用方式二的思路去解决的,具体解决方法我下次再写吧!希望对大家有帮助~

你可能感兴趣的:(java)