本文目录导读:

在Java项目中,设计统一的错误码结构至关重要,它能帮助开发者快速定位问题、方便前端处理异常、以及形成标准化的API文档。
下面提供几个从简单到复杂的统一错误码结构案例,包含枚举定义、接口规范、异常类和DTO输出。
基础枚举型(最常用,适合中小型项目)
特点:将错误码和消息定义在枚举中,通过接口约束。
定义错误码接口(可选,但推荐)
public interface IErrorCode {
/**
* 错误码
*/
Integer getCode();
/**
* 错误消息
*/
String getMessage();
}
枚举实现
public enum SystemErrorCode implements IErrorCode {
// 通用成功
SUCCESS(10000, "操作成功"),
// 通用系统错误 (5位数字: 2位子系统 + 3位具体错误)
SYSTEM_ERROR(10001, "系统内部异常"),
SYSTEM_BUSY(10002, "系统繁忙,请稍后再试"),
SERVICE_TIMEOUT(10003, "服务超时"),
// 参数校验错误
PARAM_ILLEGAL(20001, "参数不合法"),
PARAM_MISSING(20002, "缺少必要参数"),
PARAM_TYPE_ERROR(20003, "参数类型错误"),
PARAM_FORMAT_ERROR(20004, "参数格式错误"),
// 业务逻辑错误 (订单模块 300xx)
ORDER_NOT_FOUND(30001, "订单不存在"),
ORDER_STATUS_ERROR(30002, "订单状态异常"),
ORDER_DUPLICATE(30003, "订单重复提交"),
// 用户鉴权错误 (400xx)
TOKEN_EXPIRED(40001, "Token已过期"),
TOKEN_INVALID(40002, "Token无效"),
USER_NOT_LOGIN(40003, "用户未登录"),
PERMISSION_DENIED(40004, "权限不足"),
// 外部服务错误 (500xx)
THIRD_PARTY_ERROR(50001, "调用第三方服务异常"),
DATABASE_ERROR(50002, "数据库操作失败"),
REDIS_ERROR(50003, "Redis操作失败");
private final Integer code;
private final String message;
SystemErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
自定义业务异常类
public class BusinessException extends RuntimeException {
private IErrorCode errorCode;
private String detailMessage; // 用于填充具体的错误详情(如 userName 不存在)
public BusinessException(IErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public BusinessException(IErrorCode errorCode, String detailMessage) {
super(errorCode.getMessage() + ":" + detailMessage);
this.errorCode = errorCode;
this.detailMessage = detailMessage;
}
public IErrorCode getErrorCode() {
return errorCode;
}
public Integer getCode() {
return errorCode.getCode();
}
public String getDetailMessage() {
return detailMessage;
}
}
统一响应体(DTO)
@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_NULL) // 空值不返回
public class ApiResult<T> {
private Integer code;
private String message;
private T data;
// 静态工厂方法
public static <T> ApiResult<T> success(T data) {
ApiResult<T> result = new ApiResult<>();
result.code = SystemErrorCode.SUCCESS.getCode();
result.message = SystemErrorCode.SUCCESS.getMessage();
result.data = data;
return result;
}
public static <T> ApiResult<T> error(IErrorCode errorCode) {
ApiResult<T> result = new ApiResult<>();
result.code = errorCode.getCode();
result.message = errorCode.getMessage();
return result;
}
public static <T> ApiResult<T> error(IErrorCode errorCode, String detail) {
ApiResult<T> result = new ApiResult<>();
result.code = errorCode.getCode();
result.message = errorCode.getMessage() + ":" + detail;
return result;
}
public static <T> ApiResult<T> error(int code, String message) {
ApiResult<T> result = new ApiResult<>();
result.code = code;
result.message = message;
return result;
}
}
可扩展/模块化分级(适合大型微服务项目)
思想:不同模块(用户、订单、商品)有独立的错误码段,且支持动态构建。
统一错误码前缀定义
public enum ErrorCodePrefix {
COMMON("COM", "通用"),
USER("USR", "用户模块"),
ORDER("ORD", "订单模块"),
PAYMENT("PAY", "支付模块"),
PRODUCT("PRO", "商品模块");
private final String prefix;
private final String desc;
ErrorCodePrefix(String prefix, String desc) {
this.prefix = prefix;
this.desc = desc;
}
public String getPrefix() {
return prefix;
}
}
模块化错误码枚举
以用户模块为例:
public enum UserErrorCode implements IErrorCode {
USER_REGISTER_FAIL(ErrorCodePrefix.USER, 001, "用户注册失败"),
USER_NOT_FOUND(ErrorCodePrefix.USER, 002, "用户未找到"),
USER_PASSWORD_ERROR(ErrorCodePrefix.USER, 003, "密码错误"),
USER_LOCKED(ErrorCodePrefix.USER, 004, "账户已锁定");
private final String prefix;
private final int seq; // 顺序号
private final String message;
// 最终代号如: USR_001
private final String code;
UserErrorCode(ErrorCodePrefix prefix, int seq, String message) {
this.prefix = prefix.getPrefix();
this.seq = seq;
this.message = message;
// 使用字符串作为错误码,方便阅读
this.code = prefix.getPrefix() + "_" + String.format("%03d", seq);
}
@Override
public String getCode() { // 这里改回 String 型
return code;
}
@Override
public String getMessage() {
return message;
}
}
注意:这里IErrorCode的getCode()返回类型要改为String,或者保留Integer的话,可以映射数字,但字符串前缀更适合大型文档归类。
国际化支持版
扩展:当需要返回多语言时,枚举只存code和i18n key。
public enum I18nErrorCode implements IErrorCode {
SUCCESS(10000, "common.success"),
SYSTEM_ERROR(10001, "common.system.error"),
PARAM_ILLEGAL(20001, "param.illegal");
private final Integer code;
private final String i18nKey;
@Override
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
// 这里可以直接返回 i18nKey,或者由全局异常处理器根据 Locale 转换
return i18nKey;
}
}
在前端或全局异常处理器中,通过Locale从MessageSource获取真实消息。
全局异常捕获(Spring Boot示例)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResult<?> handleBusinessException(BusinessException e) {
return ApiResult.error(e.getErrorCode(), e.getDetailMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult<?> handleValidationException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ":" + error.getDefaultMessage())
.collect(Collectors.joining("; "));
return ApiResult.error(SystemErrorCode.PARAM_ILLEGAL.getCode(), msg);
}
@ExceptionHandler(Exception.class)
public ApiResult<?> handleUnknownException(Exception e) {
// 生产环境建议记录日志,返回通用错误
log.error("未知异常", e);
return ApiResult.error(SystemErrorCode.SYSTEM_ERROR);
}
}
总结建议
| 项目规模 | 推荐结构 | 原因 |
|---|---|---|
| 小项目 / 快速原型 | 案例一(枚举 + Integer code) | 简单直接,无需复杂管理 |
| 中大型项目 | 案例二(模块化字符串编码) | 可读性强,方便按模块归类 |
| 海外多语言项目 | 案例三(i18n key) | 方便统一国际化 |
编码规范建议:
- 成功码建议统一为 10000(避免与前端的 200 混淆)
- 错误码采用 5 位数字:
XXYYY(XX=模块,YYY=具体错误) - 或采用字符串:
MODULE_001 - 永远不要在代码里硬编码数字
return 40001;,始终引用枚举