文章目录
前言
统一结果封装可以带来以下几个好处:
- 提高一致性:所有API接口返回的数据格式一致,方便前端处理和展示
- 易于调试:统一的响应格式便于日志记录和错误追踪
- 增强安全性:可以在统一封装过程中对响应数据进行过滤和处理,避免敏感信息泄露
- 便于扩展:统一的响应格式有助于在项目扩展时保持代码的可维护性
{
"code": "code value",
"status": true or false,
"message": "message value",
"data": {}
}
这种返回格式主要包含四部分:
- code:状态码,由后端统一定义各种返回结果的状态码
- status:状态信息,用于标识请求是否成功
- message:返回信息,向客户端传递处理结果的描述
- data:实际数据,包含业务处理的具体结果
我们返回的信息至少包括code、message、data三部分,其中code是我们后端和前端约定好的状态码,message为返回信息,data为返回的实际数据,没有返回数据则为null。除了这三部分外,你还可以定义一些其他字段,比如状态信息status,请求时间timestamp
一、统一结果集处理器
1. 自定义常用结果枚举类
- 在utils包下新建enums包,其中新建HttpCodeEnum文件,在这里我们需要定义一个状态码的枚举类,不同的状态码对应不同的描述信息,如下:
/**
* <p>
* 常用结果枚举类
* </p>
*
* @author Patrick
* @since 2024-07-02
*/
@Getter
public enum HttpCodeEnum {
//==================== 登录相关枚举 ======================
/**
* 登陆超时
*/
RC100(100, "登陆超时!"),
/**
* 用户未登录
*/
RC101(101, "用户未登录,请先进行登录!"),
/**
* 账户被禁用,请联系管理员解决
*/
RC102(102, "账户被禁用,请联系管理员解决!"),
/**
* 用户信息加载失败
*/
RC103(103, "用户信息加载失败!"),
/**
* 用户身份信息获取失败
*/
RC104(104, "用户身份信息获取失败!"),
/**
* 用户名不能为空
*/
RC105(105, "用户名不能为空!"),
/**
* 密码不能为空
*/
RC106(106, "密码不能为空!"),
/**
* 用户名或密码错误
*/
RC107(107, "用户名或密码错误!"),
/**
* 用户登录成功
*/
RC108(108, "用户登录成功!"),
/**
* 用户注销成功
*/
RC109(109, "用户注销成功!"),
//==================== 注册相关枚举 ======================
/**
* 验证码错误
*/
RC300(300, "验证码错误!"),
/**
* 验证码过期
*/
RC301(301, "验证码已过期!"),
/**
* 用户名已存在
*/
RC302(302, "用户名已存在!"),
//======================= 其他枚举 ==============================
/**
* 参数格式不合法
*/
RC400(400, "参数格式不合法,请检查后重试!"),
/**
* 没有权限
*/
RC403(403, "您没有操作权限!"),
/**
* 页面不存在
*/
RC404(404, "未找到您请求的资源!"),
/**
* 请求方式错误
*/
RC405(405, "请求方式错误,请检查后重试!"),
/**
* 操作成功
*/
SUCCESS(200, "操作成功!"),
/**
* 操作失败
*/
ERROR(500, "操作失败!"),
/**
* 未知异常
*/
RC600(600, "服务器繁忙或服务器错误,请稍后再试!");
// 响应状态码
private final Integer code;
// 响应返回信息
private final String message;
HttpCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
该枚举类为我们和前端约定好的返回状态码和描述信息,可根据自己的需求修改状态码和返回信息
2. 自定义统一返回格式
- 在utils包下新建JsonResult文件,在这里我们定义一个统一返回结果的处理类:
/**
* <p>
* 统一结果集处理类
* </p>
*
* @author Patrick
* @since 2024-07-02
*/
@Data
public class JsonResult {
@Schema(description = "状态码")
private Integer code;
@Schema(description = "状态信息")
private Boolean status;
@Schema(description = "返回信息")
private String message;
@Schema(description = "数据")
private Map<String, Object> data = new HashMap<>();
/**
* 请求成功
*
* @return success
*/
public static JsonResult success() {
JsonResult result = new JsonResult();
result.setStatus(true);
result.setCode(HttpCodeEnum.SUCCESS.getCode());
result.setMessage(HttpCodeEnum.SUCCESS.getMessage());
return result;
}
/**
* 请求失败
*
* @return error
*/
public static JsonResult error() {
JsonResult result = new JsonResult();
result.setStatus(false);
result.setCode(HttpCodeEnum.ERROR.getCode());
result.setMessage(HttpCodeEnum.ERROR.getMessage());
result.data(null);
return result;
}
/**
* 设置 状态码 和 信息 (一)
*
* @param code 状态码
* @param message 信息
* @return JsonResult
*/
public JsonResult codeAndMessage(Integer code, String message) {
this.setCode(code);
this.setMessage(message);
return this;
}
/**
* 设置 状态码 和 信息 (二)
*
* @param httpCodeEnum 枚举信息
* @return JsonResult
*/
public JsonResult codeAndMessage(HttpCodeEnum httpCodeEnum) {
this.setCode(httpCodeEnum.getCode());
this.setMessage(httpCodeEnum.getMessage());
return this;
}
/**
* 设置 数据 (一)
*
* @param key key
* @param value value ==> Object
* @return JsonResult
*/
public JsonResult data(String key, Object value) {
this.data.put(key, value);
return this;
}
/**
* 设置 数据 (二)
*
* @param data Map 集合
* @return JsonResult
*/
public JsonResult data(Map<String, Object> data) {
this.setData(data);
return this;
}
}
@Data注解为Lombok工具类库中的注解,提供类的get、set、equals、hashCode、canEqual、toString方法,使用时需配置Lombok,如不配置请手动生成相关方法。
- 定义了统一返回类后,controller层返回数据时统一使用JsonResult.success()方法封装
@RestController
@Tag(name = "测试")
@RequestMapping("/test")
public class UserController {
@GetMapping("test1")
public JsonResult data() {
HashMap<String, String> map = new HashMap<>();
map.put("username", "admin");
map.put("password", "123456");
return JsonResult.success().data("list", map);
}
}
- 例如在以上代码中,我们的需求是查询测试用户信息,我们调用这个test1接口就返回了以下的结果:
{
"code": 200,
"status": true,
"message": "操作成功!",
"data": {
"list": {
"password": "123456",
"username": "admin"
}
}
}
二、自定义异常的基本步骤
1. 解析异常的使用
1. 异常分类
- java异常体系结构图
2. 为什么处理异常
- 处理异常,可以让程序在发生异常时不中断,提高代码的健壮性、容错性
3. 什么时候用异常
- 系统自动抛出异常
- 程序员手动抛出异常
4. 如何处理异常(两种方法)
- try - catch 直接解决异常
- throws 向上抛异常
2. 自定义业务异常处理
- 很多时候我们在处理业务逻辑的时候需要抛出一些异常,例如查询某个用户信息,如果用户根本不存在,则需要抛出异常
手动抛出异常的步骤:
- 自定义异常类,extends继承Excepion(编译时异常)或者RunTimeException(运行时异常)
- 自定义构造方法,无参、带参都可,传入异常信息
- 上抛throws异常
/**
* 业务异常处理类
*/
@Getter
public class BusinessException extends RuntimeException {
/**
* 状态码
*/
private final Integer code;
/**
* 报错信息
*/
private final String message;
/**
* 全参构造方法
*
* @param code 状态码
* @param message 报错信息
*/
public BusinessException(Integer code, String message) {
this.code = code;
this.message = message;
}
/**
* 构造方法
*
* @param message 报错信息
*/
public BusinessException(String message) {
this(HttpCodeEnum.ERROR.getCode(), message);
}
/**
* 构造方法
* @param httpCodeEnum http枚举类
*/
public BusinessException(HttpCodeEnum httpCodeEnum) {
this(httpCodeEnum.getCode(), httpCodeEnum.getMessage());
}
}
3. 自定义全局异常处理
- 创建GlobalException类,添加@RestControllerAdvice注解
- 创建一个方法,方法上添加@ExceptionHandler注解,指定异常类型,分别处理不同的异常
@RestControllerAdvice:是一个 Spring Boot 中用于全局异常处理的注解。它结合了@ControllerAdvice 和@ResponseBody 两个注解的功能,可以用于处理所有控制器中抛出的未处理异常,并返回自定义的响应数据
@ExceptionHandler:是异常处理器,两个结合表示当出现异常的时候执行某个通知
Exception.class:是 Java 中的一个类对象,它表示了 java.lang.Exception 类。Exception 是所有异常类的父类,它包含了一些通用的方法和属性,例如异常信息、异常堆栈跟踪等
/**
* 全局异常信息处理类
*/
@Slf4j
@RestControllerAdvice
public class GlobalException {
/**
* 处理业务异常
*
* @param e BusinessException
* @return businessException
*/
@ExceptionHandler(value = BusinessException.class)
public JsonResult businessException(BusinessException e) {
log.error("业务异常 => code: {}, 原因是: {}", e.getCode(), e.getMessage());
return JsonResult.error().codeAndMessage(e.getCode(), e.getMessage());
}
/**
* 处理未知(600)异常
*
* @param e otherException
* @return globalException
*/
@ExceptionHandler(value = Exception.class)
public JsonResult globalException(Exception e) {
log.error("未知(600)异常 => 原因是: {}", e.getMessage());
return JsonResult.error().codeAndMessage(HttpCodeEnum.RC600);
}
}
需要注意的是一个异常只会被捕获一次,比如空指针异常,只会被第二个方法捕获,处理之后不会再被最后一个方法捕获。当上面两个方法都没有捕获到指定异常时,最后一个方法指定了@ExceptionHandler(value = Exception.class)就可以捕获到所有的异常,相当于if elseif else语句
分别测试自定义异常、空指针异常以及其他异常:
- 自定义异常
- 接口:
@RestController
@RequestMapping("/test")
public class TestController {
/**
* 测试业务异常
*/
@PostMapping("/test")
public void test() {
int i = 0;
throw new BusinessException(HttpCodeEnum.RC403);
}
}
- 返回json:
{
"code": 403,
"status": false,
"message": "您没有操作权限!",
"data": null
}
- 控制台日志:
- 空指针异常:
- 接口:
/**
* 测试空指针异常
*/
@PostMapping("/test1")
public void test1(int id, String name) {
boolean equals = name.equals("id");
System.out.println("id: " + id + ", name: " + name);
}
- 返回json:
{
"code": 400,
"status": false,
"message": "参数格式不合法,请检查后重试!",
"data": null
}
- 控制台日志:
- 其他异常:
- 接口:
/**
* 测试其他异常
*/
@PostMapping("/test2")
public void test2() {
int i = 1 / 0;
}
- 返回json:
{
"code": 600,
"status": false,
"message": "服务器繁忙或服务器错误,请稍后再试!",
"data": null
}
- 控制台日志:
4. 处理404与405异常
1. 处理404异常
首先,我们在application.yml配置文件增加以下配置项:
spring:
# 关闭默认的静态资源路径映射
web:
resources:
add-mappings: false
- 现在当我们请求一个不存在的接口是,控制台会报noHandlerFound警告,然后被全局异常处理捕获到,并统一返回
- 控制台日志:
- 返回json:
{
"code": 600,
"status": false,
"message": "服务器繁忙或服务器错误,请稍后再试!",
"data": null
}
- 当发生404错误时,我们可以在全局异常处理中单独对NoHandlerFoundException异常进行处理
/**
* 处理页面不存在(404)异常
*
* @param e HttpServletRequest
* @return noHandlerFoundException
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public JsonResult noHandlerFoundException(HttpServletRequest e) {
log.error("请求地址错误(404)异常 => 请求方式: {}, 请求地址: {}", e.getMethod(), e.getServletPath());
return JsonResult.error().codeAndMessage(HttpCodeEnum.RC404);
}
在上面中,我们使用@ExceptionHandler(value = NoHandlerFoundException.class)单独捕获处理404异常,统一返回格式中code也设置为404。你也可以使用@ResponseStatus(HttpStatus.NOT_FOUND)注解指定http状态码为404
- 现在当我们再次发生404异常时,返回json如下:
{
"code": 404,
"status": false,
"message": "未找到您请求的资源!",
"data": null
}
- 控制台日志:
2. 处理405异常
- 405错误对应的异常为HttpRequestMethodNotSupportedException
/**
* 处理请求方式错误(405)异常
*
* @param e HttpServletRequest
* @return httpRequestMethodNotSupportedException
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public JsonResult httpRequestMethodNotSupportedException(HttpServletRequest e) {
log.error("请求方式错误(405)异常 => 请求方式: {}, 请求地址: {}", e.getMethod(), e.getServletPath());
return JsonResult.error().codeAndMessage(HttpCodeEnum.RC405);
}
- 返回json:
{
"code": 405,
"status": false,
"message": "请求方式错误,请检查后重试!",
"data": null
}
- 控制台日志: