refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)

配置 SSOT(TOML + .env)
统一错误契约
Auth 与事务边界
Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client
可观测性(OpenTelemetry + LGTM)
This commit is contained in:
Sully
2026-05-22 13:44:50 +08:00
committed by GitHub
parent f09ae248f9
commit 53e0065e3e
298 changed files with 15247 additions and 4344 deletions

View File

@@ -11,11 +11,39 @@ alwaysApply: true
4. **feature 间禁止 import router**:跨 feature 调用通过 service 注入
5. **所有 schema 变更走 Alembic**:禁止直接 DDL
6. **新增 provider 必须实现 port protocol**:不得在 feature 中直接调用 SDK
7. **事务边界repo 不提交service 管事务**`repo.py` 只做 `add/delete/query``commit/rollback` 由 service 或 UoW 统一执行。`get_async_db()` 不自动 commit
7. **事务边界repo 不提交service 管事务**`repo.py` 只做 `add/delete/query``commit/rollback` 由 service 或 Celery task 统一执行。`get_async_db()` 不自动 commit;禁止在 router/repo 中 `await db.commit()` / `session.commit()`
8. **port 边界不可打穿**service 需要厂商增强能力时,必须扩充 port 或定义第二个窄 port禁止直接引用 adapter 扩展方法
9. **quota 是独立 feature**conversation、memoir、payment 如需配额检查,通过注入 `QuotaService`,不得直接 import quota 内部函数
10. **成功响应不强制包装**`core/errors.py` 只统一错误响应格式 `{error_code, message, request_id}`,成功响应直接返回 Pydantic model / FileResponse / 原始结构
## 配置 SSOTTOML + .env
- **Secrets / bootstrap 仅进 `.env`**`DATABASE_URL`、`SECRET_KEY`、各厂商 API key、支付私钥等 → `app/core/config.py` 的 `Settings`
- **产品 / 部署调参仅进 TOML**`api/config/default.toml` + `api/config/{APP_ENV}.toml``development` / `staging` / `production`
- **禁止**在 `Settings` 新增 `chat_*` / `memoir_*` / `memory_*` 等产品字段;`tests/test_settings_allowlist.py` 会拦截 env 反弹
- **禁止**业务代码散落 `os.getenv()`;读配置走既有入口:
- feature 常量:`from app.features.<feature>.constants import chat`(等)
- 运行时默认:`from app.core.runtime_constants import llm_defaults, tts_defaults, ...`
- deploy 开关:`settings.enable_tts` 等(`SettingsFacade` 代理到 TOML `[deploy]`
- 改默认值时 **`default.toml` 与 `app_config_models.py` 必须同步**`tests/test_default_toml_legacy_parity.py` 锁定关键默认行为
- 字段对照与运维说明见 `api/docs/configuration.md`
## 错误处理
- router / service **抛 `AppError` 子类**`BadRequestError`、`AuthenticationError`、`NotFoundError`、feature 内 `AuthError` 等),**禁止** `HTTPException`
- 业务专用 `error_code` 登记在 `app/core/error_codes.py`OpenAPI 通过 `ErrorResponse` 组件文档化
- 429 语义:`QuotaExceededError` vs `RateLimitedError`;勿用裸 `HTTPException(429)`legacy handler 无法区分 quota
- 客户端(`app-expo` / `app-eval-web`)统一用 `parseApiError` 读 `message` / `error_code`(兼容旧 `detail`
## 事务 helpers补充规则 7
- 多步写、需原子提交 → `transactional()` / `transactional_sync()``app/core/db.py`
- 外层事务 active 时、局部失败可独立回滚 → `transactional_nested()` / `transactional_nested_sync()`savepoint
- **同一 session 上连续两次 `transactional()` = 两次独立 commit**WS 分段持久化等刻意为之);不要假设嵌套合并成一个事务
- Celery sync 路径用 `transactional_sync()` + `get_sync_db()`外部副作用SMS、COS、LLM放在 commit 成功之后
## Redis / Celery
- 业务 key`settings.redis_url_resolved`(通常 DB/0
- Celery broker/backend`settings.celery_redis_url_resolved`(通常 DB/1compose 显式 `CELERY_REDIS_URL`
- `REDIS_URL` 使用 DB/15 时必须显式设置 `CELERY_REDIS_URL`(无法 auto +1
## 依赖管理uv
11. **依赖统一用 uv 管理**:禁止直接 `pip install`、禁止手动编辑 `pyproject.toml` 的 `[project.dependencies]` 或 `[dependency-groups]`
12. **新增依赖用 `uv add <pkg>`**dev 依赖用 `uv add --dev <pkg>`,移除用 `uv remove <pkg>`
@@ -23,4 +51,4 @@ alwaysApply: true
14. **安装环境统一用 `uv sync`**:开发环境 `uv sync --dev`,生产环境 `uv sync --no-dev`
15. **运行命令统一用 `uv run`**:如 `uv run pytest`、`uv run alembic upgrade head`、`uv run uvicorn ...`
16. 每次添加新代码时一定要阅读已有部分代码确保符合项目架构patternloguru模式
17. **日志**:业务代码使用 `app.core.logging.get_logger(__name__)`loguru `bind`);禁止在 `app` 包内用 `import logging` 取业务 logger仅第三方 SDK / 适配器层可对标准库 `logging` 做桥接(如 `InterceptHandler`
17. **日志**:业务代码使用 `app.core.logging.get_logger(__name__)`loguru `bind`);禁止在 `app` 包内用 `import logging` 取业务 logger仅第三方 SDK / 适配器层可对标准库 `logging` 做桥接(如 `InterceptHandler`

View File

@@ -0,0 +1,21 @@
---
description: Mobile and eval-web API client error handling and auth refresh
globs: app-expo/src/**/*.{ts,tsx},app-eval-web/src/**/*.{ts,tsx}
alwaysApply: false
---
# API Clientapp-expo / app-eval-web
后端错误体为 `{ error_code, message, request_id }`(旧版可能仍有 `{ detail }`)。
## 错误解析
- **统一使用 `parseApiError`**`app-expo/src/core/api/parseApiError.ts`、`app-eval-web/src/parseApiError.ts`
- 展示给用户 / toast 用返回的 `message`;分支逻辑用 `errorCode`(对应后端 `error_code`
- **禁止**在新代码里直接读 `body.detail` 或假设 FastAPI 默认错误形状
## Token 刷新
- `app-expo` 并发 401 refresh 必须走 `refresh-lock.ts`,避免双端同时 refresh 触发 `REFRESH_TOKEN_REUSE`
- refresh 失败应清 session 并引导重新登录,不要无限重试同一 refresh token
## 类型
- 共享错误 body 形状见各 app 的 `ApiErrorBody` / `types.ts`;与 OpenAPI `ErrorResponse` 对齐

View File

@@ -19,10 +19,23 @@ alwaysApply: false
- 用 `httpx.AsyncClient` + `ASGITransport` 进行异步 HTTP 测试。
- `api/tests/conftest.py` 和 `api/tests/factories.py` 只提供通用基础设施,不要把具体业务测试硬编码进去。
## HTTP 错误契约
- 失败响应断言 **`error_code` + `message`**(及可选 `request_id`),不要断言 FastAPI 旧格式 `{ detail }`。
- OpenAPI / router 级 smoke参考 `tests/test_http_router_error_contract.py`、`tests/test_openapi_error_response.py`。
- Auth / payment / SMS 等 feature 码:断言具体 `error_code`(如 `INVALID_SMS_CODE`、`REFRESH_TOKEN_REUSE`),不只测 status code。
## 配置 / TOML 测试
- TOML 加载测试用 `CONFIG_DIR` 指向 fixture 目录,或 `reload_app_config()`;见 `tests/test_app_config_loader.py`。
- **不要**用 `os.environ["OTEL_ENABLED"]` 等已迁出 Settings 的 env 键驱动应用行为(运行时已读 TOML
- 新增 TOML 默认值时,若影响产品行为,更新 `tests/test_default_toml_legacy_parity.py` 或明确 overlay 意图。
## Test Layering
- HTTP 场景测试:注册、登录、刷新、登出、受保护资源访问、关键资源 CRUD、重要失败分支。
- 纯单元测试:纯函数、规则计算、序列化、适配器错误分支与格式转换。
- Service / 事务边界测试:仅当保护明确业务承诺(如 refresh rotation、SMS 回滚、幂等 ingest且 HTTP 层难以稳定复现时允许;参考 `tests/test_auth_refresh_rotation.py`、`tests/test_db_transactional.py`。
- 手工 / E2EWebSocket 多轮对话、真实短信、Celery + Redis + LLM 编排、支付/对象存储/ASR/图像生成联调。
## Decision Filter