Home
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • JavaSE
  • JVM
  • JUC
  • Netty
  • CPP
  • QT
  • UE
  • Go
  • Gin
  • Gorm
  • HTML
  • CSS
  • JavaScript
  • vue2
  • TypeScript
  • vue3
  • react
  • Spring
  • SpringMVC
  • Mybatis
  • SpringBoot
  • SpringSecurity
  • SpringCloud
  • Mysql
  • Redis
  • 消息中间件
  • RPC
  • 分布式锁
  • 分布式事务
  • 个人博客
  • 弹幕视频平台
  • API网关
  • 售票系统
  • 消息推送平台
  • SaaS短链接系统
  • Linux
  • Docker
  • Git
GitHub (opens new window)
Home
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • JavaSE
  • JVM
  • JUC
  • Netty
  • CPP
  • QT
  • UE
  • Go
  • Gin
  • Gorm
  • HTML
  • CSS
  • JavaScript
  • vue2
  • TypeScript
  • vue3
  • react
  • Spring
  • SpringMVC
  • Mybatis
  • SpringBoot
  • SpringSecurity
  • SpringCloud
  • Mysql
  • Redis
  • 消息中间件
  • RPC
  • 分布式锁
  • 分布式事务
  • 个人博客
  • 弹幕视频平台
  • API网关
  • 售票系统
  • 消息推送平台
  • SaaS短链接系统
  • Linux
  • Docker
  • Git
GitHub (opens new window)
  • 如何设计一个短链系统
  • 新Get的开发技巧
  • 项目通用工具

    • 异常设计
      • 异常码规范
      • 异常码分类
        • 客户端异常
        • 服务端异常
        • 远程调用异常
      • 异常码设计
      • 异常设计
      • 全局异常
    • 全局统一返回实体
    • 用户信息脱敏
  • 用户模块

  • 短链模块

  • 流量风控
  • 如何获取用户IP?
  • SaaS短链接系统
  • 项目通用工具
Nreal
2024-01-03
目录

异常设计

# 异常码规范

错误产生来源 + 四位数字编号

  1. 错误源
    • A:用户,如参数错误,用户安装版本过低,用户支付超时等问题;
    • B:当前系统,往往是业务逻辑出错,或程序健壮性差等问题;
    • C:第三方服务,如 CDN 服务出错,消息投递超时等问题;
  2. 异常码分类:一级宏观错误码、二级宏观错误码、三级详细错误码;

# 异常码分类

# 客户端异常

错误码 中文描述 说明
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

基础错误码定义:

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

# 异常设计

异常体系: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
  • 客户端

    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
新Get的开发技巧
全局统一返回实体

← 新Get的开发技巧 全局统一返回实体→

Theme by Vdoing | Copyright © 2021-2024
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式