Files
2026-moving-helper/docs/design/step-1-alembic-foundation.md
T
tliu93 c42cc2ddb6 docs: add LLM integration design and three-step implementation plan
Add docs/: a bilingual repository brief, plus docs/design/ with the high-level design (Alembic migration foundation, LLM integration, basic AI search) and a self-contained per-step implementation plan (step 1-3).
2026-06-01 13:10:59 +02:00

102 lines
6.7 KiB
Markdown
Raw 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.
# 步骤 1 · Alembic 迁移地基 / Step 1 · Migration Foundation
> **可独立执行 / Self-contained.** 完整背景见设计文档 [`llm-integration-design.md`](./llm-integration-design.md) §3;跨步骤约定见 [`implementation-plan.md`](./implementation-plan.md)。
> **前置 / Prerequisite** 无(第一步)/ none.
> **产出 / Output** 一个可独立合入的 PR**不改任何业务 schema**。A mergeable PR with **zero business-schema change**.
---
## 目标 / Goal
引入 Alembic 并**安全接管现有生产库**,schema 一点不改,所有现有测试保持绿。
Introduce Alembic and **safely adopt the existing prod DB**, with zero schema change; all existing tests stay green.
---
## 必要背景 / Essential Context(仅凭本文件即可执行 / enough to execute from this file
- **当前没有 Alembic。** 唯一的"迁移"是 `app/db.py::_sync_sqlite_image_columns()`(启动时缺图片列就 `ALTER TABLE ADD COLUMN`)。
No Alembic today; the only "migration" is the hand-rolled image-column sync in `app/db.py`.
- `app/db.py::init_db()` 在 FastAPI lifespan 启动时被 `create_app()` 调用,现在执行 `Base.metadata.create_all()` + `_sync_sqlite_image_columns()`。相关符号:`Base``engine``SessionLocal``configure_database()`
`init_db()` runs at lifespan startup and currently does `create_all()` + the image-column sync.
- `tests/conftest.py``client` fixture`configure_database(tmp_url)``create_app()`(触发 `init_db`)。每个测试用临时 SQLite,互不污染。
- models 在 `app/models.py``Box` / `Item` / `SubItem` 三张表;每张含 `image_blob`(BLOB) / `image_mime_type` / `image_width` / `image_height`,以及 `created_at` / `updated_at`
- DB URL 来自 `app/config.py::get_settings().database_url`(默认 `sqlite:///./data/app.db`)。
- **生产库**是当年 `create_all` 建的、**已装上千件数据、没有 `alembic_version` 表**。
### 铁律 / The Invariant(不可违背 / non-negotiable
- 所有数据库最终收敛到同一个 `head`。All DBs converge to the same `head`.
- **V1 baseline 必须严格等于"今天的真实 schema"**(三张表 + 现有图片列 + 索引),**不多一列**。新东西放后续 revision。
The V1 baseline must equal **today's actual schema exactly** — nothing more.
- 老库:`stamp V1`(只写版本号,**不建表、不碰数据**)→ `upgrade head`
Existing DB: `stamp V1` (writes only the version row, **no DDL, no data change**) → `upgrade head`.
- 新库:跑 `V1`(真正建表)→ `upgrade head`
Fresh DB: run `V1` (creates tables) → `upgrade head`.
---
## 任务 / Tasks
- [ ] `requirements.txt` 增加 `alembic`(钉一个明确版本 / pin a version)。
- [ ] 初始化 Alembic 工程:`alembic.ini` + `migrations/`(含 `env.py``versions/`)。
- [ ] 配置 `migrations/env.py`
- `target_metadata = app.db.Base.metadata`(确保导入 `app.models` 以注册三张表)。
- `sqlalchemy.url` **从 `app.config.get_settings().database_url` 动态读取**,不写死在 `alembic.ini`
- 对 SQLite 设 `render_as_batch=True`(为未来改列/删列预留 batch 能力)。
- [ ] 生成 **V1 baseline 迁移**=当前 models 的完整建表(`boxes`/`items`/`subitems`,含图片列与索引)。做法:对**空库** `--autogenerate`
Author V1 by autogenerating against an **empty** DB.
- [ ] **验证 baseline**:对一份**生产库副本**跑 `alembic check`,确认**无差异**(印证可安全 `stamp`;SQLite 偶有类型亲和/索引命名假差异,人眼复核)。
Verify with `alembic check` against a **copy of the prod DB** → expect no diff.
- [ ] 新增封装 `app/migrate.py`,导出 `run_migrations(database_url: str)`
- 编程方式构造 Alembic `Config``script_location` 指向打包进镜像的 `migrations/``sqlalchemy.url` = 传入 URL)。
- 用 SQLAlchemy inspector 实现自动认领:
-`alembic_version``command.upgrade(cfg, "head")`
-`alembic_version` 但有 `boxes` 表 → `command.stamp(cfg, "<V1 rev>")``command.upgrade(cfg, "head")`
- 全空 → `command.upgrade(cfg, "head")`
- [ ]`app/db.py::init_db()`:改为调 `run_migrations(resolved_url)`**删除** `_sync_sqlite_image_columns()`Alembic 接管后冗余)。保留 `configure_database()` / engine 装配逻辑。
`init_db()` calls `run_migrations(...)`; **remove** `_sync_sqlite_image_columns()`.
- [ ] `Dockerfile`:加 `COPY alembic.ini .``COPY migrations ./migrations`(否则容器内无迁移脚本)。
- [ ] CI(可选 / optional):`.github/workflows/test.yml` 加一步 `alembic check`,防止 model 与迁移漂移。
---
## 涉及文件 / Files
`requirements.txt``alembic.ini`(新)、`migrations/**`(新)、`app/migrate.py`(新)、`app/db.py``Dockerfile``tests/`、(可选)`.github/workflows/test.yml`
---
## 测试 / Tests
- [ ] 现有 ~83 个测试全绿(它们经 `init_db` 现在改走迁移建表)。
All existing ~83 tests pass (schema now built via migrations through `init_db`).
- [ ] 新增**认领老库**用例:构造一个"有 `boxes` 数据、无 `alembic_version`"的库(可先用 `create_all` 造),调 `run_migrations` 后断言:数据保留、`alembic_version` 到达 `head`、未重复建表报错。
New adoption test: a "has `boxes` data, no `alembic_version`" DB → after `run_migrations`, data preserved and version at `head`.
- [ ] 新增**全新库**用例:空 URL → `run_migrations` 后三张表存在、版本到 `head`
---
## 验收 / Acceptance
- 全新库:从 V1 建表,应用正常起。Fresh DB builds from V1; app starts.
- 模拟老库:自动 `stamp` + `upgrade`**数据无损**。Existing-like DB auto-adopts; data intact.
- 全部测试绿;schema 与本步骤前**逐列一致**(本步不改业务 schema)。
All tests green; schema identical to before (no business-schema change).
---
## 风险与缓解 / Risks & Mitigations
- **baseline 与现状有偏差 → `stamp` 失真。** 缓解:`alembic check` 对生产副本校验 + 人眼复核 SQLite 假差异。
Baseline drift → `alembic check` against a prod copy + manual eyeball.
- **容器内找不到迁移脚本。** 缓解:确认 `Dockerfile``COPY` `alembic.ini``migrations/``script_location` 用绝对/相对镜像 WORKDIR(`/app`) 正确解析。
Migrations missing in image → ensure they're `COPY`-ed and `script_location` resolves under `/app`.
---
## 相关约定 / Conventions(详见 implementation-plan.md
- 不主动 push/commit,除非业主要求。Don't push/commit unless asked.
- 实现与设计若有偏差 → 回写设计文档 §3 与仓库简报 `../repository-brief.md` §10。