👋 哈喽!我是 小奏 , 欢迎关注我的公众号【小奏技术】
统一错误码管理平台设计
目标
- 集中管理,本地缓存:错误码由平台统一录入,但SDK必须在本地缓存,绝不能因为错误码平台挂了导致业务服务不可用。
- 无侵入性:SDK接入应尽可能简单(Starter方式),对业务代码改动最小。
- 多语言(i18n)支持:设计之初就要考虑国际化,不同Locale返回不同Message。
- 动态热更新:修改错误文案后,服务无需重启即可生效。
错误码规范设计
推荐格式: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`)
);
自动扫描注册错误码到管理平台
本文为博主原创文章,未经博主允许不得转载