Files
baozaotumao df48c36422 refactor: 规则中性化——剥离 PPT 业务,统一为订单示范域
把规则从原 ppt-gen 项目泛化为通用脚手架,覆盖 19 个文件:
- ddd/architecture/design-discipline/error-codes/communication-* 等:
  领域术语、错误码、消息示例、扩展点、状态流统一为「订单」示范域
- agent-dag/agent.md/agent-concurrency/agent-llm-*:DAG 节点、配置、并发、
  LLM 适配从「对话→预览→生成→截图→合成」改为「intake→process→validate→finalize」
- backend-db:Session 实体/状态机/ORM/仓储/checkpoint 对齐新流水线
- 移除幻灯片专属依赖与产物:agent 去 playwright/python-pptx,
  Dockerfile 去 Chromium 系统库,.gitignore 去 *.pptx
- 保留合法通用项:ANSI(PTY 处理)、Playwright(E2E 测试工具)、Gemini CLI(真实 provider)

验证:gemini-cli/openai-api 两变体生成退出 0,生成项目零 PPT 残留,
backend/agent 骨架均可 import 运行。
2026-06-01 23:15:05 -07:00

202 lines
9.3 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.
---
paths: ["backend/**"]
---
# backend
FastAPI 服务(port 8000)的技术栈、目录、分层、路由、中间件与配置规范。
## 概览
职责:WebSocket 代理层 + 文件服务 + 会话持久化。
**Backend 不连接任何 LLM**——所有大模型交互都在 Agent。Backend 只做 WebSocket 代理 + 数据持久化。
## 技术栈
| 库 | 用途 |
|----|------|
| FastAPI + uvicorn | Web 框架 + ASGI 服务器 |
| SQLAlchemy 2.x (async) | ORM |
| SQLite | 轻量数据库(单文件,无需独立服务) |
| Alembic | 数据库迁移 |
| loguru | 结构化日志 |
| pydantic-settings | 环境变量管理 |
| websockets | Agent WebSocket 客户端 |
| pytest + httpx + pytest-asyncio | 测试 |
## 包管理(uv
```bash
uv add fastapi uvicorn "sqlalchemy[asyncio]" aiosqlite alembic loguru pydantic-settings websockets pytest httpx pytest-asyncio
uv run python -m src.main # 运行服务
uv run pytest # 运行测试
```
关键文件:`pyproject.toml`(依赖声明)、`uv.lock`(锁文件,必须提交 git)、`alembic.ini`(迁移配置)
## 目录结构
```
backend/
├── alembic.ini # Alembic 入口配置
├── alembic/
│ ├── env.py # 迁移环境(导入所有 Model)
│ └── versions/ # 自动生成的迁移脚本
├── src/
│ ├── main.py # FastAPI app 实例、启动/关闭事件
│ ├── domain/ # 领域层,纯逻辑,零框架/IO 依赖
│ │ ├── entities.py # Session 聚合根(纯 Python dataclass,含状态机方法)
│ │ ├── value_objects.py # SessionStatusStrEnum,含 can_recover 规则)
│ │ └── ports.py # SessionRepository Protocol(仓储端口)
│ ├── routers/
│ │ ├── ws.py # WebSocket 代理 /ws/{session_id}
│ │ └── files.py # 文件下载 + 预览路由
│ ├── services/
│ │ ├── agent_client.py # 连接 Agent WebSocket
│ │ └── session_service.py # 会话用例编排(依赖 SessionRepository 端口)
│ ├── schemas/
│ │ ├── messages.py # Pydantic WebSocket 消息模型
│ │ └── session.py # Pydantic 会话读写 schema(与 ORM 解耦)
│ ├── middleware/
│ │ ├── request_id.py # 每个请求注入唯一 X-Request-Id
│ │ ├── access_log.py # 请求/响应结构化日志
│ │ └── error_handler.py # 全局未捕获异常 → 标准错误响应
│ ├── exceptions/
│ │ ├── base.py # AppException 基类
│ │ └── handlers.py # FastAPI exception_handler 注册
│ ├── db/
│ │ ├── base.py # async_engine、AsyncSessionLocal、Base 声明基类
│ │ ├── models.py # SessionRecord ORM 映射(纯持久化,无业务逻辑)
│ │ └── repositories.py # SQLAlchemySessionRepository(实现 SessionRepository 端口)
│ ├── utils/ # 横切纯函数工具(无业务逻辑、无状态)
│ │ ├── ids.py # request_id / uuid 生成
│ │ └── timing.py # 计时、超时上下文管理器
│ └── core/
│ ├── config.py # Settingspydantic-settings
│ ├── deps.py # FastAPI 依赖注入(get_db、get_session_repo、get_settings
│ └── logging.py # loguru 初始化、sink 配置
└── tests/
├── conftest.py # 测试 DBin-memory SQLite)、app fixture
├── test_session_entity.py # Session 领域实体单元测试(classicist,不 mock
├── test_ws.py
├── test_files.py
└── test_session_service.py
```
## 分层映射(六边形架构)
backend 是支撑域,遵循六边形架构,依赖恒向内:
| 层 | 目录 | 职责 |
|----|------|------|
| 接口适配器 | `routers/` | WS/HTTP 入站适配,仅转译协议 |
| 应用(用例) | `services/` | 会话用例编排,依赖 `SessionRepository` 端口 |
| 领域 | `domain/` | `Session` 聚合根、`SessionStatus` 值对象、`SessionRepository` 端口;纯逻辑零 IO |
| 基础设施 | `db/repositories.py``agent_client` | 实现 `SessionRepository` 端口、出站适配 |
## 路由层规范
- 路由函数只做:参数校验 + 调用 service + 返回响应
- 禁止在路由函数中写业务 if/else
- 所有 DB 操作必须通过 `session_service`,禁止在路由层直接操作 DB
- REST 路由统一挂 `/api/v1` 前缀(下载/预览等);WS 消息带 `v` 字段(见根「协议版本化」)
- 会话记录保留 `DB_RETENTION_DAYS`(默认 30 天),`lifespan` 定时任务软删→硬删过期记录
### RESTful 资源命名规范
| 项 | 规范 |
|----|------|
| 资源 | 名词复数:`/api/v1/sessions``/api/v1/sessions/{session_id}/export` |
| 路径段 | 小写 + kebab-case,不放动词(动作用 HTTP method |
| 方法 | GET 读 / POST 建 / PUT 全量改 / PATCH 局部改 / DELETE 删 |
| JSON 字段 | snake_case |
| 版本 | `/api/v1` 前缀 |
| 状态码 | 语义化 200/201/204/4xx/5xx |
例:`GET /api/v1/sessions/{session_id}/export`。路径/字段命名靠 review + 可选 spectralOpenAPI lint);HTTP 语义靠 review。
### Swagger / OpenAPI 文档
- FastAPI 自动生成 `/docs`Swagger UI)、`/redoc``/openapi.json`
-`.env``ENABLE_API_DOCS` 控制:dev 默认开,prod 关(`docs_url=None` 等)
- Settings 字段 `enable_api_docs: bool = True`,映射环境变量 `ENABLE_API_DOCS`
## 中间件(middleware/
### request_id.py
每个请求(含 WebSocket 握手)注入 `X-Request-Id`,传入 loguru 上下文,在所有日志条目中携带,便于全链路追踪。
### access_log.py
记录每个 HTTP 请求的结构化日志(method、path、status_code、duration_ms、request_id)。WebSocket 连接记录 connect / disconnect 事件。
### error_handler.py
捕获所有未处理异常,返回统一错误结构:
```json
{
"error": {
"code": "INTERNAL_ERROR",
"message": "服务器内部错误",
"request_id": "xxx"
}
}
```
生产环境不暴露堆栈信息;开发环境在日志中完整记录。
异常体系(exceptions/):业务错误必须抛 `AppException` 子类(携带 `code`/`message`/`status_code`),禁止在路由层直接 raise HTTPException。在 `main.py` 注册 `app.add_exception_handler(AppException, app_exception_handler)``app.add_exception_handler(Exception, global_exception_handler)`
## 配置(core/config.py
配置项见 `.env.example`(提交 git 的模板);真实配置放 `.env`gitignore)。`.env` 用 SCREAMING_SNAKESettings 字段用 snake_caseenv 映射(`agent_ws_url``AGENT_WS_URL`)为 pydantic-settings 默认行为,无需 alias_generator。
```python
class Settings(BaseSettings):
env: Literal["dev", "prod"] = "dev"
agent_ws_url: str = "ws://localhost:8001"
work_dir: Path = Path.home() / "app-data"
database_url: str = f"sqlite+aiosqlite:///{Path.home()}/app-data/app.db"
port: int = 8000
log_level: str = "DEBUG"
enable_api_docs: bool = True
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8",
)
```
- 缺失/类型错误在启动时即报错(fail-fast)
- 优先级:环境变量 > `.env` > 默认值
- 敏感配置只从环境读取,`.env` 不提交 git
## WebSocket 代理核心
重连入口(routers/ws.py):
```python
@router.websocket("/ws/{session_id}")
async def ws_proxy(ws: WebSocket, session_id: str, repo: SessionRepository = Depends(get_session_repo)):
await ws.accept()
checkpoint = await session_service.load_checkpoint(repo, session_id)
# 将 checkpoint 信息附在 start_session 消息中转发给 Agent
async with agent_client.connect(session_id, checkpoint=checkpoint) as agent_ws:
await asyncio.gather(forward_to_agent(ws, agent_ws), forward_to_browser(agent_ws, ws))
```
详见 `backend-concurrency.md`(双向转发 + Task 管理)与 `backend-db.md`checkpoint)。
## 日志规范(core/logging.py
loguru,两个 sinkstderrdev 人类可读彩色)+ `{WORK_DIR}/logs/backend.log`(JSON 每行一条,所有环境)。文件 sink 配 `rotation="100 MB"``retention="14 days"``compression="zip"`。级别 `DEBUG`(dev)/`INFO`(prod)。每条日志必须携带 `request_id`loguru contextualize 注入)。禁止 `print()` 与 stdlib `logging`,统一 `from loguru import logger`
## 测试规范
- **领域实体(classicist**`test_session_entity.py` 直接实例化 `Session`,验证状态机方法与不变量,零 mock,零 IO
- **service 层(mockist**`test_session_service.py` mock `SessionRepository` 端口,验证用例编排(状态流转调用顺序)
- **集成(真实 DB**`conftest.py` 提供 in-memory SQLite + `SQLAlchemySessionRepository``test_ws.py` mock Agent WS,验证消息原样转发
- checkpoint 测试:验证不同 status 下重连时返回正确的 `Session` 数据,`can_recover()` 结果符合预期
- 每次 CI 前自动运行 `alembic upgrade head`(针对测试 DB