一. 统一捕获异常
统一捕获异常,使用AOP的思想,解决在controller中大量try-catch重复代码。
@RestControllerAdvice
: 贴在类上,@RestControllerAdvice
的增强,同时可以在controller执行前后做一些额外逻辑。
@ExceptionHandler(异常类.class)
:贴在方法上,可捕获指定类型的异常。
二. 使用枚举封装返回的异常信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package io.coderyeah.ymcc.enums;
public enum YmccSystemError { SYSTEM_ERROR("10010", "系统错误,正在加班修理中-_-"), SYSTEM_OAUTH_ERROR("10020", "你没有权限访问!未授权!"), SYSTEM_PARAM_ERROR("10030", "数据格式错误");
private String code; private String message;
YmccSystemError(String code, String message) { this.code = code; this.message = message; }
public String getCode() { return code; }
public String getMessage() { return message; } }
|
三. 自定义业务异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package io.coderyeah.ymcc.exception;
import io.coderyeah.ymcc.enums.YmccSystemError; import lombok.Data; import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true) @Data public class BusinessException extends RuntimeException { private String message; private String code; private YmccSystemError systemError;
public BusinessException(YmccSystemError systemError) { this.systemError = systemError; }
public BusinessException(String message, String code) { this.message = message; this.code = code; }
public BusinessException(String message) { this.message = message; } }
|
全局异常代码:包含三种全局异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package io.coderyeah.ymcc.exception;
import io.coderyeah.ymcc.enums.YmccSystemError; import io.coderyeah.ymcc.result.JSONResult; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletResponse; import java.util.List;
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class) public JSONResult businessException(HttpServletResponse response, BusinessException ex) { log.warn("业务异常:{}", ex.getSystemError().getMessage() + ":" + ex.getSystemError().getCode()); log.warn("错误代码:{}", response.getStatus()); return JSONResult.error(ex.getSystemError()); }
@ExceptionHandler(Exception.class) public JSONResult globalException(HttpServletResponse response, Exception ex) { ex.printStackTrace(); log.warn("全局系统异常:{}", ex.getMessage()); log.warn("错误代码:{}", response.getStatus()); return JSONResult.error(YmccSystemError.SYSTEM_ERROR); }
@ExceptionHandler(MethodArgumentNotValidException.class) public JSONResult parameterException(HttpServletResponse response, MethodArgumentNotValidException ex) { ex.printStackTrace(); final StringBuffer paramInfo = new StringBuffer("数据格式错误:"); final List<ObjectError> errors = ex.getBindingResult().getAllErrors(); errors.forEach(err -> { paramInfo.append(err.getDefaultMessage()).append(";"); }); paramInfo.deleteCharAt(paramInfo.lastIndexOf(";")); log.warn("参数异常:{}", paramInfo.toString()); log.warn("错误代码:{}", response.getStatus()); return JSONResult.error(paramInfo.toString(), YmccSystemError.SYSTEM_PARAM_ERROR.getCode()); }
}
|
统一返回结果类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| package io.coderyeah.ymcc.result;
import io.coderyeah.ymcc.enums.YmccSystemError; import io.coderyeah.ymcc.exception.BusinessException; import lombok.Builder; import lombok.Data;
@Data
public class JSONResult {
private boolean success = true;
private String message = "成功";
private String code = "1000";
private Object data;
public static JSONResult success() { return new JSONResult(); }
public static JSONResult success(Object obj) { JSONResult instance = new JSONResult(); instance.setData(obj); return instance; }
public static JSONResult success(Object obj, String code) { JSONResult instance = new JSONResult(); instance.setCode(code); instance.setData(obj); return instance; }
public static JSONResult error(String message, String code) { JSONResult instance = new JSONResult(); instance.setMessage(message); instance.setSuccess(false); instance.setCode(code); return instance; }
public static JSONResult error() { JSONResult jsonResult = new JSONResult(); jsonResult.setSuccess(false); return jsonResult; }
public static JSONResult error(String message) { return error(message, null); }
public static JSONResult error(YmccSystemError ex) { return error(ex.getMessage(), ex.getCode()); }
public static JSONResult error(BusinessException ex) { JSONResult jsonResult = new JSONResult(); jsonResult.setSuccess(false); jsonResult.setMessage(ex.getMessage()); return jsonResult; } }
|
四.JSR303校验
导入依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.5.10</version> </dependency>
|
Bean Validation 中内置的 constraint
相关注解
Constraint |
详细信息 |
@Null |
被注释的元素必须为 null |
@NotNull |
被注释的元素必须不为 null |
@AssertTrue |
被注释的元素必须为 true |
@AssertFalse |
被注释的元素必须为 false |
@Min(value) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) |
被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) |
被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past |
被注释的元素必须是一个过去的日期 |
@Future |
被注释的元素必须是一个将来的日期 |
@Pattern(value) |
被注释的元素必须符合指定的正则表达式 |
Constraint |
详细信息 |
@Email |
被注释的元素必须是电子邮箱地址 |
@Length |
被注释的字符串的大小必须在指定的范围内 |
@NotEmpty |
被注释的字符串的必须非空 |
@Range |
被注释的元素必须在合适的范围内 |
在参数实体类的字段上注解
1 2 3
| @NotBlank(message = "姓名不允许为空") @TableField("real_name") private String realName;
|
1 2
| @Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号格式错误") private String tel;
|
1 2
| @Email(message = "邮箱格式错误") private String email;
|
开启校验
@Valid 或者 @Validated都可以标识该类需要进行校验,在类上也可以加该注解。
1 2 3 4 5 6 7 8 9
| @RequestMapping(value = "/save", method = RequestMethod.POST) public JSONResult saveOrUpdate(@Valid @RequestBody User user) { if (user.getId() != null) { userService.updateById(user); } else { user.insert(user); } return JSONResult.success(); }
|
五.自定义校验注解
定义校验注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package io.coderyeah.ymcc.anno;
import io.coderyeah.ymcc.validator.PhoneValidator;
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*;
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = PhoneValidator.class) public @interface Phone {
String message() default "无效的手机格式";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {}; }
|
定义校验器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package io.coderyeah.ymcc.validator;
import io.coderyeah.ymcc.anno.Phone;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Matcher; import java.util.regex.Pattern;
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final String PHONE_REGEX = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0-9]))\\d{8}$";
@Override public void initialize(Phone constraintAnnotation) { }
@Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { Pattern p = Pattern.compile(PHONE_REGEX); Matcher m = p.matcher(value); return m.matches(); } }
|
使用校验注解
1 2 3
| @Phone private String tel;
|
springboot项目中如何使用全局异常处理和自定义业务异常