Java业务状态码与技术异常需无缝衔接:用枚举定义结构化错误码,异常链承载code并保留cause,全局处理器分离响应(code/msg)与日志(完整堆栈+context),context存订单号等可观测数据。

Java中实现业务状态码与技术底层的无缝衔接,核心在于让异常既是“业务语义载体”,又是“技术根因容器”。不是用状态码掩盖异常,而是用异常链承载状态码——上层看到清晰的 ORDER_PAYMENT_FAILED,下层仍能精准定位到 MySQL Deadlock 或 Feign timeout。
用结构化错误码定义业务意图
状态码必须脱离字符串硬编码和 HTTP 状态码混用。推荐使用枚举统一管理:
- OrderErrorCode.PAYMENT_TIMEOUT("PAY_001", "支付超时,请重试")
- UserErrorCode.PHONE_BOUND("USR_004", "手机号已被绑定")
- 每个枚举值固定 code + message,支持 i18n 占位(如 "用户 {0} 已被禁用")
- Controller 层按 code 映射 HTTP 状态:PAYMENT_TIMEOUT → 408;PHONE_BOUND → 409
构造异常时必须传 cause,保留技术根因
业务异常不是终点,是翻译层。每层包装都需显式传递原始异常:
- DAO 层抛出
DataAccessException→ Service 层封装为BusinessException(PAYMENT_TIMEOUT, "支付失败", e) - 调用第三方 API 抛出
HttpClientErrorException→ 封装时保留其为 cause,不 new 后丢弃 - 避免反模式:
throw new BusinessException("支付失败")(丢失 cause) - 检查所有自定义异常类是否提供
BusinessException(ErrorCode, String, Throwable)构造函数,并正确调用super(message, cause)
全局处理器中分离响应与日志语义
@RestControllerAdvice 不是日志兜底,而是协议转换中枢:
立即学习“Java免费学习笔记(深入)”;
- 响应体只返回前端需要的字段:
{"code":"PAY_001","msg":"支付超时,请重试","timestamp":"2026-06-04T17:56:00Z"} - 日志必须打印完整异常链:
log.error("支付异常[{}], traceId={}", ex.getErrorCode(), traceId, ex);(注意 ex 是第三个参数) - 这样既屏蔽了堆栈细节,又确保 ELK 或 Arthas 能查到 root cause
- 对 NullPointerException、ClassCastException 等系统异常,统一降级为 SYSTEM_ERROR,不暴露技术路径
上下文信息走 context 字段,不拼 message
订单号、用户 ID、请求参数等运行时现场,应存进异常对象的 Map 字段:
- 定义
private final Map<String, Object> context = new HashMap<>(); - 构造时注入:
ex.withContext("orderNo", "ORD20260604001").withContext("userId", 12345) - 日志框架可自动提取 context 做聚合分析;告警规则可配置 “当 orderNo 出现 PAY_001 超过 10 次/分钟” 触发通知
- message 仅用于提示,context 才是可观测性的数据源


















