跳到主要内容
👋 哈喽!我是 小奏 , 欢迎关注我的公众号【小奏技术

统一错误码管理平台设计

目标

  1. 集中管理,本地缓存:错误码由平台统一录入,但SDK必须在本地缓存,绝不能因为错误码平台挂了导致业务服务不可用。
  2. 无侵入性:SDK接入应尽可能简单(Starter方式),对业务代码改动最小。
  3. 多语言(i18n)支持:设计之初就要考虑国际化,不同Locale返回不同Message。
  4. 动态热更新:修改错误文案后,服务无需重启即可生效。

错误码规范设计

推荐格式:A-BB-CCC (例如:1001004)

组成部分长度说明示例
错误来源1位/2位区分错误类型1: 系统级/通用错误 (NPE, DB链接失败)2: 业务级错误 (余额不足)
服务/模块2位/3位对应具体微服务01: 用户服务;02: 订单服务
具体编码3位具体错误场景001: 参数为空;002: 记录不存在

java sdk设计

定义统一的错误码接口 (IErrorCode)

public interface IErrorCode {
/**
* 获取错误码
*/
String getCode();

/**
* 获取默认描述(仅供开发人员在代码里看,实际对外展示会走动态多语言平台)
*/
String getDefaultMessage();
}

定义 SDK 基础异常类 (BizException)

@Data
public class BizException extends RuntimeException {

private final String code;
private final Object[] args; // 多语言参数

public BizException(String code, Object... args) {
this.code = code;
this.args = args;
}

public BizException(IErrorCode errorCode, Object... args) {
// 这里可以把 errorCode.getDefaultMessage() 存下来打日志用
super(errorCode.getDefaultMessage());
this.code = errorCode.getCode();
this.args = args;
}

}

全局异常处理

import org.springframework.validation.BindException;
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 org.springframework.dao.DataAccessException;
import org.springframework.context.i18n.LocaleContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.util.Locale;

@RestControllerAdvice
public class GlobalExceptionHandler {

private final ErrorCodeManager errorCodeManager;

// 假设我们预留了几个基础错误码
private static final String PARAM_ERROR_CODE = "400000"; // 参数通用错误码
private static final String DB_ERROR_CODE = "500001"; // 数据库通用错误码
private static final String SYSTEM_ERROR_CODE = "500000"; // 系统通用兜底错误码

public GlobalExceptionHandler(ErrorCodeManager errorCodeManager) {
this.errorCodeManager = errorCodeManager;
}

/**
* 1. 处理自定义业务异常 (BizException)
*/
@ExceptionHandler(BizException.class)
public Result<Void> handleBizException(BizException ex) {
String lang = LocaleContextHolder.getLocale().toLanguageTag();
String messageTemplate = errorCodeManager.getMessage(ex.getCode(), lang);

String finalMessage = messageTemplate;
if (ex.getArgs() != null && ex.getArgs().length > 0) {
finalMessage = MessageFormat.format(messageTemplate, ex.getArgs());
}

// 业务异常通常记 INFO 或 WARN 日志即可
// log.warn("BizException: code={}, msg={}", ex.getCode(), finalMessage);

return Result.fail(ex.getCode(), finalMessage, TraceContext.traceId());
}

/**
* 2. 处理参数校验异常 (Spring @Valid / @Validated 抛出的异常)
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public Result<Void> handleValidationException(Exception ex) {
FieldError fieldError = null;
if (ex instanceof MethodArgumentNotValidException) {
fieldError = ((MethodArgumentNotValidException) ex).getBindingResult().getFieldError();
} else if (ex instanceof BindException) {
fieldError = ((BindException) ex).getBindingResult().getFieldError();
}

// 获取校验框架默认的提示信息 (例如 "@NotBlank(message="用户名不能为空")")
String defaultMsg = (fieldError != null) ? fieldError.getDefaultMessage() : "参数校验失败";

// 我们统一使用预设的参数错误码 400000
return Result.fail(PARAM_ERROR_CODE, defaultMsg, TraceContext.traceId());
}

/**
* 3. 处理数据库异常 (Spring Data / JDBC)
*/
@ExceptionHandler(DataAccessException.class)
public Result<Void> handleDatabaseException(DataAccessException ex) {
// 【关键】数据库异常必须记录 ERROR 日志,并包含原始堆栈,触发监控告警
// log.error("Database Exception Error, TraceId: {}", TraceContext.traceId(), ex);

// 获取多语言的兜底文案,例如:"系统繁忙,请稍后再试"
String lang = LocaleContextHolder.getLocale().toLanguageTag();
String safeMessage = errorCodeManager.getMessage(DB_ERROR_CODE, lang);

// 绝不暴露 SQL 细节给前端
return Result.fail(DB_ERROR_CODE, safeMessage, TraceContext.traceId());
}

/**
* 4. 终极兜底:处理所有未知的 Exception
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleSystemException(Exception ex) {
// 记录严重级别的 ERROR 日志
// log.error("Unknown System Error, TraceId: {}", TraceContext.traceId(), ex);

String lang = LocaleContextHolder.getLocale().toLanguageTag();
String safeMessage = errorCodeManager.getMessage(SYSTEM_ERROR_CODE, lang);

return Result.fail(SYSTEM_ERROR_CODE, safeMessage, TraceContext.traceId());
}
}

业务系统使用

业务方使用 Enum 实现 IErrorCode

public enum UserErrorCode implements IErrorCode {

USER_NOT_FOUND("2010001", "用户不存在"),
PASSWORD_INCORRECT("2010002", "密码错误"),
ORDER_NOT_FOUND("3010001", "订单号 [{0}] 不存在"),
ACCOUNT_LOCKED("2010003", "账号被锁定");

private final String code;
private final String defaultMessage;

UserErrorCode(String code, String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}

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

@Override
public String getDefaultMessage() { return defaultMessage; }
}

异常抛出

if (user == null) {
throw new BizException(UserErrorCode.USER_NOT_FOUND);
}
public void queryOrder(String orderId) {
Order order = orderMapper.findById(orderId);
if (order == null) {
throw new OrderException(OrderErrorCode.ORDER_NOT_FOUND, orderId);
}
}

自定义业务异常

public class OrderException extends BizException {

public OrderException(IErrorCode errorCode, Object... args) {
super(errorCode, args);
}
}

管理平台设计 (The Platform)

管理平台(ErrorCode Center, ECC)负责数据的增删改查和持久化。

数据库设计

CREATE TABLE `error_code` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '服务名,如 user-service',
`code` varchar(32) NOT NULL COMMENT '错误码,如 201001',
`message` varchar(255) NOT NULL COMMENT '默认错误信息',
`type` varchar(20) DEFAULT 'BIZ' COMMENT '类型:SYS-系统, BIZ-业务',
`severity` varchar(20) DEFAULT 'INFO' COMMENT '严重等级',
`solution` text COMMENT '给开发看的排查建议',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`)
);

自动扫描注册错误码到管理平台

本文为博主原创文章,未经博主允许不得转载