springboot 统一返回数据格式和统一异常处理

数据类 

package com.william.commonutils;
import com.fasterxml.jackson.annotation.JsonInclude;

import java.io.Serializable;

/**
 * @program: online_edu
 * @description: 自定义返回数据类
 * @author: William Munch
 * @create: 2020-11-27 09:55
 **/
/*
当我们提供接口的时候,  Ajax  返回的时候,当对象在转换  JSON  (序列化)的时候,值为Null 或者为“” 的字段还是输出来了。看上去不优雅。

现在我叙述三种方式来控制这种情况。

注解的方式( @JsonInclude(JsonInclude.Include.NON_EMPTY))
通过@JsonInclude 注解来标记,但是值的可选项有四类。
Include.Include.ALWAYS (Default / 都参与序列化)
Include.NON_DEFAULT(当Value 为默认值的时候不参与,如Int a; 当 a=0 的时候不参与)
Include.NON_EMPTY(当Value 为“” 或者null 不输出)
Include.NON_NULL(当Value 为null 不输出)
 */
//如果是null 不返回
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class MyResponse<T> implements Serializable {

    private static final long serialVersionUID = -5270573611742584765L;
    private boolean success;//是否成功
    private T data;//数据
    private String code;//  成功/失败编码
    private String msg;// 对应编码的含义

    public MyResponse() {
    }

    public MyResponse(boolean success, T data) {
        super();
        this.success = success;
        this.data = data;
    }

    @Override
    public String toString() {
        return "MyResponse{" +
                "success=" + success +
                ", data=" + data +
                ", code='" + code + '\'' +
                ", msg='" + msg + '\'' +
                '}';
    }

    public MyResponse(boolean success, T data, String code, String msg) {
        super();
        this.success = success;
        this.data = data;
        this.code = code;
        this.msg = msg;
    }

    public MyResponse(boolean success, String code, String msg) {
        this.success = success;
        this.code = code;
        this.msg = msg;
    }

    public MyResponse(boolean success, MyResponseEnums enums) {
        this.success = success;
        this.code = enums.getCode();
        this.msg = enums.getMsg();
    }

    public MyResponse(boolean success, T data, MyResponseEnums enums) {
        this.success = success;
        this.data = data;
        this.code = enums.getCode();
        this.msg = enums.getMsg();
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    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;
    }




}

提示枚举

package com.william.commonutils;

/**
 * @program: online_edu
 * @description: 友好提示枚举
 * @author: William Munch
 * @create: 2020-11-27 10:06
 **/
public enum MyResponseEnums {
    //Config Error 1xxx  基础配置异常
    SYSTEM_ERROR("1000", "系统错误"),
    DATABASE_ERROR("1001", "数据库异常"),
    CONNECTION_ERROR("1002", "网络连接请求失败"),

    //Success 2xxx  业务逻辑成功
    REGISTER_SUCCESS("2000", "注册成功"),
    LOGIN_SUCCESS("2001", "登陆成功"),
    LOGOUT_SUCCESS("2002", "已退出登录"),
    OPERATE_SUCCESS("2003", "操作成功"),
    SEND_EMAIL_SUCCESS("2004", "邮件已发送,请注意查收"),
    EDIT_PWD_SUCCESS("2005", "修改密码成功"),
    UPLOAD_FILE_SUCCESS("2006", "上传成功"),
    QUERY_SUCCESS("2007", "查询成功"),


    //Error 3xxx  业务逻辑失败
    OPERATE_FAILURE("3000", "操作失败"),
    REPEAT_REGISTER("3001", "重复注册"),
    NO_USER_EXIST("3002", "用户不存在"),
    INVALID_PASSWORD("3003", "密码错误"),
    NO_LOGIN("3004", "未登陆"),
    NO_FILE_SELECT("3005", "未选择文件"),
    ERROR_IDCODE("3006", "验证码不正确"),
    NO_RECORD("3007", "没有查到相关记录"),
    REPEAT_MOBILE("3008", "已存在此手机号"),
    REPEAT_EMAIL("3009", "已存在此邮箱地址"),
    INVALID_MOBILE("3010", "无效的手机号码"),
    INVALID_EMAIL("3011", "无效的邮箱"),
    INVALID_GENDER("3012", "无效的性别"),


    //Client Error 4xxx  客户端错误  仿照4xx的http错误
    BAD_REQUEST("4000", "错误的请求参数"),
    UNAUTHORIZED("4001", "未经授权"),
    PAYMENT_REQUIRED("4002", "付费请求"),
    FORBIDDEN("4003", "资源不可用"),
    NOT_FOUND("4004", "无效的访问路径"),
    METHOD_NOT_ALLOWED("4005", "不合法的请求方式"),
    NOT_ACCEPTABLE("4006", "不可接受"),
    PROXY_AUTHENTICATION_REQUIRED("4007", "需要代理身份验证"),
    REQUEST_TIMEOUT("4008", "请求超时"),
    CONFLICT("4009", "指令冲突"),
    GONE("4010", "文档永久地离开了指定的位置"),
    LENGTH_REQUIRED("4011", "需要CONTENT-LENGTH头请求"),
    PRECONDITION_FAILED("4012", "前提条件失败"),
    REQUEST_ENTITY_TOO_LARGE("4013", "请求实体太大"),
    REQUEST_URI_TOO_LONG("4014", "请求URI太长"),
    UNSUPPORTED_MEDIA_TYPE("4015", "不支持的媒体类型"),
    REQUESTED_RANGE_NOT_SATISFIABLE("4016", "请求的范围不可满足"),
    EXPECTATION_FAILED("4017", "期望失败"),


    //Server Error 5xxx  服务器错误  仿照5xx的http错误
    INTERNAL_SERVER_ERROR("5000", "内部服务器错误"),
    NOT_IMPLEMENTED("5001", "未实现"),
    BAD_GATEWAY("5002", "错误的网关"),
    SERVICE_UNAVAILABLE("5003", "服务不可用"),
    GATEWAY_TIMEOUT("5004", "网关超时"),
    HTTP_VERSION_NOT_SUPPORTED("5005", "HTTP版本不支持"),


    //终极赖皮手段
    UNKNOWN_ERROR("0000", "未知错误");

    private String code;
    private String msg;

    private MyResponseEnums(String code, String msg) {

        this.code = code;
        this.msg = msg;
    }

    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;
    }


}

自定义异常

package com.william.commonutils;

/**
 * @program: online_edu
 * @description: 自定义异常
 * 比如普通用户的越权行为,管理员修改超级管理员的信息,查找不存在的人,等等,系统会报告异常并提示信息
 * @author: William Munch
 * @create: 2020-11-27 21:40
 **/


//service里抛出异常,全局可以捕获,就不用一层一层往外传到controller里,再由controller返回一个表征错误的myresponse了
public class MyRuntimeException extends RuntimeException {
    private static final long serialVersionUID = -4028958620005315321L;
    protected String code;
    protected String msg;

    public MyRuntimeException() {
        super();
    }

    public MyRuntimeException(MyResponseEnums enums) {
        super();
        this.code = enums.getCode();
        this.msg = enums.getMsg();
    }

    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;
    }
}

全局异常捕获

package com.william.servicebase.handler;

import com.william.commonutils.MyResponse;
import com.william.commonutils.MyResponseEnums;
import com.william.commonutils.MyRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.net.ConnectException;
import java.sql.SQLException;

/**
 * @program: online_edu
 * @description: 我的全局异常处理类
 * @author: William Munch
 * @create: 2020-11-27 20:07
 **/
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class MyGlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(MyGlobalExceptionHandler.class);

    /*
     *
     * 总是返回 200,是否成功在 body 里面用一个字段来指示
     * 全200还有若干实际细节的好处,打比方阿里云看到你500 404什么的就会直接砍掉你的body,
     * 你的client到server之间有太多东西会经手你的http请求,大量使用200之外的状态码各种稀奇古怪的问题
     *
     * 业务逻辑的异常都做处理,友好返回,status返回200(HttpStatus.OK)
     * 系统运行期间的异常(例如空指针异常)也做 "系统错误"的返回,状态码也是200
     * 系统运行期间的异常中,有些我们单独处理,如下文的数据库异常,(特定异常),其余的才会走上边那条路
     *
     *
     * MyBasicErrorController里把映射到/error的错误也做友好返回,但是status显示404,500等真实状态码(保持HttpStatus),
     * 这些如果body被阿里截取调也没关系
     *
     * 因为系统异常做了处理,本该会被最终映射到500的错误上,因为处理了,状态码改了200,再配上错误提示,直接返回了。
     * 要是不做系统异常处理,映射到500,再映射到/error错误,错误提示就不会显示"系统错误"了,而会显示"内部服务器错误"
     * 又因为MyBasicErrorController没改状态码,所以甚至可能会被截了错误提示,只剩下500状态码
     */


    /*
     * 系统出现异常(能捕获所有的)
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.OK)
    public MyResponse systemError(Exception e) {
        logger.error("occurs error when execute method ,message {}", e.getMessage());
        return new MyResponse<>(false,MyResponseEnums.SYSTEM_ERROR);
    }

    /**
     * 数据库操作出现异常(特定异常)
     */
    @ExceptionHandler(value = {SQLException.class, DataAccessException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.OK)
    public MyResponse databaseError(Exception e) {
        logger.error("occurs error when execute method ,message {}", e.getMessage());
        return new MyResponse<>(false, MyResponseEnums.DATABASE_ERROR);
    }

    /**
     * 远程连接失败(特定异常)
     */
    @ExceptionHandler(value = {ConnectException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.OK)
    public MyResponse connectError(Exception e) {
        logger.error("occurs error when execute method ,message {}", e.getMessage());
        return new MyResponse<>(false, MyResponseEnums.CONNECTION_ERROR);
    }

    /*
     * 自定义异常(用于捕获自己业务中抛出的)
     */
    @ExceptionHandler(value = {MyRuntimeException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.OK)
    public  MyResponse myError(MyRuntimeException exception, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        logger.error("occurs error when execute url ={} ,message {}", requestURI, exception.getMsg());
        return new MyResponse<>(false, exception.getCode(), exception.getMsg());
    }
}

将映射到error页面的错误也予以json返回

package com.william.servicebase.controller;

import com.william.commonutils.MyResponse;
import com.william.commonutils.MyResponseEnums;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;

/**
 * @program: online_edu
 * @description: 统一处理一些映射到/error的错误
 * 这些错误出现时,springboot默认会由DefaultHandlerExceptionResolver做处理,然后将请求映射到 /error 路径中去,
 * 如果没有相应的路径请求处理器,那么就会返回默认的Whitelabel 错误页面
 * 在这里我将统一处理这些映射到/error的错误,予以友好返回Json,但是不改变其状态码
 * @author: William Munch
 * @create: 2020-11-27 21:43
 **/

@RestController
@ApiIgnore
public class MyBasicErrorController implements ErrorController {

    private static final String ERROR_PATH = "/error";

    @RequestMapping(ERROR_PATH)
    public MyResponse handleError(HttpServletRequest request) {
        //获取statusCode 404等
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == 400) {
            return new MyResponse<>(false,  MyResponseEnums.BAD_REQUEST);
        } else if (statusCode == 401) {
            return new MyResponse<>(false,  MyResponseEnums.UNAUTHORIZED);
        } else if (statusCode == 402) {
            return new MyResponse<>(false,  MyResponseEnums.PAYMENT_REQUIRED);
        } else if (statusCode == 403) {
            return new MyResponse<>(false,  MyResponseEnums.FORBIDDEN);
        } else if (statusCode == 404) {
            return new MyResponse<>(false, MyResponseEnums.NOT_FOUND);
        } else if (statusCode == 405) {
            return new MyResponse<>(false,  MyResponseEnums.METHOD_NOT_ALLOWED);
        } else if (statusCode == 406) {
            return new MyResponse<>(false,  MyResponseEnums.NOT_ACCEPTABLE);
        } else if (statusCode == 407) {
            return new MyResponse<>(false,  MyResponseEnums.PROXY_AUTHENTICATION_REQUIRED);
        } else if (statusCode == 408) {
            return new MyResponse<>(false,  MyResponseEnums.REQUEST_TIMEOUT);
        } else if (statusCode == 409) {
            return new MyResponse<>(false,  MyResponseEnums.CONFLICT);
        } else if (statusCode == 410) {
            return new MyResponse<>(false,  MyResponseEnums.GONE);
        } else if (statusCode == 411) {
            return new MyResponse<>(false,  MyResponseEnums.LENGTH_REQUIRED);
        } else if (statusCode == 412) {
            return new MyResponse<>(false,  MyResponseEnums.PRECONDITION_FAILED);
        } else if (statusCode == 413) {
            return new MyResponse<>(false, MyResponseEnums.REQUEST_ENTITY_TOO_LARGE);
        } else if (statusCode == 414) {
            return new MyResponse<>(false,  MyResponseEnums.REQUEST_URI_TOO_LONG);
        } else if (statusCode == 415) {
            return new MyResponse<>(false,  MyResponseEnums.UNSUPPORTED_MEDIA_TYPE);
        } else if (statusCode == 416) {
            return new MyResponse<>(false,  MyResponseEnums.REQUESTED_RANGE_NOT_SATISFIABLE);
        } else if (statusCode == 417) {
            return new MyResponse<>(false,  MyResponseEnums.EXPECTATION_FAILED);
        } else if (statusCode == 500) {
            return new MyResponse<>(false,  MyResponseEnums.INTERNAL_SERVER_ERROR);
        } else if (statusCode == 501) {
            return new MyResponse<>(false,  MyResponseEnums.NOT_IMPLEMENTED);
        } else if (statusCode == 502) {
            return new MyResponse<>(false, MyResponseEnums.BAD_GATEWAY);
        } else if (statusCode == 503) {
            return new MyResponse<>(false,  MyResponseEnums.SERVICE_UNAVAILABLE);
        } else if (statusCode == 504) {
            return new MyResponse<>(false,  MyResponseEnums.GATEWAY_TIMEOUT);
        } else if (statusCode == 505) {
            return new MyResponse<>(false,  MyResponseEnums.HTTP_VERSION_NOT_SUPPORTED);
        }

        return new MyResponse<>(false, MyResponseEnums.UNKNOWN_ERROR);
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

}

使用

    @ApiOperation(value = "删除课程")
    @DeleteMapping("/{courseId}")
    public MyResponse deleteCourse(@ApiParam(name = "courseId", value = "课程id", required = true)
                                       @PathVariable String courseId) {
        courseService.removeCourse(courseId);
        return new MyResponse<>(true,  MyResponseEnums.OPERATE_SUCCESS);
    }
  //删除课程
    @Override
    public void removeCourse(String courseId) {
        //1 根据课程id删除小节
        eduVideoService.removeVideoByCourseId(courseId);

        //2 根据课程id删除章节
        chapterService.removeChapterByCourseId(courseId);

        //3 根据课程id删除描述
        courseDescriptionService.removeById(courseId);

        //4 根据课程id删除课程本身
        int result = baseMapper.deleteById(courseId);
        if(result == 0) { //失败返回
            throw new MyRuntimeException(MyResponseEnums.OPERATE_FAILURE);
        }
    }

进阶版

SpringBoot 全局异常处理进阶:使用 @ControllerAdvice 对不同的 Controller 分别捕获异常并处理_因特马的博客-CSDN博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值