Skip to content

Commit 08f791f

Browse files
authored
feat: standardize response codes to string format across API endpoints (#312)
* feat: standardize response codes to string format across API endpoints * feat: add global exception handling middleware to standardize error responses * feat: implement BusinessException and global exception handling for standardized API error responses * feat: refactor exception handling and standardize API responses with SuccessResponse * feat: refactor exception handling and standardize API responses with SuccessResponse * feat: refactor exception handling and standardize API responses with SuccessResponse * feat: refactor exception handling and standardize API responses with SuccessResponse
1 parent 4a3deaa commit 08f791f

File tree

28 files changed

+1651
-916
lines changed

28 files changed

+1651
-916
lines changed

runtime/datamate-python/app/core/exception.py

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
"""
2+
核心异常处理模块
3+
4+
为应用程序提供统一、优雅的异常处理系统,确保所有 API 响应都符合标准化格式。
5+
6+
## 快速开始
7+
8+
### 1. 注册异常处理器(在 main.py 中)
9+
10+
```python
11+
from app.core.exception import register_exception_handlers, ExceptionHandlingMiddleware
12+
13+
app = FastAPI()
14+
15+
# 注册全局异常捕获中间件(最外层)
16+
app.add_middleware(ExceptionHandlingMiddleware)
17+
18+
# 注册异常处理器
19+
register_exception_handlers(app)
20+
```
21+
22+
### 2. 在代码中抛出业务异常
23+
24+
```python
25+
from app.core.exception import ErrorCodes, BusinessError
26+
27+
# 资源不存在
28+
async def get_user(user_id: str):
29+
user = await db.get_user(user_id)
30+
if not user:
31+
raise BusinessError(ErrorCodes.NOT_FOUND)
32+
return user
33+
34+
# 参数验证失败
35+
async def create_user(name: str):
36+
if not name:
37+
raise BusinessError(ErrorCodes.BAD_REQUEST)
38+
# ...
39+
```
40+
41+
### 3. 返回成功响应
42+
43+
```python
44+
from app.core.exception import SuccessResponse
45+
46+
@router.get("/users/{user_id}")
47+
async def get_user(user_id: str):
48+
user = await service.get_user(user_id)
49+
return SuccessResponse(data=user)
50+
# 返回格式: {"code": "0", "message": "success", "data": {...}}
51+
```
52+
53+
### 4. 使用事务管理
54+
55+
```python
56+
from app.core.exception import transaction, BusinessError, ErrorCodes
57+
58+
@router.post("/users")
59+
async def create_user(request: CreateUserRequest, db: AsyncSession = Depends(get_db)):
60+
async with transaction(db):
61+
# 所有数据库操作
62+
user = User(name=request.name)
63+
db.add(user)
64+
await db.flush()
65+
66+
# 如果抛出异常,事务会自动回滚
67+
if check_duplicate(user.name):
68+
raise BusinessError(ErrorCodes.OPERATION_FAILED)
69+
70+
# 事务已自动提交
71+
return SuccessResponse(data=user)
72+
```
73+
74+
## 异常类型说明
75+
76+
### BusinessError(业务异常)
77+
用于预期的业务错误,如资源不存在、权限不足、参数错误等。
78+
79+
特点:
80+
- 不会记录完整的堆栈跟踪(因为这是预期内的错误)
81+
- 返回对应的 HTTP 状态码(400、404 等)
82+
- 客户端会收到标准化的错误响应
83+
84+
使用场景:
85+
- 资源不存在
86+
- 参数验证失败
87+
- 权限不足
88+
- 业务规则违反
89+
90+
### SystemError(系统异常)
91+
用于意外的系统错误,如数据库错误、网络错误、配置错误等。
92+
93+
特点:
94+
- 记录完整的堆栈跟踪
95+
- 返回 HTTP 500
96+
- 不暴露敏感的系统信息给客户端
97+
98+
使用场景:
99+
- 数据库连接失败
100+
- 网络超时
101+
- 配置错误
102+
- 编程错误
103+
104+
## 错误码定义
105+
106+
所有错误码在 `ErrorCodes` 类中集中定义,遵循规范:`{module}.{sequence}`
107+
108+
### 通用错误码
109+
- `SUCCESS` (0): 操作成功
110+
- `BAD_REQUEST` (common.0001): 请求参数错误
111+
- `NOT_FOUND` (common.0002): 资源不存在
112+
- `FORBIDDEN` (common.0003): 权限不足
113+
- `UNAUTHORIZED` (common.0004): 未授权访问
114+
- `VALIDATION_ERROR` (common.0005): 数据验证失败
115+
116+
### 系统级错误码
117+
- `INTERNAL_ERROR` (system.0001): 服务器内部错误
118+
- `DATABASE_ERROR` (system.0002): 数据库错误
119+
- `NETWORK_ERROR` (system.0003): 网络错误
120+
121+
### 模块错误码
122+
- `annotation.*`: 标注模块相关错误
123+
- `collection.*`: 归集模块相关错误
124+
- `evaluation.*`: 评估模块相关错误
125+
- `generation.*`: 生成模块相关错误
126+
- `rag.*`: RAG 模块相关错误
127+
- `ratio.*`: 配比模块相关错误
128+
129+
## Result 类型(可选的函数式错误处理)
130+
131+
如果你不喜欢使用异常,可以使用 Result 类型进行函数式错误处理:
132+
133+
```python
134+
from app.core.exception import Result, Ok, Err, ErrorCodes
135+
136+
def get_user(user_id: str) -> Result[User]:
137+
user = db.find_user(user_id)
138+
if user:
139+
return Ok(user)
140+
return Err(ErrorCodes.NOT_FOUND)
141+
142+
# 使用结果
143+
result = get_user("123")
144+
if result.is_ok():
145+
user = result.unwrap()
146+
print(f"User: {user.name}")
147+
else:
148+
error = result.unwrap_err()
149+
print(f"Error: {error.message}")
150+
151+
# 链式操作
152+
result = get_user("123")
153+
.map(lambda user: user.name)
154+
.and_then(validate_name)
155+
```
156+
157+
## 响应格式
158+
159+
### 成功响应
160+
```json
161+
{
162+
"code": "0",
163+
"message": "success",
164+
"data": {
165+
"id": 123,
166+
"name": "张三"
167+
}
168+
}
169+
```
170+
171+
### 错误响应
172+
```json
173+
{
174+
"code": "common.0002",
175+
"message": "资源不存在",
176+
"data": null
177+
}
178+
```
179+
180+
## 最佳实践
181+
182+
1. **始终使用业务异常**:对于可预见的业务错误,使用 `BusinessError` 而不是 HTTPException
183+
2. **集中定义错误码**:所有错误码在 `ErrorCodes` 中定义,不要硬编码
184+
3. **提供有用的数据**:在抛出异常时,可以通过 `data` 参数传递额外的错误信息
185+
4. **使用事务管理**:涉及多个数据库操作时,使用 `transaction` 上下文管理器
186+
5. **不要捕获 SystemError**:让系统错误由全局处理器统一处理
187+
188+
## 迁移指南
189+
190+
如果你有旧的异常处理代码,迁移步骤:
191+
192+
1. 删除旧的异常类定义
193+
2. 将 `raise OldException(...)` 替换为 `raise BusinessError(ErrorCodes.XXX)`
194+
3. 移除 try-except 中的异常转换逻辑,让全局处理器处理
195+
4. 更新导入语句:`from app.core.exception import ErrorCodes, BusinessError`
196+
197+
## 测试
198+
199+
可以使用测试端点验证异常处理:
200+
201+
```bash
202+
curl http://localhost:8000/test-success
203+
curl http://localhost:8000/test-business-error
204+
curl http://localhost:8000/test-system-error
205+
```
206+
"""
207+
208+
from .base import BaseError, ErrorCode, SystemError, BusinessError
209+
from .codes import ErrorCodes
210+
from .handlers import (
211+
register_exception_handlers,
212+
ErrorResponse,
213+
SuccessResponse
214+
)
215+
from .middleware import ExceptionHandlingMiddleware
216+
from .result import Result, Ok, Err
217+
from .transaction import transaction, ensure_transaction_rollback
218+
219+
__all__ = [
220+
# 基础异常类
221+
'BaseError',
222+
'ErrorCode',
223+
'SystemError',
224+
'BusinessError',
225+
# 错误码
226+
'ErrorCodes',
227+
# 处理器
228+
'register_exception_handlers',
229+
'ErrorResponse',
230+
'SuccessResponse',
231+
# 中间件
232+
'ExceptionHandlingMiddleware',
233+
# Result 类型
234+
'Result',
235+
'Ok',
236+
'Err',
237+
# 事务管理
238+
'transaction',
239+
'ensure_transaction_rollback',
240+
]
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""
2+
基础异常类和错误码定义
3+
"""
4+
from typing import Any
5+
from dataclasses import dataclass
6+
7+
8+
@dataclass(frozen=True)
9+
class ErrorCode:
10+
"""
11+
不可变的错误码定义
12+
13+
属性:
14+
code: 错误码字符串(如 "annotation.0001")
15+
message: 人类可读的错误消息
16+
http_status: HTTP状态码(业务错误默认为400)
17+
"""
18+
code: str
19+
message: str
20+
http_status: int = 400
21+
22+
def __post_init__(self):
23+
"""验证错误码格式"""
24+
if not isinstance(self.code, str):
25+
raise ValueError(f"错误码必须是字符串,实际类型: {type(self.code)}")
26+
if not self.code:
27+
raise ValueError("错误码不能为空")
28+
29+
30+
class BaseError(Exception):
31+
"""
32+
所有应用异常的基类
33+
34+
所有自定义异常都应该继承此类。
35+
提供自动的错误码和消息处理。
36+
37+
使用示例:
38+
raise BusinessError(ErrorCodes.TASK_NOT_FOUND)
39+
40+
客户端将收到:
41+
{
42+
"code": "annotation.0001",
43+
"message": "任务不存在",
44+
"data": null
45+
}
46+
"""
47+
48+
def __init__(
49+
self,
50+
error_code: ErrorCode,
51+
data: Any = None,
52+
*args: Any
53+
):
54+
"""
55+
使用错误码和可选数据初始化异常
56+
57+
Args:
58+
error_code: ErrorCode 实例
59+
data: 附加错误数据(会包含在响应中)
60+
*args: 额外参数(用于兼容性)
61+
"""
62+
self.error_code = error_code
63+
self.data = data
64+
super().__init__(error_code.message, *args)
65+
66+
@property
67+
def code(self) -> str:
68+
"""获取错误码字符串"""
69+
return self.error_code.code
70+
71+
@property
72+
def message(self) -> str:
73+
"""获取错误消息"""
74+
return self.error_code.message
75+
76+
@property
77+
def http_status(self) -> int:
78+
"""获取HTTP状态码"""
79+
return self.error_code.http_status
80+
81+
def to_dict(self) -> dict:
82+
"""将异常转换为响应字典"""
83+
return {
84+
"code": self.code,
85+
"message": self.message,
86+
"data": self.data
87+
}
88+
89+
90+
class SystemError(BaseError):
91+
"""
92+
系统级异常(意外错误)
93+
94+
用于:
95+
- 数据库错误
96+
- 网络错误
97+
- 配置错误
98+
- 编程错误(bug)
99+
100+
系统错误会记录完整的堆栈跟踪
101+
"""
102+
103+
def __init__(self, error_code: ErrorCode, data: Any = None):
104+
super().__init__(error_code, data)
105+
106+
107+
class BusinessError(BaseError):
108+
"""
109+
业务逻辑异常(预期内的错误)
110+
111+
用于:
112+
- 验证失败
113+
- 资源不存在
114+
- 权限不足
115+
- 业务规则违反
116+
117+
业务错误不记录堆栈跟踪(因为它们是预期的)
118+
"""
119+
120+
def __init__(self, error_code: ErrorCode, data: Any = None):
121+
super().__init__(error_code, data)

0 commit comments

Comments
 (0)