我们在项目中, 有时候会遇到异常抛出的时候, 二springboot自带的异常抛出, 在前后端开发中不遵循restful风格, 会给前端带来不少的麻烦!
原始报错返回:
$ curl http://localhost:8080
{
"timestamp": "2022-04-14T22:30:33.820+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/"
}
ControllerAdvice,是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理
需要配合@ExceptionHandler使用。
当将异常抛到controller时,可以对异常进行统一处理,规定返回的json格式或是跳转到一个错误页面
/**
* 统一异常拦截处理
* 可以针对异常的类型进行捕获,然后返回json信息到前端
*/
@ControllerAdvice
@Log4j2
public class GlobalExceptionHandler {
/**
* @param e:
* @return JSONResult
* @author Ladidol
* @description 其他的系统异常抛出
* @date 2022/4/16 16:01
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public JSONResult error(Exception e) {
//通过日志打印异常
log.error(e.getMessage());
// e.printStackTrace();
//返回相应的json result类
return JSONResult.errorMsg(e.getMessage());
}
/**
* @param e:
* @return JSONResult
* @author Ladidol
* @description 自定义异常抛出
* @date 2022/4/16 16:01
*/
@ExceptionHandler(AppException.class)
@ResponseBody
public JSONResult myException(AppException e) {
log.error(e.getMessage());
return JSONResult.exception(e.getResponseStatusEnum());
}
/**
* @param e:
* @return JSONResult
* @author Ladidol
* @description 方法的参数不合法抛出异常
* @date 2022/4/16 16:02
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public JSONResult methodArgumentNotValid(MethodArgumentNotValidException e) {
log.error(e.getMessage());
BindingResult result = e.getBindingResult();
Map<String, String> errorMap = getErrors(result);
return JSONResult.errorMap(errorMap);
}
//验证前端传过来的参数是否存在错误,同时以json的形式返回给前端,优雅!
public Map<String,String> getErrors(BindingResult result){
Map<String, String> map = new HashMap<String, String>();
List<FieldError> errorList = result.getFieldErrors();
for (FieldError error : errorList) {
map.put(error.getField(), error.getDefaultMessage());
}
return map;
}
}
一般可以用继承了RuntimeExceptino的自定义的异常类来包装异常,
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AppException extends RuntimeException {
/**
* 状态码和状态信息
*/
private ResponseStatusEnum responseStatusEnum;
}
package com.feng.result;
import java.util.Map;
/**
* 自定义响应数据类型
*
* @Package com.feng.utils
* @Description: 自定义响应数据结构
*
*/
public class JSONResult {
// 响应业务状态码
private Integer status;
// 响应消息
private String msg;
// 是否成功
private Boolean success;
// 响应数据,可以是Object,也可以是List或Map等
private Object data;
/**
* 成功返回,带有数据的,直接往OK方法丢data数据即可
* @param data
* @return
*/
public static JSONResult ok(Object data) {
return new JSONResult(data);
}
/**
* 成功返回,不带有数据的,直接调用ok方法,data无须传入(其实就是null)
* @return
*/
public static JSONResult ok() {
return new JSONResult(ResponseStatusEnum.SUCCESS);
}
public JSONResult(Object data) {
this.status = ResponseStatusEnum.SUCCESS.status();
this.msg = ResponseStatusEnum.SUCCESS.msg();
this.success = ResponseStatusEnum.SUCCESS.success();
this.data = data;
}
/**
* 错误返回,直接调用error方法即可,当然也可以在ResponseStatusEnum中自定义错误后再返回也都可以
* @return
*/
public static JSONResult error() {
return new JSONResult(ResponseStatusEnum.FAILED);
}
/**
* 错误返回,map中包含了多条错误信息,可以用于表单验证,把错误统一的全部返回出去
* @param map
* @return
*/
public static JSONResult errorMap(Map map) {
return new JSONResult(ResponseStatusEnum.FAILED, map);
}
/**
* 错误返回,直接返回错误的消息
* @param msg
* @return
*/
public static JSONResult errorMsg(String msg) {
return new JSONResult(ResponseStatusEnum.FAILED, msg);
}
/**
* 错误返回,token异常,一些通用的可以在这里统一定义
* @return
*/
public static JSONResult errorTicket() {
return new JSONResult(ResponseStatusEnum.TICKET_INVALID);
}
/**
* 自定义错误范围,需要传入一个自定义的枚举,可以到[ResponseStatusEnum.java[中自定义后再传入
* @param responseStatus
* @return
*/
public static JSONResult errorCustom(ResponseStatusEnum responseStatus) {
return new JSONResult(responseStatus);
}
public static JSONResult exception(ResponseStatusEnum responseStatus) {
return new JSONResult(responseStatus);
}
public JSONResult(ResponseStatusEnum responseStatus) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
}
public JSONResult(ResponseStatusEnum responseStatus, Object data) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
this.data = data;
}
public JSONResult(ResponseStatusEnum responseStatus, String msg) {
this.status = responseStatus.status();
this.msg = msg;
this.success = responseStatus.success();
}
public JSONResult() {
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
前端接收类:
@Data
public class RegistLogin {
@NotBlank(message="账号不能为空! ")
@Length(min=8,max=11,message="账号长度必须不小于8且不大于11! ")
private String account;
@NotBlank(message="密码不能为空! ")
private String password;
@NotBlank(message="邮箱不能为空! ")
@Email(message="邮箱格式错误! ")
private String email;
}
响应结果枚举类
package com.feng.result;
/**
* 响应结果枚举,用于提供给JSONResult返回给前端的
* 本枚举类中包含了很多的不同的状态码供使用,可以自定义
* 便于更优雅的对状态码进行管理,一目了然
*/
public enum ResponseStatusEnum {
SUCCESS(200, true, "操作成功!"),
FAILED(500, false, "操作失败!"),
// 50x
UN_LOGIN(501,false,"请登录后再继续操作!"),
TICKET_INVALID(502,false,"会话失效,请重新登录!"),
NO_AUTH(503,false,"您的权限不足,无法继续操作!"),
MOBILE_ERROR(504,false,"短信发送失败,请稍后重试!"),
SMS_NEED_WAIT_ERROR(505,false,"短信发送太快啦~请稍后再试!"),
SMS_CODE_ERROR(506,false,"验证码过期或不匹配,请稍后再试!"),
USER_FROZEN(507,false,"用户已被冻结,请联系管理员!"),
USER_UPDATE_ERROR(508,false,"用户信息更新失败,请联系管理员!"),
USER_INACTIVE_ERROR(509,false,"请前往[账号设置]修改信息激活后再进行后续操作!"),
USER_INFO_UPDATED_ERROR(5091,false,"用户信息修改失败!"),
USER_INFO_UPDATED_NICKNAME_EXIST_ERROR(5092,false,"昵称已经存在!"),
USER_INFO_UPDATED_IMOOCNUM_EXIST_ERROR(5092,false,"慕课号已经存在!"),
USER_INFO_CANT_UPDATED_IMOOCNUM_ERROR(5092,false,"慕课号无法修改!"),
FILE_UPLOAD_NULL_ERROR(510,false,"文件不能为空,请选择一个文件再上传!"),
FILE_UPLOAD_FAILD(511,false,"文件上传失败!"),
FILE_FORMATTER_FAILD(512,false,"文件图片格式不支持!"),
FILE_MAX_SIZE_500KB_ERROR(5131,false,"仅支持500kb大小以下的图片上传!"),
FILE_MAX_SIZE_2MB_ERROR(5132,false,"仅支持2MB大小以下的图片上传!"),
FILE_NOT_EXIST_ERROR(514,false,"你所查看的文件不存在!"),
USER_STATUS_ERROR(515,false,"用户状态参数出错!"),
USER_NOT_EXIST_ERROR(516,false,"用户不存在!"),
// 自定义系统级别异常 54x
SYSTEM_INDEX_OUT_OF_BOUNDS(541, false, "系统错误,数组越界!"),
SYSTEM_ARITHMETIC_BY_ZERO(542, false, "系统错误,无法除零!"),
SYSTEM_NULL_POINTER(543, false, "系统错误,空指针!"),
SYSTEM_NUMBER_FORMAT(544, false, "系统错误,数字转换异常!"),
SYSTEM_PARSE(545, false, "系统错误,解析异常!"),
SYSTEM_IO(546, false, "系统错误,IO输入输出异常!"),
SYSTEM_FILE_NOT_FOUND(547, false, "系统错误,文件未找到!"),
SYSTEM_CLASS_CAST(548, false, "系统错误,类型强制转换错误!"),
SYSTEM_PARSER_ERROR(549, false, "系统错误,解析出错!"),
SYSTEM_DATE_PARSER_ERROR(550, false, "系统错误,日期解析出错!"),
// admin 管理系统 56x
ADMIN_USERNAME_NULL_ERROR(561, false, "管理员登录名不能为空!"),
ADMIN_USERNAME_EXIST_ERROR(562, false, "管理员登录名已存在!"),
ADMIN_NAME_NULL_ERROR(563, false, "管理员负责人不能为空!"),
ADMIN_PASSWORD_ERROR(564, false, "密码不能为空后者两次输入不一致!"),
ADMIN_CREATE_ERROR(565, false, "添加管理员失败!"),
ADMIN_PASSWORD_NULL_ERROR(566, false, "密码不能为空!"),
ADMIN_NOT_EXIT_ERROR(567, false, "管理员不存在或密码错误!"),
ADMIN_FACE_NULL_ERROR(568, false, "人脸信息不能为空!"),
ADMIN_FACE_LOGIN_ERROR(569, false, "人脸识别失败,请重试!"),
CATEGORY_EXIST_ERROR(570, false, "文章分类已存在,请换一个分类名!"),
// 媒体中心 相关错误 58x
ARTICLE_COVER_NOT_EXIST_ERROR(580, false, "文章封面不存在,请选择一个!"),
ARTICLE_CATEGORY_NOT_EXIST_ERROR(581, false, "请选择正确的文章领域!"),
ARTICLE_CREATE_ERROR(582, false, "创建文章失败,请重试或联系管理员!"),
ARTICLE_QUERY_PARAMS_ERROR(583, false, "文章列表查询参数错误!"),
ARTICLE_DELETE_ERROR(584, false, "文章删除失败!"),
ARTICLE_WITHDRAW_ERROR(585, false, "文章撤回失败!"),
ARTICLE_REVIEW_ERROR(585, false, "文章审核出错!"),
ARTICLE_ALREADY_READ_ERROR(586, false, "文章重复阅读!"),
// 人脸识别错误代码
FACE_VERIFY_TYPE_ERROR(600, false, "人脸比对验证类型不正确!"),
FACE_VERIFY_LOGIN_ERROR(601, false, "人脸登录失败!"),
// 系统错误,未预期的错误 555
SYSTEM_ERROR(555, false, "系统繁忙,请稍后再试!"),
SYSTEM_OPERATION_ERROR(556, false, "操作失败,请重试或联系管理员"),
SYSTEM_RESPONSE_NO_INFO(557, false, ""),
SYSTEM_ERROR_GLOBAL(558, false, "全局降级:系统繁忙,请稍后再试!"),
SYSTEM_ERROR_FEIGN(559, false, "客户端Feign降级:系统繁忙,请稍后再试!"),
SYSTEM_ERROR_ZUUL(560, false, "请求系统过于繁忙,请稍后再试!"),
// 个人自定错误 6000+
PERSONAL_ERROR(666,false, "你好这里是错误! ");
// 响应业务状态
private Integer status;
// 调用是否成功
private Boolean success;
// 响应消息,可以为成功或者失败的消息
private String msg;
ResponseStatusEnum(Integer status, Boolean success, String msg) {
this.status = status;
this.success = success;
this.msg = msg;
}
public Integer status() {
return status;
}
public Boolean success() {
return success;
}
public String msg() {
return msg;
}
}
用个controller来测试一下:
@RestController
@RequestMapping
public class TestController {
@GetMapping("/test1")
public JSONResult testException(String name) throws Exception{
if(name == null || name.length()==0){
throw new MissingServletRequestParameterException("name","String");
}
return JSONResult.ok(name + ", 你成功了!");
}
@RequestMapping("/test2")
public String testMyException() throws AppException {
//返回一下个人错误!
throw new AppException(PERSONAL_ERROR);
}
@PostMapping("loginPost")
public JSONResult login(@Valid @RequestBody RegistLogin registLogin) throws Exception {
return JSONResult.ok();
}
}
通过接口测试一下异常捕获
http://localhost:8080/test1
没参数name的时候:
成功返回结果:
http://localhost:8080/test2
http://localhost:8080/loginPost
邮箱格式错误:
多种错误测试:
如果是系统上的其他异常抛出的话, 这种异常通常是前端开发的时候会出的异常, 比如请求方式get用成post了等等:
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。 @Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum. @Range(min=10000,max=50000,message=“range.bean.wage”) private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
参考链接:
链接1
欢迎点赞关注哦!
也欢迎到访我的博客!
同样欢迎友链互加!
小小的博客传送门!