feat: 重组为标准 skill 包结构 + 修复 verify.sh bash 3.2 兼容

- 新增 .claude/skills/new-project/SKILL.md(标准 skill 格式)
- install-skill.sh 安装时组装 SKILL.md + verify.sh,单一源无重复
- verify.sh 改用索引数组替代 declare -A,兼容 macOS bash 3.2
- log_pass/log_skip 显式 return 0,避免 set -e 下非 verbose 误退出
- README 改为 skill 优先;补全 copier.yml/scripts/template 入库
This commit is contained in:
baozaotumao
2026-06-01 21:19:45 -07:00
parent 167fa10dde
commit 39fe248f9f
53 changed files with 3744 additions and 55 deletions
+201
View File
@@ -0,0 +1,201 @@
---
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}/pptx` |
| 路径段 | 小写 + 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}/pptx`。路径/字段命名靠 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() / "ppt-gen"
database_url: str = f"sqlite+aiosqlite:///{Path.home()}/ppt-gen/ppt_gen.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 人类可读彩色)+ `~/ppt-gen/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