前置条件
已经初始化好一个spring boot项目且版本为3X,项目可正常启动。
作者版本为3.2.2
初始化教程:新版idea(2023)创建spring boot3项目
封装目的
Spring Boot进行统一结果封装的主要目的是提高开发效率、降低代码重复率,并且提供一致的API响应格式,从而简化前后端交互和错误处理
具体好处如下:
- 方便前端处理:前端可以统一根据返回格式进行处理,而不用关注具体的返回类型和内容。
- 提高 API 的可读性和可维护性:统一结果封装可以使 API 的返回更加规范化,统一了 API 返回结果的格式,使得代码更加易于阅读和维护。
- 增加系统的健壮性:通过统一结果封装,能够更好地处理错误和异常情况,从而增加系统的健壮性和稳定性。
- 统一日志输出:通过统一结果封装,可以方便地记录请求参数、请求结果、异常信息等,方便后续的日志分析和排查问题。
常用格式
最常用的格式为状态码、提示信息、携带数据如图
具体格式还要看团队,这里我就使用这种通用的格式。
定义返回结果枚举类
通过使用这样的枚举类有助于避免硬编码的状态码和消息,提高了代码的可维护性。
import lombok.Getter; /** * @author mijiupro */ @Getter public enum ResultEnum { /* 成功状态码 */ SUCCESS(1, "操作成功!"), /* 错误状态码 */ FAIL(0, "操作失败!"), /* 参数错误:10001-19999 */ PARAM_IS_INVALID(10001, "参数无效"), PARAM_IS_BLANK(10002, "参数为空"), PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"), PARAM_NOT_COMPLETE(10004, "参数缺失"), /* 用户错误:20001-29999*/ USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"), USER_LOGIN_ERROR(20002, "账号不存在或密码错误"), USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"), USER_NOT_EXIST(20004, "用户不存在"), USER_HAS_EXISTED(20005, "用户已存在"), /* 系统错误:40001-49999 */ FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"), FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"), /* 数据错误:50001-599999 */ RESULT_DATA_NONE(50001, "数据未找到"), DATA_IS_WRONG(50002, "数据有误"), DATA_ALREADY_EXISTED(50003, "数据已存在"), AUTH_CODE_ERROR(50004, "验证码错误"), /* 权限错误:70001-79999 */ PERMISSION_UNAUTHENTICATED(70001, "此操作需要登陆系统!"), PERMISSION_UNAUTHORIZED(70002, "权限不足,无权操作!"), PERMISSION_EXPIRE(70003, "登录状态过期!"), PERMISSION_TOKEN_EXPIRED(70004, "token已过期"), PERMISSION_LIMIT(70005, "访问次数受限制"), PERMISSION_TOKEN_INVALID(70006, "无效token"), PERMISSION_SIGNATURE_ERROR(70007, "签名失败"); // 状态码 int code; // 提示信息 String message; ResultEnum(int code, String message) { this.code = code; this.message = message; } public int code() { return code; } public String message() { return message; } public void setCode(int code) { this.code = code; } public void setMessage(String message) { this.message = message; } }
定义返回结果封装类
import com.mijiu.commom.enumerate.ResultEnum; import lombok.Data; /** * @author mijiupro */ @Data public class Result<T> { // 操作代码 Integer code; // 提示信息 String message; // 结果数据 T data; public Result(ResultEnum resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); } public Result(ResultEnum resultCode, T data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } public Result(String message) { this.message = message; } //成功返回封装-无数据 public static Result<String> success() { return new Result<String>(ResultEnum.SUCCESS); } //成功返回封装-带数据 public static <T> Result<T> success(T data) { return new Result<T>(ResultEnum.SUCCESS, data); } //失败返回封装-使用默认提示信息 public static Result<String> error() { return new Result<String>(ResultEnum.FAIL); } //失败返回封装-使用返回结果枚举提示信息 public static Result<String> error(ResultEnum resultCode) { return new Result<String>(resultCode); } //失败返回封装-使用自定义提示信息 public static Result<String> error(String message) { return new Result<String>(message); } }
对返回结果封装
此时我们就可以使用封装类封装统一格式的响应了
import com.mijiu.common.result.Result; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author mijiupro */ @RestController @RequestMapping("/test1") public class Test1Controller { @GetMapping("/hello") public Result<String> hello() { return Result.success("hello world"); } }
我们的接口通常有很多,如果对每个接口都封装属于重复劳动,可以利用AOP技术拦截控制类的返回结果进行封装。
代码如下
import cn.hutool.json.JSONUtil; import com.mijiu.commom.result.Result; import io.micrometer.common.lang.NonNullApi; import io.micrometer.common.lang.Nullable; 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; /** * 统一结果封装增强器 * @author mijiupro */ @RestControllerAdvice(basePackages = "com.mijiu.controller")//指定要增强的包 @NonNullApi public class ResultAdvice implements ResponseBodyAdvice<Object> { /** * 判断是否支持对返回类型的处理 * * @param returnType 方法参数的类型 * @param converterType 转换器的类型 * @return 是否支持处理 */ @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } /** * 在写入响应体之前对返回结果进行处理和封装 * * @param body 返回结果对象 * @param returnType 方法参数的类型 * @param selectedContentType 响应内容的类型 * @param selectedConverterType 转换器的类型 * @param request HTTP 请求对象 * @param response HTTP 响应对象 * @return 处理后的返回结果 */ @Override public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //当返回结果为字符串类型需要单独处理 if (body instanceof String) { // 如果返回结果是字符串类型,将其封装为成功的结果对象,并转换为 JSON 字符串 return JSONUtil.toJsonStr(Result.success(body)); } // 将返回结果封装为成功的结果对象 return Result.success(body); } }
此代码可以兼容任何类型的返回结果对其进行统一封装。
为何遇到方法的返回结果是String类型需要手动转成json返回呢?
测试封装
现在com.mijiu.controller下的所有控制类的所有方法都会被拦截将返回结果进行统一格式封装。
到这里返回结果统一封装结束。