补充异常相关

This commit is contained in:
zk
2026-07-01 17:47:03 +08:00
parent 547cf21bf2
commit dfe7cca5a4
4 changed files with 265 additions and 27 deletions
+52 -4
View File
@@ -102,10 +102,58 @@ inclusion: manual
## 异常处理
- HTTP 异常使用 `raise HTTPException(status_code=xxx, detail="描述")`
- 简单断言直接使用 Python `assert` 或 `if not ... raise`
- 不要 catch 后吞掉异常,交由全局异常处理器(`exceptions.py`)统一处理
- 全局异常处理器已注册:HTTP异常、验证异常、断言异常、未知异常
### 核心约定
- 统一异常与全局处理器都集中在 `app/core/exceptions.py``main.py` 只调用一次 `register_exception_handlers(app)`
- **自定义异常一律 HTTP 500**,真实业务含义靠响应体里的 `code` 区分(前端拦截器读 `code`
- 抛异常时传一句具体原因(detail)即可,不用记错误码;不传就用类别名兜底
- `raise BizError("不支持的商品")` → msg = `业务异常[不支持的商品]`
- `raise BizError()` → msg = `业务异常`
- **不要** catch 后吞掉异常,交由全局异常处理器统一处理
### 自定义异常类(`app/core/exceptions.py`
业务代码优先抛下列语义化异常,不要直接 `raise HTTPException`
| 异常类 | code | 类别名 | 典型场景 |
|--------|------|--------|----------|
| `ParamError` | 4000 | 参数异常 | 入参缺失/非法、模型档位不支持 |
| `AssertError` | 4100 | 断言异常 | 业务前置条件不满足(由 `Assert` 工具抛出) |
| `BizError` | 4200 | 业务异常 | 资源不存在、限频、验证码、越权、会员校验 |
| `CreditError` | 4300 | 扣费异常 | 余额不足、扣费失败(前端引导充值) |
| `SysError` | 5000 | 系统异常 | 已知内部错误 / 上游模型调用失败(主动抛) |
- 所有自定义异常继承 `AppError`,处理器只在基类上注册,靠继承链自动覆盖全部子类
- 流式(SSE)场景用 `exc.to_event()` 输出 error 事件;上游异常包装用 `SysError.from_exc(exc)`
- 兜底:未建模的异常由 `global_exception_handler` 统一按系统异常(code=5000)返回,dev 环境暴露细节,生产只回通用提示
```python
from app.core.exceptions import BizError, CreditError, ParamError
if not resume:
raise BizError("简历不存在")
if balance < cost:
raise CreditError("积分不足")
if model not in ALLOWED:
raise ParamError(f"不支持的模型档位: {model}")
```
### 业务断言工具(`app/core/asserts.py`
前置条件校验优先用 `Assert` 工具类(风格参考 Spring `Assert`),条件不满足时抛 `AssertError`(code=4100)**不要**再手写 `if not ... raise` 或裸 `assert`
```python
from app.core.asserts import Assert
Assert.not_none(user, "用户不存在")
Assert.has_text(func_code, "功能编码不能为空")
Assert.not_empty(items, "列表不能为空")
Assert.gt(days, 0, "天数必须大于0")
Assert.eq(status, 1, "状态不可用")
```
常用方法:`is_true` / `is_false` / `not_none` / `is_none` / `has_text` / `not_empty` / `gt` / `gte` / `lt` / `lte` / `eq` / `ne`
### HTTPException
- 仅在需要返回特定 HTTP 状态码的场景(如框架层、鉴权 401/403)使用 `raise HTTPException(status_code=xxx, detail="描述")`
- 业务拒绝一律用上面的自定义异常,不要用 `HTTPException` 表达业务错误
## Redis 使用规范
+3 -2
View File
@@ -26,7 +26,8 @@ offerpie_python_ai/
│ ├─ lifespan.py # FastAPI 生命周期管理(启动初始化 DB/Redis,关闭释放资源)
│ ├─ logger.py # Loguru 日志配置(控制台+文件,自动注入 request_id/user_id
│ ├─ middleware.py # 中间件注册(RequestID、JWT鉴权、登录拦截、请求日志、响应统一包装)
│ ├─ exceptions.py # 全局异常处理器(HTTP异常、验证异常、断言异常、未知异常
│ ├─ exceptions.py # 统一异常定义 + 全局异常处理器(AppError/ParamError/BizError/CreditError/AssertError/SysError + HTTP/验证/断言/未知兜底
│ ├─ asserts.py # 业务断言工具类 Assert(风格参考 Spring Assert,条件不满足抛 AssertError code=4100
│ └─ schemas/
│ └─ responses.py # 统一响应模型 StandardResponsecode/msg/data/timestamp/uuid
@@ -103,7 +104,7 @@ offerpie_python_ai/
| 层级 | 主要职责 | 关键类/文件 |
|------|----------|-------------|
| **config** | 统一配置管理,基于 Pydantic Settings,支持 .env 文件加载 | `Settings`(数据库、Redis、LLM供应商、JWT、CORS、日志等全部配置项) |
| **core** | 核心基础设施:数据库连接、Redis连接、鉴权、日志、中间件、异常处理、统一响应 | `database.py``redis.py``auth.py``middleware.py``exceptions.py``logger.py``StandardResponse` |
| **core** | 核心基础设施:数据库连接、Redis连接、鉴权、日志、中间件、异常处理、断言工具、统一响应 | `database.py``redis.py``auth.py``middleware.py``exceptions.py`(统一异常+全局处理器)、`asserts.py`(业务断言工具 `Assert``logger.py``StandardResponse` |
| **ai** | AI 模型管理 + 业务 AI 能力 | `LLM` 枚举(models.py)、`model_config.py`(场景模型配置)、`resume_extractor/`(简历并行提取)、`resume_polisher/`(简历段落润色)、`resume_diagnoser/`(简历诊断)、`skill_gap_analyzer/`(技能差距分析 + 定制简历优化 + Agent 原子化规划 + 单条记录修改/新增)、`job_agent/`(求职助手岗位简历优化)、`nova_chat/`Nova 对话助手,纯对话) |
| **api** | REST API 路由定义 | `health.py`(健康检查)、`resume.py`(简历上传解析 + 段落润色)、`resume_diagnose.py`(简历诊断)、`skill_gap.py`(技能差距分析 + 生成定制简历 + AI对话编辑)、`customize_resume.py`(定制简历查询/修改/回滚)、`job_agent_chat.py`(求职助手岗位简历优化)、`nova_chat.py`Nova 对话助手) |
| **models** | SQLAlchemy ORM 模型,与 Java 端共享同一数据库 | `FuncPermission``UserFuncPermissionStock``UserFuncUsageLog``UserResume``UserResumeEducation`/`Work`/`Internship`/`Project`/`Competition``ResumeDiagnosisReport``ResumeDiagnosisIssue``Job`(只读)、`JobAgentConfig``UserJobCustomizeResume` |