背景

在创建项目的初期,我们需要规范后端返回的数据结构,以便更好地与前端开发人员合作。

比如后端返回的数据为:

{
  "msg": "请跳转登陆页面",
}

此时前端无法确定后端服务的处理结果是成功的还是失败的。在前端展示页面,成功与失败的展示是要作区分的,甚至不同的成功或失败结果要做出不同的展现效果,这也就是我们为什么要对返回结果做出统一规范的原因。

返回结果定义

public class ResultWrap<T, M> {
  //  方便前端判断当前请求处理结果是否正常
  private int code;
  //  业务处理结果
  private T data;
  //  产生错误的情况下,提示用户信息
  private String message;
  //  产生错误情况下的异常堆栈,提示开发人员
  private String error;
  //  发生错误的时候,返回的附加信息
  private M metaInfo;
}
  • 1.为了把模糊的消息定性,我们给所有的返回结果都带上一个code字段,前端可以根据这个字段来判断我们的处理结果到底是成功的还是失败的;比如code=200的时候,我们的处理结果一定是成功的,其他的code值全都是失败的,并且我们会有多种code值,以便前端页面可以根据不同的code值做出不同的交互动作。
  • 2.一般在处理成功的情况下,后端会返回一些业务数据,比如返回订单列表等,那么这些数据我们就放在字段data里面,只有业务逻辑处理成功的情况下,data字段里面才可能有数据。
  • 3.如若我们的处理失败了,那么我们应该会有对应的消息提醒用户为什么处理失败了。比如文件上传失败了,用户名或密码错误等等,都是需要告知用户的。
  • 4.除了需要告知用户失败原因,我们也需要保留一个字段给开发人员,当错误是服务器内部错误的时候,我们需要让开发人员能第一时间定位是啥原因引起的,我们会把错误的堆栈信息给到error字段。
  • 5.当发生异常的时候,我们还会需要返回一些额外的补充数据给前端,比如用户登陆失败一次和失败多次需要不同的交互效果,此时我们会在metaInfo里面返回登陆失败次数;或者在用户操作没有权限的时候,根据用户是否已登陆来确定此时是跳转登陆页面还是直接弹窗提示当前操作没有权限。

定义好返回结果后,我们和前端的交互数据结果就统一好了。

异常的定义

之所以定义一个统一的异常类,是为了把所有的异常全部汇总成一个异常,最终我们只需要在异常处理的时候单独处理这一个异常即可。

@Data
public class AwesomeException extends Throwable {
  // 错误码
  private int code;
  // 提示消息
  private String msg;
​
  public AwesomeException(int code, String msg, Exception e) {
    super(e);
    this.code = code;
    this.msg = msg;
  }
​
  public AwesomeException(int code, String msg) {
    this.code = code;
    this.msg = msg;
  }
}
  • 1.我们同样需要一个与返回结果一致的code字段,这样的话,我们在异常处理的时候,才能把当前异常转换成最终的ResultWrap结果。
  • 2.msg就是在产生异常的时候,需要给到用户的提示消息。

这样的话,我们的后端开发人员遇到异常的情况,只需要通过创建AwesomeException异常对象抛出即可,不需要再为创建什么异常而烦恼了。

异常的处理

我们下面需要针对所有抛出的异常进行统一的处理:

import com.example.awesomespring.exception.AwesomeException;
import com.example.awesomespring.vo.ResultWrap;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * @className ExceptionHandler
 * @description:
 */
@Slf4j
@RestControllerAdvice
public class AwesomeExceptionHandler {
  /**
   * 捕获没有用户权限的异常
   *
   * @return
   */
  @ExceptionHandler(AuthorizationException.class)
  public ResultWrap handleException(AuthorizationException e) {
    return ResultWrap.failure(401, "您暂时没有访问权限!", e);
  }
​
  /**
   * 处理AwesomeException
   *
   * @param e
   * @param request
   * @param response
   * @return
   */
  @ExceptionHandler(AwesomeException.class)
  public ResultWrap handleAwesomeException(AwesomeException e, HttpServletRequest request, HttpServletResponse response) {
    return ResultWrap.failure(e);
  }
​
  /**
   * 专门针对运行时异常
   *
   * @param e
   * @return
   */
  @ExceptionHandler(RuntimeException.class)
  public ResultWrap handleRuntimeException(RuntimeException e) {
    return ResultWrap.failure(e);
  }
}

在项目中,我们集成了shiro权限管理框架,因为它抛出的异常没有被我们的AwesomeException包装,所以这个AuthorizationException异常需要我们单独处理。

AwesomeException是我们大多数业务逻辑抛出来的异常,我们根据AwesomeException里面的code、msg和它包装的cause,封装成一个最终的响应数据ResultWrap。

另一个RuntimeException是必须要额外处理的,任何开发人员都无法保证自己的代码是完全没有bug的,任何的空指针异常都会影响用户体验,这种编码性的错误我们需要通过统一的错误处理让它变得更柔和一点。

返回结果的处理

我们需要针对所有的返回结果进行检查,如果不是ResultWrap类型的返回数据,我们需要包装一下,以便保证我们和前端开发人员达成的共识。

import com.example.awesomespring.vo.ResultWrap;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Objects;
​
/**
 * @className AwesomeResponseAdvice
 * @description:
 */
@RestControllerAdvice
public class AwesomeResponseAdvice implements ResponseBodyAdvice {
  @Override
  public boolean supports(MethodParameter returnType, Class converterType) {
    // 如果返回String,那么就不包装了。
    if (StringHttpMessageConverter.class.isAssignableFrom(converterType)) {
      return false;
    }
    // 有一些情况是不需要包装的,比如调用第三方API返回的数据,所以我们做了一个自定义注解来避免所以的结果都被包装成ResultWrap。
    boolean ignore = false;
    IgnoreResponseAdvice ignoreResponseAdvice =
        returnType.getMethodAnnotation(IgnoreResponseAdvice.class);
    // 如果我们在方法上添加了IgnoreResponseAdvice注解,那么就不要拦截包装了
    if (Objects.nonNull(ignoreResponseAdvice)) {
      ignore = ignoreResponseAdvice.value();
      return !ignore;
    }
    // 如果我们在类上面添加了IgnoreResponseAdvice注解,也在方法上面添加了IgnoreResponseAdvice注解,那么以方法上的注解为准。
    Class<?> clazz = returnType.getDeclaringClass();
    ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class);
    RestController restController = clazz.getDeclaredAnnotation(RestController.class);
    if (Objects.nonNull(ignoreResponseAdvice)) {
      ignore = ignoreResponseAdvice.value();
    } else if (Objects.isNull(restController)) {
      ignore = true;
    }
    return !ignore;
  }
​
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    // 如果返回结果为null,那么我们直接返回ResultWrap.success()
    if (Objects.isNull(body)) {
      return ResultWrap.success();
    }
    // // 如果返回结果已经是ResultWrap,直接返回
    if (body instanceof ResultWrap) {
      return body;
    }
    // 否则我们把返回结果包装成ResultWrap
    return ResultWrap.success(body);
  }
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
/**
 * @className IgnoreResponseAdvice
 * @description:
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
  // 是否忽略ResponseAdvice;决定了是否要包装返回数据
  boolean value() default true;
}

至此,我们把整个异常处理与返回结果的统一处理全部关联起来了,我们后端的开发人员无论是返回异常还是返回ResultWrap或者其他数据结果,都能很好地保证与前端开发人员的正常协作,不必为数据结构的变化过多地沟通。

完整代码

ResultWrap.java

import com.example.awesomespring.exception.AwesomeException;
import com.example.awesomespring.util.JsonUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
​
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Objects;
​
/**
 * @className ResultWrap
 * @description:
 */
@Data
@AllArgsConstructor
public class ResultWrap<T, M> {
  //  方便前端判断当前请求处理结果是否正常
  private int code;
  //  业务处理结果
  private T data;
  //  产生错误的情况下,提示用户信息
  private String message;
  //  产生错误情况下的异常堆栈,提示开发人员
  private String error;
  //  发生错误的时候,返回的附加信息
  private M metaInfo;
​
  /**
   * 成功带处理结果
   *
   * @param data
   * @param <T>
   * @return
   */
  public static <T> ResultWrap success(T data) {
    return new ResultWrap(HttpStatus.OK.value(), data, StringUtils.EMPTY, StringUtils.EMPTY, null);
  }
​
  /**
   * 成功不带处理结果
   *
   * @return
   */
  public static ResultWrap success() {
    return success(HttpStatus.OK.name());
  }
​
  /**
   * 失败
   *
   * @param code
   * @param message
   * @param error
   * @return
   */
  public static <M> ResultWrap failure(int code, String message, String error, M metaInfo) {
    return new ResultWrap(code, null, message, error, metaInfo);
  }
​
  /**
   * 失败
   *
   * @param code
   * @param message
   * @param error
   * @param metaInfo
   * @param <M>
   * @return
   */
  public static <M> ResultWrap failure(int code, String message, Throwable error, M metaInfo) {
    String errorMessage = StringUtils.EMPTY;
    if (Objects.nonNull(error)) {
      errorMessage = toStackTrace(error);
    }
    return failure(code, message, errorMessage, metaInfo);
  }
​
  /**
   * 失败
   *
   * @param code
   * @param message
   * @param error
   * @return
   */
  public static ResultWrap failure(int code, String message, Throwable error) {
    return failure(code, message, error, null);
  }
​
  /**
   * 失败
   *
   * @param code
   * @param message
   * @param metaInfo
   * @param <M>
   * @return
   */
  public static <M> ResultWrap failure(int code, String message, M metaInfo) {
    return failure(code, message, StringUtils.EMPTY, metaInfo);
  }
​
  /**
   * 失败
   *
   * @param e
   * @return
   */
  public static ResultWrap failure(AwesomeException e) {
    return failure(e.getCode(), e.getMsg(), e.getCause());
  }
​
​
  /**
   * 失败
   *
   * @param e
   * @return
   */
  public static ResultWrap failure(RuntimeException e) {
    return failure(500, "服务异常,请稍后访问!", e.getCause());
  }
​
  private static final String APPLICATION_JSON_VALUE = "application/json;charset=UTF-8";
​
  /**
   * 把结果写入响应中
   *
   * @param response
   */
  public void writeToResponse(HttpServletResponse response) {
    int code = this.getCode();
    if (Objects.isNull(HttpStatus.resolve(code))) {
      response.setStatus(HttpStatus.OK.value());
    } else {
      response.setStatus(code);
    }
    response.setContentType(APPLICATION_JSON_VALUE);
    try (PrintWriter writer = response.getWriter()) {
      writer.write(JsonUtil.obj2String(this));
      writer.flush();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
​
  /**
   * 获取异常堆栈信息
   *
   * @param e
   * @return
   */
  private static String toStackTrace(Throwable e) {
    if (Objects.isNull(e)) {
      return StringUtils.EMPTY;
    }
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    try {
      e.printStackTrace(pw);
      return sw.toString();
    } catch (Exception e1) {
      return StringUtils.EMPTY;
    }
  }
}

AwesomeException.java

​import lombok.Data;
​
/**
 * @className AwesomeException
 * @description:
 */
@Data
public class AwesomeException extends Throwable {
​
  private int code;
  private String msg;
  public AwesomeException(int code, String msg, Exception e) {
    super(e);
    this.code = code;
    this.msg = msg;
  }
  public AwesomeException(int code, String msg) {
    this.code = code;
    this.msg = msg;
  }
}

AwesomeExceptionHandler.java

import com.example.awesomespring.exception.AwesomeException;
import com.example.awesomespring.vo.ResultWrap;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * @className ExceptionHandler
 * @description:
 */
@Slf4j
@RestControllerAdvice
public class AwesomeExceptionHandler {
  /**
   * 捕获没有用户权限的异常
   *
   * @return
   */
  @ExceptionHandler(AuthorizationException.class)
  public ResultWrap handleException(AuthorizationException e) {
    return ResultWrap.failure(401, "您暂时没有访问权限!", e);
  }
​
  /**
   * 处理AwesomeException
   *
   * @param e
   * @param request
   * @param response
   * @return
   */
  @ExceptionHandler(AwesomeException.class)
  public ResultWrap handleAwesomeException(AwesomeException e, HttpServletRequest request, HttpServletResponse response) {
    return ResultWrap.failure(e);
  }
​
  /**
   * 专门针对运行时异常
   *
   * @param e
   * @return
   */
  @ExceptionHandler(RuntimeException.class)
  public ResultWrap handleRuntimeException(RuntimeException e) {
    return ResultWrap.failure(e);
  }
}

IgnoreResponseAdvice.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
/**
 * @className IgnoreResponseAdvice
 * @description:
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
​
  boolean value() default true;
}

AwesomeResponseAdvice.java

import com.example.awesomespring.vo.ResultWrap;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
​
import java.util.Objects;
​
/**
 * @className AwesomeResponseAdvice
 * @description:
 */
@RestControllerAdvice
public class AwesomeResponseAdvice implements ResponseBodyAdvice {
  @Override
  public boolean supports(MethodParameter returnType, Class converterType) {
    if (StringHttpMessageConverter.class.isAssignableFrom(converterType)) {
      return false;
    }
    boolean ignore = false;
    IgnoreResponseAdvice ignoreResponseAdvice =
        returnType.getMethodAnnotation(IgnoreResponseAdvice.class);
    if (Objects.nonNull(ignoreResponseAdvice)) {
      ignore = ignoreResponseAdvice.value();
      return !ignore;
    }
    Class<?> clazz = returnType.getDeclaringClass();
    ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class);
    RestController restController = clazz.getDeclaredAnnotation(RestController.class);
    if (Objects.nonNull(ignoreResponseAdvice)) {
      ignore = ignoreResponseAdvice.value();
    } else if (Objects.isNull(restController)) {
      ignore = true;
    }
    return !ignore;
  }
​
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if (Objects.isNull(body)) {
      return ResultWrap.success();
    }
    if (body instanceof ResultWrap) {
      return body;
    }
    return ResultWrap.success(body);
  }
}

使用示例

// 这里只要返回AwesomeException,就会被ExceptionHandler处理掉,包装成ResultWrap
@PostMapping("/image/upload")
String upload(@RequestPart("userImage") MultipartFile userImage) throws AwesomeException {
    fileService.putObject("video", userImage);
    return "success";
}
​
// 加上IgnoreResponseAdvice注解,该返回结果就不会被包装
@IgnoreResponseAdvice
@GetMapping("/read")
Boolean read() {
    return true;
}

到此这篇关于Springboot项目异常处理及返回结果统一的文章就介绍到这了,更多相关Springboot异常处理内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Springboot项目异常处理及返回结果统一的更多相关文章

  1. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  2. 带你一步步从零搭建一个Vue项目

    Vue.js是现在比较优秀的Web前端框架,非常推荐大家入门学习,这篇文章主要给大家介绍了关于如何一步步从零搭建一个Vue项目的相关资料,文中通过图文以及实例代码介绍的非常详细,需要的朋友可以参考下

  3. SpringBoot本地磁盘映射问题

    这篇文章主要介绍了SpringBoot本地磁盘映射问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  4. java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

    这篇文章主要介绍了java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源),文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下

  5. SpringBoot整合Javamail实现邮件发送的详细过程

    日常开发过程中,我们经常需要使用到邮件发送任务,比方说验证码的发送、日常信息的通知等,下面这篇文章主要给大家介绍了关于SpringBoot整合Javamail实现邮件发送的详细过程,需要的朋友可以参考下

  6. Python图像运算之图像阈值化处理详解

    这篇文章将详细讲解图像阈值化处理,涉及阈值化处理、固定阈值化处理和自适应阈值化处理,这是图像边缘检测或图像增强等处理的基础,感兴趣的可以了解一下

  7. SpringBoot详细讲解视图整合引擎thymeleaf

    这篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,类似于Velocity、FreeMarker等传统引擎,关于其更多相关内容,需要的小伙伴可以参考一下

  8. Koa项目搭建过程详细记录

    本篇文章主要介绍了Koa项目搭建过程详细记录,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. Springboot集成mybatis实现多数据源配置详解流程

    在日常开发中,若遇到多个数据源的需求,怎么办呢?通过springboot集成mybatis实现多数据源配置,简单尝试一下,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  10. SpringBoot使用Minio进行文件存储的实现

    本文主要介绍了SpringBoot使用Minio进行文件存储的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部