Java错误码结构案例统一

wen java案例 26

本文目录导读:

Java错误码结构案例统一

  1. 案例一:基础枚举型(最常用,适合中小型项目)
  2. 案例二:可扩展/模块化分级(适合大型微服务项目)
  3. 案例三:国际化支持版
  4. 全局异常捕获(Spring Boot示例)
  5. 总结建议

在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;
    }
}

注意:这里IErrorCodegetCode()返回类型要改为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; 
    }
}

在前端或全局异常处理器中,通过LocaleMessageSource获取真实消息。


全局异常捕获(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;,始终引用枚举

抱歉,评论功能暂时关闭!