Bootstrap

springboot 优雅处理统一返回值

背景

现有的项目中控制层的返回值总时使用Result进行返回。代码如下:

@GetMapping(value = "/c")
public Result c() {
    return Result.OK("c");
}

当所有接口都使用这种方式来返回时,感觉代码的可读性不够,所以通过 @RestControllerAdvice接口ResponseBodyAdvice对代码进行了优化。

实现

基础搭建

项目基于springboot

目录结构

目录结构

定义返回值

package com.example.universalReturn.common;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Result<T> {

    /**
     * 成功标志
     */
    private boolean success = true;

    /**
     * 返回处理消息
     */
    private String message = "操作成功!";

    /**
     * 返回代码
     */
    private Integer code = 0;

    /**
     * 返回数据对象 data
     */
    private T result;

    /**
     * 时间戳
     */
    private long timestamp = System.currentTimeMillis();

    public static<T> Result<T> OK() {
        Result<T> r = new Result<T>();
        r.setSuccess(true);
        r.setCode(CommonConstant.SC_OK_200);
        r.setMessage("成功");
        return r;
    }

    public static<T> Result<T> OK(T data) {
        Result<T> r = new Result<T>();
        r.setSuccess(true);
        r.setCode(CommonConstant.SC_OK_200);
        r.setResult(data);
        return r;
    }

    public static<T> Result<T> OK(String msg, T data) {
        Result<T> r = new Result<T>();
        r.setSuccess(true);
        r.setCode(CommonConstant.SC_OK_200);
        r.setMessage(msg);
        r.setResult(data);
        return r;
    }

    public static Result<Object> error(String msg) {
        return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg);
    }

    public static Result<Object> error(int code, String msg) {
        Result<Object> r = new Result<Object>();
        r.setCode(code);
        r.setMessage(msg);
        r.setSuccess(false);
        return r;
    }

    public Result<T> error500(String message) {
        this.message = message;
        this.code = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
        this.success = false;
        return this;
    }
}

定义一个通用的常量

package com.example.universalReturn.common;

public class CommonConstant {
    /** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
    public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
    /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
    public static final Integer SC_OK_200 = 200;
}

核心代码

统一处理返回值

package com.example.universalReturn.common;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@RestControllerAdvice
@Slf4j
public class ResponseAdvice  implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        // 1、返回true,则表示执行返回值的处理。后续的代码才会进入beforeBodyWrite方法
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (ObjectUtil.isNull(o)){
            return Result.OK();
        }
        if (o instanceof Result){
            return o;
        }
        if (o instanceof String){
            /**
             * 当返回类型是String时,消息转换器则是:StringHttpMessageConverter。
             *
             * 但通过ResponseAdvice返回的是一个Perf2WebRPCResult对象,并不是String类型,
             *
             * 通过异常堆栈可知,当在执行 getContentLength 时出错,即对该对象强转String发生异常。
             * 
             * 所以将返回的String转为对象之后再转为JSON字符串
             */
            return JSONUtil.toJsonStr(Result.OK(o));
        }
        return Result.OK(o);
    }


}


全局异常捕获

package com.example.universalReturn.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public Result<?> handleRuntimeException(Exception e) {
        // 用于捕获全局的RuntimeException异常
        log.error(e.getMessage(), e);
        return Result.error(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        // 用于捕获全局的Exception异常
        log.error(e.getMessage(), e);
        return Result.error("操作失败," + e.getMessage());
    }
}

常见的场景

返回空值

@GetMapping(value = "/a")
public Integer a() {
    return null;
}

处理后的返回值

{
    "success": true,
    "message": "成功",
    "code": 200,
    "result": null,
    "timestamp": 1665730720705
}

返回字符串

@GetMapping(value = "/b")
public String b() {
    return "删除成功";
}
{
    "success": true,
    "message": "操作成功!",
    "code": 200,
    "result": "删除成功",
    "timestamp": 1665730776941
}

返回统一返回值

@GetMapping(value = "/c")
public Result c() {
    return Result.OK("c");
}
{
    "success": true,
    "message": "操作成功!",
    "code": 200,
    "result": "c",
    "timestamp": 1665730792351
}

返回对象

@GetMapping(value = "/d")
public DemoVo d() {
    return new DemoVo("d");
}
{
    "success": true,
    "message": "操作成功!",
    "code": 200,
    "result": {
        "foo": "d"
    },
    "timestamp": 1665730798269
}

无返回值

@GetMapping(value = "/e")
public void e() {
    log.info("e");
}
{
    "success": true,
    "message": "成功",
    "code": 200,
    "result": null,
    "timestamp": 1665730803715
}

抛出运行时异常

@GetMapping(value = "/f")
public Object f() {
    throw new RuntimeException("返回运行时异常");
}
{
    "success": false,
    "message": "返回运行时异常",
    "code": 500,
    "result": null,
    "timestamp": 1665717905138
}

抛出异常

@GetMapping(value = "/g")
public Object g() throws Exception {
    throw new Exception("返回异常");
}
{
    "success": false,
    "message": "操作失败,返回异常",
    "code": 500,
    "result": null,
    "timestamp": 1665717909851
}

资源下载

包含demo源码以及对应接口的postman调用文件

;