一. 统一捕获异常

统一捕获异常,使用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;

/**
* @author lqs
* @date 2022/10/19 11:12
*/
// 系统错误异常
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;

/**
* @author lqs
* @date 2022/10/19 11:03
*/
// 自定义业务异常
@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;

/**
* @author lqs
* @date 2022/10/19 11:06
*/
@RestControllerAdvice // controller全局异常处理
@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;

//返回JSON结果
@Data
//建造者模式
//@Builder
public class JSONResult {

private boolean success = true;

private String message = "成功";

//错误码,用来描述错误类型 ,1000 表示么有错误
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. 导入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.5.10</version>
    </dependency>
  2. 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. 在参数实体类的字段上注解

    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;
  1. 开启校验

    @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. 定义校验注解

    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.*;

    /**
    * @author lqs
    * @date 2022/10/19
    */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Constraint(validatedBy = PhoneValidator.class)//此处指定了注解的实现类为PhoneValidator
    public @interface Phone {// 用来校验手机号码的注解

    String message() default "无效的手机格式";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
    }
  2. 定义校验器

    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;

    /**
    * @author lqs
    * @date 2022/10/19
    */
    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();
    }
    }
  3. 使用校验注解

    1
    2
    3
    // 校验手机号
    @Phone
    private String tel;