####异常码的设计和使用

​ 为了更快的确定异常原因,从而快速定位出错地点并解决异常。可以使用异常码,每个异常码都对应一个特定的错误。本项目参考阿里巴巴开发手册,将出错原因大体上分为3类:用户端出错,服务器端出错以及远端第三方组件出错,分别用大写英文字母A、B、C标识,每个类别下使用6位数字来表示出错原因,因而总共可以表示30万种错误。每个大类中根据内容详细程度不同,分为3个级别,级别越高内容越详细。

异常码设计

1
2
3
4
public Interface IErrorCode{
String code;
String 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
/**
* 基础错误码定义
*/
public enum BaseErrorCode implements IErrorCode {

// ========== 一级宏观错误码 客户端错误 ==========
CLIENT_ERROR("A000001", "用户端错误"),

// ========== 二级宏观错误码 用户注册错误 ==========
USER_REGISTER_ERROR("A000100", "用户注册错误"),
USER_NAME_VERIFY_ERROR("A000110", "用户名校验失败"),
USER_NAME_EXIST_ERROR("A000111", "用户名已存在"),
USER_NAME_SENSITIVE_ERROR("A000112", "用户名包含敏感词"),
USER_NAME_SPECIAL_CHARACTER_ERROR("A000113", "用户名包含特殊字符"),
PASSWORD_VERIFY_ERROR("A000120", "密码校验失败"),
PASSWORD_SHORT_ERROR("A000121", "密码长度不够"),
PHONE_VERIFY_ERROR("A000151", "手机格式校验失败"),

// ========== 二级宏观错误码 系统请求缺少幂等Token ==========
IDEMPOTENT_TOKEN_NULL_ERROR("A000200", "幂等Token为空"),
IDEMPOTENT_TOKEN_DELETE_ERROR("A000201", "幂等Token已被使用或失效"),

// ========== 一级宏观错误码 系统执行出错 ==========
SERVICE_ERROR("B000001", "系统执行出错"),
// ========== 二级宏观错误码 系统执行超时 ==========
SERVICE_TIMEOUT_ERROR("B000100", "系统执行超时"),

// ========== 一级宏观错误码 调用第三方服务出错 ==========
REMOTE_ERROR("C000001", "调用第三方服务出错");

private final String code;

private final String message;

BaseErrorCode(String code, String message) {
this.code = code;
this.message = message;
}

@Override
public String code() {
return code;
}

@Override
public String message() {
return message;
}
}

异常的设计和使用

​ 根据异常码的分类,因此产生的异常也分为3类.

异常类设计

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
import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;
import lombok.Getter;
import org.springframework.util.StringUtils;

import java.util.Optional;

/**
* 抽象项目中三类异常体系,客户端异常、服务端异常以及远程服务调用异常
*
* @see ClientException
* @see ServiceException
* @see RemoteException
*/
@Getter
public abstract class AbstractException extends RuntimeException {

public final String errorCode;

public final String errorMessage;

public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable);
this.errorCode = errorCode.code();
this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null).orElse(errorCode.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
import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode;
import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;

/**
* 客户端异常
*/
public class ClientException extends AbstractException {

public ClientException(IErrorCode errorCode) {
this(null, null, errorCode);
}

public ClientException(String message) {
this(message, null, BaseErrorCode.CLIENT_ERROR);
}

public ClientException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}

public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}

@Override
public String toString() {
return "ClientException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}

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
import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode;
import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;

/**
* 远程服务调用异常
*/
public class RemoteException extends AbstractException {

public RemoteException(String message) {
this(message, null, BaseErrorCode.REMOTE_ERROR);
}

public RemoteException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}

public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}

@Override
public String toString() {
return "RemoteException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}
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
import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode;
import com.nageoffer.shortlink.admin.common.convention.errorcode.IErrorCode;

import java.util.Optional;

/**
* 服务端异常
*/
public class ServiceException extends AbstractException {

public ServiceException(String message) {
this(message, null, BaseErrorCode.SERVICE_ERROR);
}

public ServiceException(IErrorCode errorCode) {
this(null, errorCode);
}

public ServiceException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}

public ServiceException(String message, Throwable throwable, IErrorCode errorCode) {
super(Optional.ofNullable(message).orElse(errorCode.message()), throwable, errorCode);
}

@Override
public String toString() {
return "ServiceException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}

​ 在处理异常上,设置全局异常拦截器,统一处理产生的所有异常

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
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.nageoffer.shortlink.admin.common.convention.errorcode.BaseErrorCode;
import com.nageoffer.shortlink.admin.common.convention.exception.AbstractException;
import com.nageoffer.shortlink.admin.common.convention.result.Result;
import com.nageoffer.shortlink.admin.common.convention.result.Results;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Optional;

/**
* 全局异常处理器
*
*/
@Component
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 拦截参数验证异常
*/
@SneakyThrows
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());
String exceptionStr = Optional.ofNullable(firstFieldError)
.map(FieldError::getDefaultMessage)
.orElse(StrUtil.EMPTY);
log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr);
return Results.failure(BaseErrorCode.CLIENT_ERROR.code(), exceptionStr);
}

/**
* 拦截应用内抛出的异常
*/
@ExceptionHandler(value = {AbstractException.class})
public Result abstractException(HttpServletRequest request, AbstractException ex) {
if (ex.getCause() != null) {
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString(), ex.getCause());
return Results.failure(ex);
}
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString());
return Results.failure(ex);
}

/**
* 拦截未捕获异常
*/
@ExceptionHandler(value = Throwable.class)
public Result defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
return Results.failure();
}

private String getUrl(HttpServletRequest request) {
if (StringUtils.isEmpty(request.getQueryString())) {
return request.getRequestURL().toString();
}
return request.getRequestURL().toString() + "?" + request.getQueryString();
}
}

@SneakyThrows注解:可作用于方法和构造函数上,可以帮助捕获异常,简化代码,比如

1
2
3
4
5
6
7
8
public void utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
}

使用该注解后,可以使代码更加简洁。该注解位于Lombok中

1
2
3
4
5
@SneakyThrows
public void utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}

通过本节需要掌握

  1. 异常码的设计和使用
  2. 异常类的设计和使用
  3. 全局异常处理器的设计
  4. @SneakyThrows注解的使用