数据类
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博客