如果后端提供的接口没有固定的返回格式,那么前端将会很痛苦。所以在项目中一般都会封装一个返回值类,以确保所有接口都返回固定的格式。这个返回值并没有一个标准的格式,不同公司、不同团队可能封装的不一样,下面推荐一种格式,也是使用比较多的一种格式。
{
"code": 2003,
"message": "用户名或密码错误",
"data": null
}
这种格式主要包含3部分:
当然有些人觉得需要加上请求时间 timestamp
,还有错误码errorCode
,这些都可以根据实际情况去加。
我们需要定义一个状态码的枚举类,不同的状态码对应不同的描述,如下
public enum ResponseCode {
SUCCESS(200,"操作成功!"),
FAILURE(201,"操作失败"),
/**系统相关的错误码:5开头**/
ERROR(500,"系统异常,请稍后重试"),
/**参数相关的错误码:1开头**/
PARAM_ERROR(1000,"参数异常"),
/**权限相关的错误码:2开头**/
INVALID_TOKEN(2001,"访问令牌不合法"),
ACCESS_DENIED(2002,"没有权限访问该资源"),
USERNAME_OR_PASSWORD_ERROR(2003,"用户名或密码错误");
private final int code;
private final String message;
ResponseCode(int code, String message){
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
状态码的定义最好根据不同业务区分一下,例如上面跟参数相关的状态码我们以1开头,跟用户相关的状态码以2开头
public class ApiResponse<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer code;
private String message;
private T data;
public ApiResponse(Integer code,String message){
this.code = code;
this.message = message;
}
public ApiResponse(Integer code,String message,T data){
this(code,message);
this.data = data;
}
public static <T> ApiResponse<T> success(){
return new ApiResponse<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage());
}
public static <T> ApiResponse<T> success(T data){
return new ApiResponse<>(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMessage(),data);
}
public static <T> ApiResponse<T> fail(){
return new ApiResponse<>(ResponseCode.FAILURE.getCode(),ResponseCode.FAILURE.getMessage());
}
public static <T> ApiResponse<T> fail(Integer code,String message){
return new ApiResponse<>(code,message);
}
public static <T> ApiResponse<T> error(){
return new ApiResponse<>(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMessage());
}
public void setCode(Integer code){
this.code = code;
}
public void setMessage(String message){
this.message = message;
}
public void setData(T data){
this.data = data;
}
public Integer getCode(){
return this.code;
}
public String getMessage(){
return this.message;
}
public T getData(){
return this.data;
}
@Override
public String toString() {
return (new StringJoiner(", ", ApiResponse.class.getSimpleName() + "[", "]"))
.add("code=" + this.code)
.add("message='" + this.message + "'")
.add("data=" + this.data)
.toString();
}
}
这里详细解释一下
1.Serializable
接口
ApiResponse继承了Serializable
接口, 用于实现序列化功能。当一个类实现了 Serializable
接口后,它的对象可以被转换为字节流并进行存储、传输等操作。
2.泛型T
data是一个泛型,这个也好理解,因为我们返回的数据有可能是一个整型、字符串、集合。
这里为什么不用object?
Object
类型是 Java 中的基类,它可以表示任何类型的对象。Java 中的所有类都是Object
的子类,因此Object
类型可以用于引用任何对象。但是,由于Object
类型是非泛型的,因此在使用它时需要进行类型转换,这可能会导致运行时错误。总之,泛型
T
和Object
类型都可以用于表示不确定类型的对象,但是泛型更加类型安全,而Object
类型则需要进行类型转换。在编写代码时,应该尽可能地使用泛型,以提高代码的可读性和安全性。
3.定义了如下几个方法
//无参,比如说添加用户、编辑用户成功,我们直接返回200,操作成功
success()
//比如说请求数据,返回一个列表,我们之直接返回200,操作成功,数据
success(T data)
//无参,比如说添加用户、编辑用户失败,我们直接返回201,操作失败
fail()
//比如说登录失败,我们直接返回自定义的状态码,错误信息
fail(Integer code,String message)
//系统错误,比如你在代码中写了一行1/0(1除以0),直接报500,"系统异常,请稍后重试"
error()
对于error()
可能会有这样的疑问:是不是所有的异常都返回500,为啥不异常消息不返回出去?
这也好理解,一般来说系统抛出的异常,我们打印在日志里面即可,不能把异常信息返回给前端。
上面我们讲到了系统异常,如果系统异常,那它是如何捕抓到的呢?
下面直接上代码
@Slf4j
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(Exception.class)
public ApiResponse<Void> exception(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return ApiResponse.error();
}
}
@RestControllerAdvice
@RestControllerAdvice
是一个 Spring Boot 中用于全局异常处理的注解。它结合了@ControllerAdvice
和@ResponseBody
两个注解的功能,可以用于处理所有控制器中抛出的未处理异常,并返回自定义的响应数据。
Exception.class
Exception.class
是 Java 中的一个类对象,它表示了java.lang.Exception
类。Exception
是所有异常类的父类,它包含了一些通用的方法和属性,例如异常信息、异常堆栈跟踪等。
很多时候我们在处理业务逻辑的时候需要抛出一些异常,例如查询某个用户信息,如果用户根本不存在,则需要抛出异常。
public class CustomException extends RuntimeException{
protected Integer code;
protected String message;
public CustomException(Integer code,String message,Throwable e) {
super(message,e);
this.code = code;
this.message = message;
}
public CustomException(Integer code,String message){
this(code,message,null);
}
public void setCode(Integer code){
this.code = code;
}
public void setMessage(String message){
this.message = message;
}
public Integer getCode(){
return this.code;
}
}
这里的代码比较简单,也不多讲解,可以根据实际情况扩展。
然后把这个异常添加到全局异常类里面去
@ExceptionHandler(value = CustomException.class)
public ApiResponse<Void> customExceptionHandler(CustomException e) {
return ApiResponse.fail(e.getCode(),e.getMessage());
}
这样我们在任何地方抛出这个异常,都会被捕获到。
例如:
@GetMapping("/test")
public ApiResponse<String> test(){
throw new CustomException(ResponseCode.USERNAME_OR_PASSWORD_ERROR.getCode(),ResponseCode.USERNAME_OR_PASSWORD_ERROR.getMessage());
}
得到如下结果:
{
"code": 2003,
"message": "用户名或密码错误",
"data": null
}