Files
Bruno GaoandQwen-Coder 43d1229bd7 M1: Add complete documentation structure and contract specifications
- Add comprehensive docs/ directory with standardized structure
- Include architecture specification and contract documents
- Add prompt templates for development workflows
- Establish M1 deliverables as frozen specifications

Co-authored-by: Qwen-Coder <[email protected]>
2026-02-11 23:41:04 +08:00

122 lines
4.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 异常处理契约 v1.0
## 1. 异常体系
DomainException(基类)
├── 携带 ErrorCode(枚举)
├── 携带 message(面向用户的描述)
└── 可选携带 cause(原始异常)
所有业务异常必须继承 DomainException 并携带 ErrorCode。
不允许直接抛出 RuntimeException / IllegalStateException 等通用异常表达业务规则。
## 2. ErrorCode 组织方式
按模块前缀分组(锁定):
COMMON_VALIDATION_ERROR
COMMON_INTERNAL_ERROR
COMMON_OPTIMISTIC_LOCK_CONFLICT
AUTH_INVALID_CREDENTIALS
AUTH_TOKEN_EXPIRED
AUTH_ACCESS_DENIED
ORDER_NOT_FOUND
ORDER_ALREADY_CANCELLED
ORDER_STATUS_INVALID
...
命名规则:{MODULE}_{SCENE},全大写蛇形。
新增模块时在 ErrorCode 枚举中添加对应前缀组。
## 3. ErrorCode → HTTP Status 映射表
| ErrorCode 前缀/具体值 | HTTP Status | 说明 |
|----------------------|-------------|------|
| COMMON_VALIDATION_ERROR | 400 | 参数校验失败 |
| AUTH_INVALID_CREDENTIALS | 401 | 认证失败 |
| AUTH_TOKEN_EXPIRED | 401 | 令牌过期 |
| AUTH_ACCESS_DENIED | 403 | 无权限 |
| *_NOT_FOUND | 404 | 资源不存在 |
| *_ALREADY_* / *_STATUS_INVALID | 409 | 状态冲突 |
| COMMON_OPTIMISTIC_LOCK_CONFLICT | 409 | 乐观锁冲突 |
| COMMON_INTERNAL_ERROR | 500 | 系统内部错误 |
## 4. 统一错误响应格式
{
"code": 404, // HTTP Status(固定,便于前端/网关)
"errorCode": "ORDER_NOT_FOUND", // 稳定枚举值(便于埋点/告警/统计)
"message": "订单不存在", // 面向用户,可本地化
"traceId": "abc-123-def", // 链路追踪 ID
"timestamp": "2026-02-11T10:30:00Z", // 服务器时间
"details": null // 仅 VALIDATION_ERROR 时填充字段级明细
}
各字段职责:
- code:前端快速判断错误大类(4xx / 5xx)
- errorCode:后端精确定位错误场景,用于日志检索 / 告警规则 / 监控大盘
- message:展示给终端用户
- details:VALIDATION_ERROR 时返回 [{"field": "xxx", "message": "xxx"}]
- 生产环境【禁止】在响应中返回堆栈信息
## 5. 异常转换规则
### 规则 1:异常只在 API 层的 GlobalExceptionHandler 转换为 HTTP 响应
- domain/application 层只抛 DomainException
- infrastructure 层将技术异常包装为 DomainException:
- DataAccessException → COMMON_INTERNAL_ERROR
- OptimisticLockingFailureException → COMMON_OPTIMISTIC_LOCK_CONFLICT
- ConnectException / TimeoutException → COMMON_INTERNAL_ERROR
### 规则 2:禁止吞没异常
- 任何 catch 块必须:要么重新抛出,要么记录 WARN/ERROR 日志
- 空 catch 块会被 SpotBugs 检测并阻断 CI
### 规则 3:参数校验 vs 业务校验
- 参数格式校验:Jakarta Validation(@NotNull, @Size, @Pattern 等),
Controller 层 @Valid/@Validated 拦截,GlobalExceptionHandler 捕获
MethodArgumentNotValidException → 400 + COMMON_VALIDATION_ERROR + details
- 业务规则校验:Domain 层方法内判断并抛出具体 DomainException
---
## ✅ 可自动检查项
| 规则 | 检查方式 |
|------|---------|
| 所有自定义异常继承 DomainException | ArchUnit: classes in ..exception.. that extend RuntimeException should also extend DomainException |
| 禁止空 catch 块 | SpotBugs: DE_MIGHT_IGNORE + REC_CATCH_EXCEPTION |
| GlobalExceptionHandler 存在且标注 @RestControllerAdvice | ArchUnit: 至少有一个类标注 @RestControllerAdvice 且在 ..api.exception.. 包下 |
| Controller 中 @Valid/@Validated 注解存在 | 人工评审 + Prompt 约束 |
## ❌ 典型违规示例
### 违规 1:直接抛 RuntimeException 表达业务规则
// 错误!
if (order == null) {
throw new RuntimeException("订单不存在"); // 无 ErrorCode,GlobalExceptionHandler 只能兜底 500
}
// 正确
if (order == null) {
throw new DomainException(ErrorCode.ORDER_NOT_FOUND);
}
### 违规 2:Infrastructure 层不包装技术异常
// 错误!让 DataAccessException 直接穿透到 API 层
public class OrderPersistenceAdapter implements OrderPersistencePort {
public Order findById(OrderId id, String customerId) {
return repo.findByIdAndCustomerId(id.value(), customerId)
.map(mapper::toDomain)
.orElse(null); // 返回 null,让上层 NPE → 500
}
}
// 正确:包装为领域异常
public Order findById(OrderId id, String customerId) {
return repo.findByIdAndCustomerId(id.value(), customerId)
.map(mapper::toDomain)
.orElseThrow(() -> new DomainException(ErrorCode.ORDER_NOT_FOUND));
}