2024-01-17
原文作者:路人 原文地址: http://www.itsoku.com/course/6/225

1、本文目的

目前多数系统都采用前后端分离的方式,后端只负责提供restfull接口,返回json格式的数据就可以了,前端负责渲染。

本文带大家主要解决2个问题,在springmvc提供json格式的接口的时候,需要解决2个问题

  • 问题1:所有接口的返回值采用统一的格式
  • 问题2:系统中异常处理设计的问题,采用一种非常好的方式来解决这个问题

下面咱们一起来解决这2个问题。

2、解决问题1:实现统一的返回值

所有的接口均返回ResultDto类型的数据,ResultDto类的代码如下,主要有4个字段

  • success:表示接口是成功还是失败
  • code:错误码,当有异常的时候,可以返回具体的错误码
  • msg:提示信息,比如:操作成功、用户名有误、密码有误等等
  • data:类型是一个泛型,表示任意类型,这个用来存放接口中具体返回的数据,可以是任意类型的对象
  • 还提供了几个静态方法,方便创建ResultDto对象
    /**
     * rest接口通用返回值数据结构
     *
     * @param <T>
     */
    public class ResultDto<T> {
        //接口状态(成功还是失败)
        private Boolean success;
        //错误码
        private String code;
        //提示信息
        private String msg;
        //数据
        private T data;
    
        public Boolean getSuccess() {
            return success;
        }
    
        public void setSuccess(Boolean success) {
            this.success = success;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public static <T> ResultDto<T> success(T data) {
            return success(data, "操作成功!");
        }
    
        public static <T> ResultDto<T> success(T data, String msg) {
            ResultDto<T> result = new ResultDto<>();
            result.setSuccess(Boolean.TRUE);
            result.setMsg(msg);
            result.setData(data);
            return result;
        }
    
        public static <T> ResultDto<T> error(String msg) {
            return error(null,msg);
        }
        public static <T> ResultDto<T> error(String code,String msg) {
           return error(code,msg,null);
        }
        public static <T> ResultDto<T> error(String code, String msg, T data) {
            ResultDto<T> result = new ResultDto<>();
            result.setSuccess(Boolean.FALSE);
            result.setCode(code);
            result.setMsg(msg);
            result.setData(data);
            return result;
        }
    }

3、解决问题2:统一处理异常

3.1、如何做?

异常处理这块,我们的设计主要有2点,通过这2点来解决异常处理的问题

  • 第一点:定义一个基础的业务异常类(BusException),业务代码中手动抛出异常的时候,统一抛出这种类型的异常,异常类型中可以携带更详细的错误信息,比如错误码、提示信息、扩展数据等等
  • 第2点:采用springmvc全局来处理异常,控制器中不要捕获异常,将一次交给springmvc框架来统一处理。

3.2、具体代码

下面我们来看具体的代码片段,主要有2个类

业务异常类:BusException

代码比较简单,主要有2个属性和几个静态方法

  • code:异常错误码,最终会丢给ResultDto的code属性输出到客户端
  • data:异常的时候,可以传递一些扩展信息,此时可以丢到data中,最终会丢给ResultDto的data属性输出到客户端
  • 提供了几个静态方法,便于抛出BusException异常
  • 当我们的业务代码中需要抛出异常的时候,要求均抛出BusException类型的异常
    /**
     * 业务异常
     */
    public class BusException extends RuntimeException {
        //异常错误码
        private String code;
        //错误扩展信息
        private Object data;
    
        public BusException(String msg) {
            this(null, msg);
        }
    
        public BusException(String code, String msg) {
            this(code, msg, null);
        }
    
        public BusException(String code, String msg, Object data) {
            super(msg);
            this.code = code;
            this.data = data;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    
        public static void throwBusException(String msg) {
            throwBusException(null, msg);
        }
    
        public static void throwBusException(String code, String msg) {
            throwBusException(code, msg, null);
        }
    
        public static void throwBusException(String code, String msg, Object data) {
            throw new BusException(code, msg, data);
        }
    }

全局异常统一处理类:GlobalExceptionHandle

如下代码,大家对springmvc统一异常处理不了解,建议先看一下上一篇文章。

注意下面代码中的@1,这里使用到了@RestControllerAdvice,这个注解之前没有介绍过,他和@ControllerAdvice功能类似,只是这个注解内部定义的时候上面多了一个@ResponseBody注解,表示下面这个类中处理异常的方法返回值最终都会以json格式输出到客户端

    /**
     * 全局异常处理
     */
    @RestControllerAdvice // @1
    public class GlobalExceptionHandle {
        /**
         * 统一处理业务异常
         *
         * @param e
         * @param <T>
         * @return
         */
        @ExceptionHandler(BusException.class)
        public <T> ResultDto<T> doBusException(BusException e) {
            //1、记录错误日志
            //2、返回结果
            return ResultDto.error(e.getCode(), e.getMessage(), (T) e.getData());
        }
    
        /**
         * 处理其他异常
         *
         * @param e
         * @param <T>
         * @return
         */
        @ExceptionHandler
        public <T> ResultDto<T> doException(Exception e) {
            //1、记录错误日志
            //2、返回结果
            return ResultDto.error("系统异常,请联系管理员,错误详情:" + e.getMessage());
        }
    }

2个问题解决了,下面我们来看看controller中如何使用。

4、Controller中代码如何写?

来个案例

如下代码,注意两点信息

  • 内部提供了2个接口,接口的返回值都是ResultDto类型的
  • 代码中,没有了try catch,而是将异常类型封装为BusException类型抛出,比如验证码有误,会抛出了BusException,顺便携带了错误码和错误提示信息,这些都会通过全局异常的处理,输出到客户端
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        /**
         * 获取用户id
         *
         * @param code
         * @return
         */
        @RequestMapping("/getUserName")
        public ResultDto<String> getUserName(@RequestParam("code") Integer code) {
            if (!Integer.valueOf(6666).equals(code)) {
                //验证码有误的时候,返回4001错误码
                BusException.throwBusException("4001", "验证码有误!");
            }
            return ResultDto.success("路人");
        }
    
        /**
         * 获取用户id
         *
         * @param code
         * @return
         */
        @RequestMapping("/getUserId")
        public ResultDto<String> getUserId(@RequestParam("code") Integer code) {
            if (!Integer.valueOf(6666).equals(code)) {
                BusException.throwBusException("4001", "验证码有误!");
            }
            return ResultDto.success("8888");
        }
    
    }

验证效果

下面我们通过idea提供的HTTP client工具搞三个测试用例,测试下接口的效果,如下图,大家可以分别运行一下3个案例。

202401172041394131.png

用例1输出结果:

    {
      "success": true,
      "code": null,
      "msg": "操作成功!",
      "data": "路人"
    }

用例2输出结果:

    {
      "success": false,
      "code": "4001",
      "msg": "验证码有误!",
      "data": null
    }

用例3输出的结果:

    {
      "success": false,
      "code": null,
      "msg": "系统异常,请联系管理员,错误详情:Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: \"abc\"",
      "data": null
    }

5、总结

本文内容主要有2点:统一返回值、统一异常的处理,这2点大家要好好掌握,目前业界很少使用springmvc直接开发接口了,更多的是采用springboot来开发接口,本文的内容直接可以用到springboot中,来优化咱们的系统。

6、案例代码

    git地址:https://gitee.com/javacode2018/springmvc-series

202401172041401442.png

阅读全文