DigitalHainan 框架异常处理全生命周期规范助手。覆盖异常类型选择、错误码枚举定义、i18n 国际化配置、异常抛出(throw/工厂方法/参数校验/占位符)、异常捕获与降级处理等完整流程。当用户需要处理异常、定义错误码、编写 try-catch、参数校验、异常类型转换、跨服务异常传递时使用。关键词:异常处理/错误码/枚举/i18n/国际化/throw/catch/降级/参数校验/占位符/BusinessException/BusinessHintException/SystemException/AuthException。

开始异常处理前,请确认以下步骤:
Order、User)src/main/resources/i18n/messages.properties 和 src/main/resources/i18n/messages_en.properties 都同时存在(如不存在则创建)| 异常类型 | 父类 | 日志行为 | 适用场景 |
|---|---|---|---|
BusinessException | AbstractRuntimeException | 记录 error 日志 | 一般业务异常:参数校验失败、业务规则违反、数据不存在 |
BusinessHintException | AbstractRuntimeException | 不记录日志 | 业务提示:需要给用户友好提示但不需要记录错误日志的场景 |
SystemException | AbstractRuntimeException | 记录 error 日志 | 系统级异常:第三方服务调用失败、系统内部错误 |
AuthException | RuntimeException | 记录日志 | 认证/鉴权失败(纯标记异常,由安全框架统一处理) |
框架全局异常处理器(
HttpHandlerExceptionResolverConfiguration)会自动捕获这些异常并转换为统一的BaseResponse响应。
按以下顺序逐步判断,匹配到任一条件即停止:
判断点 1:是否需要给用户提示且不需要记录错误日志?
BusinessHintException判断点 2:是否涉及认证或鉴权失败?
AuthException判断点 3:是否为系统内部错误或第三方服务调用失败?
SystemException判断点 4:其余所有业务逻辑异常场景
BusinessException(参数校验、业务规则、数据不存在等)| 场景描述 | 推荐异常类型 | 原因 |
|---|---|---|
| 参数格式不合法、必填字段缺失 | BusinessException | 业务规则校验,需要记录日志 |
| 根据 ID 查询不到数据 | BusinessException | 数据不存在属于业务异常 |
| 给用户展示友好提示(如"额度不足")且不记录 error 日志 | BusinessHintException | 仅需提示,不需要记录错误日志 |
| 调用第三方 API 超时或失败 | SystemException | 系统级问题,非业务逻辑 |
| Token 过期、无权限访问 | AuthException | 认证/鉴权相关,由安全框架处理 |
| Dubbo/Feign 远程调用失败 | SystemException | 属于外部服务调用失败 |
枚举类需实现 IBusinessExceptionCodeConfiguration 接口。
命名规则:XxxOfBusinessExceptionCodeEnum 或 XxxExceptionCodeEnum
包路径建议:xxx.constants 或 xxx.enums
完整枚举模板代码与 i18n 资源文件定义详见 error-code-definition.md。
| 方法 | 用途 | 是否必须实现 |
|---|---|---|
code() | 返回业务异常状态码,同时作为 i18n key | 是 |
defaultMsg() | 返回默认错误描述,作为 i18n 找不到 key 时的 fallback | 否(默认返回 code()) |
belongHttpStatus() | 指定 HTTP 状态码 | 否(默认 HttpStatus.BAD_REQUEST) |
每个异常枚举需配套创建以下两个文件:
src/main/resources/i18n/messages.propertiessrc/main/resources/i18n/messages_en.properties键名为枚举 code() 返回值,值为对应语言的错误描述。同时提供中英文可确保英文环境不 fallback 到中文。
完整 properties 示例详见 error-code-definition.md。
支持 java.text.MessageFormat 格式({0}、{1} 等)。
定义与使用必须成对出现:在枚举中定义占位符后,抛出时务必通过 args 传入对应数量的实际值,否则占位符无法被替换。
// Step 2:枚举定义占位符
ERR_SIZE_LIMIT("字段 {0} 长度必须在 {1} 到 {2} 之间")
// Step 3:抛出时传入对应数量的 args
throw BusinessException.error(
XxxOfBusinessExceptionCodeEnum.ERR_SIZE_LIMIT,
"description", 10, 200
);
// 中文结果: "字段 description 长度必须在 10 到 200 之间"
// 英文结果: "Field description length must be between 10 and 200"| 场景 | 异常类型 | 推荐方式 | 示例 |
|---|---|---|---|
| 常规业务异常 | BusinessException | BusinessException.error(enum) | throw BusinessException.error(ERR_DATA_NOT_FOUND) |
| 需要 i18n 占位符替换 | BusinessException | BusinessException.error(enum, args...) | throw BusinessException.error(ERR_REQUIRED, "userName") |
| 需要包装底层异常(传递 cause) | BusinessException | new BusinessException(enum, msg, cause) | new BusinessException(ERR_RPC, e.getMessage(), e) |
| 用户提示场景(不记录日志) | BusinessHintException | BusinessHintException.error(enum) | throw BusinessHintException.error(ERR_NO_PERMISSION) |
| 系统级异常 | SystemException | SystemException.error(enum) | throw SystemException.error(ERR_RPC_FAIL) |
| 认证/鉴权失败 | AuthException | new AuthException(msg) | throw new AuthException("Token 已过期") |
模板 A:使用枚举默认消息(配合 i18n)
throw BusinessException.error(XxxOfBusinessExceptionCodeEnum.ERR_DATA_NOT_FOUND);模板 B:带参数 args(i18n 占位符替换)
throw BusinessException.error(
XxxOfBusinessExceptionCodeEnum.ERR_REQUIRED_FIELD_MISSING,
"userName"
);模板 C:多参数占位符替换
throw BusinessException.error(
XxxOfBusinessExceptionCodeEnum.ERR_SIZE_LIMIT,
"description", 10, 200
);模板 D:包装底层异常(需要传递 cause 时)
使用构造函数直接实例化并传递原始异常:
try {
thirdPartyApi.call();
} catch (Exception e) {
throw new BusinessException(
XxxOfBusinessExceptionCodeEnum.ERR_RPC,
e.getMessage(),
e
);
}注:当需要传递
cause时,必须使用new构造函数而非静态工厂方法error(),以确保原始堆栈信息不丢失。
| 场景 | 异常类型(Step 1 决策) | 核心要点 | 参考文档 |
|---|---|---|---|
| 参数校验失败 | BusinessException | dto 判空、字段判空后抛出 BusinessException.error(enum) | exception-throwing.md |
| 数据不存在 | BusinessException | Mapper 查询结果判空后抛出 BusinessException.error(enum) | exception-throwing.md |
| 业务规则校验 | BusinessException | 状态/权限等校验,配合 i18n 占位符传参 | exception-throwing.md |
| 用户提示场景 | BusinessHintException | 使用 BusinessHintException.error(enum),不记录 error 日志 | exception-throwing.md |
| 包装第三方异常 | BusinessException | new BusinessException(enum, msg, cause) 保留原始堆栈 | exception-throwing.md |
| 参数批量校验 | BusinessException | 使用 ValidationUtils.validateBean() 或 validateProperty() | validation-utils.md |
上述场景的完整代码示例详见对应参考文档。
在 Dubbo/Feign 等跨服务调用场景中:
BusinessException,远程调用失败用 SystemException),无需额外处理try-catch,让全局处理器统一接管BusinessHintException 的处理方法中不记录 error 日志| 场景 | 核心要点 | 参考文档 |
|---|---|---|
| 异常降级为外部接口格式 | catch 后返回标准 DTO,提取枚举 code 和 msg | exception-catching.md |
| 异常类型转换 | BusinessException → BusinessHintException,避免公开接口记录过多错误日志 | exception-catching.md |
| 捕获后包装为外部接口格式 | catch 后构建 ExternalApiDTO,传递 code 和 msg | exception-catching.md |
| 跨服务调用异常处理 | Provider 端直接抛异常;Consumer 端让全局处理器接管 | exception-catching.md |
上述场景的完整代码示例详见 exception-catching.md。
业务层不需要手动编写 @ControllerAdvice 或 try-catch 来转换响应格式。框架自动完成以下工作:
code() 作为响应码@AppI18n 切面自动翻译消息BusinessHintException 不记录)BaseResponse 返回给前端| 反模式 | 问题说明 | 正确做法 |
|---|---|---|
所有异常都用 BusinessException | 日志膨胀,用户看到不必要的错误 | 用户提示场景用 BusinessHintException |
| 错误码用魔法字符串 | 难以维护,容易冲突 | 定义枚举实现 IBusinessExceptionCodeConfiguration |
| 捕获后打印日志再 throw | 导致重复日志 | 直接抛出,让全局处理器统一记录 |
使用 e.printStackTrace() | 不规范的日志输出 | 使用 log.error("msg", e) 或交由全局处理器 |
| 异常消息硬编码中文/英文 | 不支持多语言 | 通过 i18n 资源文件管理消息 |
| 只创建中文资源文件 | 英文环境体验差 | 同时提供 messages.properties 和 messages_en.properties |
| args 参数用字符串拼接 | 破坏 i18n 占位符替换 | 将动态值通过 args 传入,资源文件中使用占位符 |
| Do(推荐做法) | Don't(避免做法) |
|---|---|
每个异常枚举配套创建 messages.properties(中文)和 messages_en.properties(英文) | 只创建中文资源文件,导致英文环境 fallback 到中文 |
动态值通过 args 传入,在资源文件中使用 {0}、{1} 占位符 | 使用字符串拼接传入异常消息,破坏 i18n 占位符替换机制 |
枚举的 defaultMsg() 作为 fallback,实际翻译以资源文件为准 | 将异常消息硬编码在代码中 |
使用 java.text.MessageFormat 格式的占位符 {0}、{1} | 使用 Slf4j 的 {} 占位符格式 |
使用 BusinessException.error(enum) 静态工厂方法 | 直接 new BusinessException() 而不传枚举 |
需要包装 cause 时使用 new BusinessException(enum, msg, cause) | 吞掉原始异常,不传递 cause |
BusinessHintException 用于只需提示用户的场景 | 所有场景都使用 BusinessException,导致日志膨胀 |
| 有明确降级逻辑时才捕获异常 | 无意义地捕获后仅打印日志再重新抛出 |
| 直接抛出异常,让全局处理器统一记录日志 | 捕获后打印日志再 throw,导致重复日志 |
| 降级时返回标准 DTO 而非抛出异常 | 静默吞掉异常,不做任何处理 |
| 文档 | 内容 |
|---|---|
| error-code-definition.md | 完整枚举模板与 i18n 配置示例 |
| exception-throwing.md | 完整抛出场景示例(参数校验、数据不存在、业务规则、第三方包装、多参数占位符) |
| exception-catching.md | 完整捕获场景示例(降级、类型转换、外部接口包装) |
| validation-utils.md | ValidationUtils Bean 校验与属性校验 |