异常设计
# 异常码规范
错误产生来源 + 四位数字编号
- 错误源
- A:用户,如参数错误,用户安装版本过低,用户支付超时等问题;
- B:当前系统,往往是业务逻辑出错,或程序健壮性差等问题;
- C:第三方服务,如 CDN 服务出错,消息投递超时等问题;
- 异常码分类:一级宏观错误码、二级宏观错误码、三级详细错误码;
# 异常码分类
# 客户端异常
错误码 | 中文描述 | 说明 |
---|---|---|
A0001 | 用户端错误 | 一级宏观错误码 |
A0100 | 用户注册错误 | 二级宏观错误码 |
A0101 | 用户未同意隐私协议 | |
A0102 | 注册国家或地区受限 | |
A0110 | 用户名校验失败 | |
A0111 | 用户名已存在 | |
A0112 | 用户名包含敏感词 | |
xxx | xxx | |
A0200 | 用户登录异常 | 二级宏观错误码 |
A02101 | 用户账户不存在 | |
A02102 | 用户密码错误 | |
A02103 | 用户账户已作废 | |
xxx | xxx |
# 服务端异常
错误码 | 中文描述 | 说明 |
---|---|---|
B0001 | 系统执行出错 | 一级宏观错误码 |
B0100 | 系统执行超时 | 二级宏观错误码 |
B0101 | 系统订单处理超时 | |
B0200 | 系统容灾功能被触发 | 二级宏观错误码 |
B0210 | 系统限流 | |
B0220 | 系统功能降级 | |
B0300 | 系统资源异常 | 二级宏观错误码 |
B0310 | 系统资源耗尽 | |
B0311 | 系统磁盘空间耗尽 | |
B0312 | 系统内存耗尽 | |
xxx | xxx |
# 远程调用异常
错误码 | 中文描述 | 说明 |
---|---|---|
C0001 | 调用第三方服务出错 | 一级宏观错误码 |
C0100 | 中间件服务出错 | 二级宏观错误码 |
C0110 | RPC服务出错 | |
C0111 | RPC服务未找到 | |
C0112 | RPC服务未注册 | |
xxx | xxx |
# 异常码设计
规约包:convention/errorcode
public interface IErrorCode {
/**
* 错误码
*/
String code();
/**
* 错误信息
*/
String message();
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
基础错误码定义:
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已被使用或失效"),
// ========== 二级宏观错误码 系统请求操作频繁 ==========
FLOW_LIMIT_ERROR("A000300", "当前系统繁忙,请稍后再试"),
// ========== 一级宏观错误码 系统执行出错 ==========
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;
}
}
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
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
# 异常设计
异常体系:convention/exception
@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
2
3
4
5
6
7
8
9
10
11
12
13
客户端
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服务端
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远程调用
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
# 全局异常
common/web
@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();
}
}
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
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