e052fb778b
- ensure_pg: 无 Docker 时【显式】降级 SQLite(带醒目降级横幅),不再硬失败(反转设计 D4) - ensure_pg: _docker_cli_ok 改用 `docker --version`(纯客户端),修「装了 Docker 但没启动」 被 `docker version`(要连 daemon)误判成「没装 CLI」而绕过自动拉起(需求②) - ensure_pg: 钉死 COMPOSE_PROJECT_NAME=shaguabijia + 清「同名但非本项目」残留容器, 修跨目录/worktree 切换时 container_name 撞名 + pgdata 卷分裂 - run8771.bat: 新增 admin 后端(:8771)启动脚本,跑 app.admin.main:admin_app(run.bat 对等版) - 设计文档 §10 记录以上 D4 反转与三处修复 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
282 lines
20 KiB
Markdown
282 lines
20 KiB
Markdown
# 本地开发切 Docker PostgreSQL —— 设计文档
|
||
|
||
> 让本地开发与测试统一跑在 Docker 化的 PostgreSQL 上,彻底退掉 SQLite。
|
||
> `run.bat` / `run.sh` 启动时自动检测本机 PG,没起就拉起 Docker → 起 PG 容器(镜像缺失先拉),
|
||
> 目的是让开发/大模型能放心用 PG 专有的高效聚合函数,不再为兼容 SQLite 而退化成"取基础数据后内存聚合"。
|
||
>
|
||
> 状态:已定稿(待用户复核)。作者对话日期:2026-07-08。
|
||
> 关联:[postgres-migration.md](../../database/postgres-migration.md)(切引擎完整步骤)、`scripts/init_postgres.py`(生产原生 PG 初始化)。
|
||
> ⚠️ **2026-07-27 增补(见 §10)**:D4 已从「sqlite 硬失败」松为「显式 SQLite 逃生舱」。§1-9 描述的是初版「彻底退掉 SQLite」设计;凡涉及「无 Docker / DATABASE_URL 是 sqlite 时如何处理」,**以 §10 为准**(测试仍只跑 PG 不变)。
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
### 问题
|
||
当前开发环境默认用 SQLite(`DATABASE_URL=sqlite:///./data/app.db`),生产用 PostgreSQL 16。两套引擎并存,导致写数据访问代码时(尤其 `app/admin/repositories/` 的报表聚合)为了"两边都能跑",放弃 PG 专有能力(窗口函数、`FILTER`、`JSONB` 操作符、`GROUPING SETS` 等),改成"先查基础数据、再在 Python 内存里聚合"——既慢又啰嗦。
|
||
|
||
### 目标
|
||
本地开发与测试都跑在 PG 上,SQLite 退出本地开发闭环。之后写 PG 专有 SQL 时:
|
||
- 开发运行时(`run.bat`/`run.sh`)直接连 PG,手动验证可行;
|
||
- `pytest` 也连 PG,PG 专有 SQL 在被测代码路径里也安全,不会因 SQLite 而挂——**这是"双库兼容代码彻底消失"的必要条件**。
|
||
|
||
### 非目标(本期不做)
|
||
- 不动**生产**部署(生产仍是原生 PG16 + systemd,无 Docker;`init_postgres.py` 保持不变)。
|
||
- 不做数据搬迁(MVP 阶段无真实用户数据,详见迁移指南背景假设)。
|
||
- 不接 CI(仓库当前无 `.github/workflows`;若将来加 CI,再单独让 CI 起 PG service)。
|
||
- 不引入 Redis / testcontainers / 连接池中间件。
|
||
|
||
---
|
||
|
||
## 2. 决策记录(本次对话已拍板)
|
||
|
||
| # | 决策点 | 结论 | 理由 |
|
||
|---|---|---|---|
|
||
| D1 | PG 覆盖范围 | **dev 运行 + 测试都切 PG** | 只切运行时的话,被 SQLite 测试覆盖的代码路径(如 `test_cps_admin.py` 覆盖的 `admin/repositories/cps.py`)仍不能用 PG 专有 SQL,双库代码不会真正消失 |
|
||
| D2 | 打包方式 | **方案 A:Compose + `scripts/ensure_pg.py`** | 唯一真正需要定制的部分(启动 Docker 守护进程、等 PG 就绪)集中到一个跨平台模块,`run.bat`/`run.sh`/`conftest.py` 共用;声明式的容器/卷/健康检查交给 Compose |
|
||
| D3 | 宿主端口 | **5432**(与生产/文档一致) | 边界:若本机已有原生 PG 占 5432,`ensure_pg` 会探测到"PG 已在"直接复用它(可能连到不带业务库的实例)——见 §7 风险,文档提示 |
|
||
| D4 | dev 下 `DATABASE_URL` 仍是 sqlite | **硬失败**(打印一行 fix 后非 0 退出) | 彻底断掉 SQLite 退路,符合"让大家都用 PG"的目标 |
|
||
| D5 | dev 数据库密码 | 固定 `shaguabijia_dev_pw`,写进 compose + `.env.example` | 本地容器仅绑 `localhost`,非机密;保证 `.env.example` 复制即可用。生产密码另由 `init_postgres.py` 强随机生成,不复用 |
|
||
| D6 | 改哪些启动脚本 | `run.bat` 与 `run.sh` **都改** | 仓库一贯保持两者同步 |
|
||
| D7 | 镜像 | `postgres:16-alpine` | 对齐生产 PG16;alpine 体积小 |
|
||
|
||
---
|
||
|
||
## 3. 现状(改动前)
|
||
|
||
- **配置**:`app/core/config.py` `DATABASE_URL` 默认 `sqlite:///./data/app.db`,pydantic-settings 从 `.env` 读(环境变量优先级高于 `.env` 文件)。
|
||
- **引擎**:`app/db/session.py` 用 `_is_sqlite = DATABASE_URL.startswith("sqlite")` 分流——SQLite 加 `check_same_thread=False`、不建池;非 SQLite 加 `pool_size=10/max_overflow=20/pool_recycle=3600`。**已天然支持 PG,无需改。**
|
||
- **迁移**:`alembic/env.py` 从 `settings.DATABASE_URL` 读连接串,`render_as_batch` 仅对 sqlite 开;PG 下自动关。**无 psycopg2 硬编码,切 PG 无需改。**
|
||
- **驱动**:`pyproject.toml` 已装 `psycopg[binary]>=3.1`(psycopg3)。URL scheme 必须 `postgresql+psycopg://`(裸 `postgresql://` 会被 SQLAlchemy 路由到未安装的 psycopg2 → ModuleNotFoundError)。
|
||
- **测试**:`tests/conftest.py` 在 import app 前把 `DATABASE_URL` 设成临时文件 SQLite;session 级 autouse fixture 做 `Base.metadata.create_all(engine)` / 结束 `drop_all`(schema 来自 model 而非 alembic,无逐用例 rollback,全会话共享一个库)。
|
||
- **启动脚本**:`run.bat` / `run.sh` 均为:校验 `.env` 存在 → `mkdir data` → `alembic upgrade head` → uvicorn 监听 `0.0.0.0:8770`(`.sh` 带 `--reload --reload-dir app`)。
|
||
- **现有 PG 资产**:`scripts/init_postgres.py`(交互式:建用户/建库/授权/写 .env/跑迁移,面向**已装好的原生 PG**)、`docs/database/postgres-migration.md`(切引擎完整步骤,含 §2 测试切 PG、§2.2 会暴露的真 bug 清单)。
|
||
- **CI**:无(`.github/workflows` 不存在),故测试切 PG 无 CI 联动负担。
|
||
|
||
---
|
||
|
||
## 4. 方案详解
|
||
|
||
### 4.1 新增 `docker-compose.yml`(app-server 根目录)
|
||
```yaml
|
||
services:
|
||
postgres:
|
||
image: postgres:16-alpine
|
||
container_name: shaguabijia-pg
|
||
environment:
|
||
POSTGRES_USER: shaguabijia_app
|
||
POSTGRES_PASSWORD: shaguabijia_dev_pw
|
||
POSTGRES_DB: shaguabijia
|
||
ports:
|
||
- "5432:5432"
|
||
volumes:
|
||
- pgdata:/var/lib/postgresql/data
|
||
- ./docker/initdb:/docker-entrypoint-initdb.d:ro
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U shaguabijia_app -d shaguabijia"]
|
||
interval: 3s
|
||
timeout: 3s
|
||
retries: 20
|
||
volumes:
|
||
pgdata:
|
||
```
|
||
- `POSTGRES_USER` 设定后,该用户以超级用户身份创建并拥有 `POSTGRES_DB`,故能再建测试库。
|
||
- 首启 initdb 脚本建测试库(见 4.2)。命名卷 `pgdata` 让数据跨重启留存。
|
||
|
||
### 4.2 新增 `docker/initdb/01-create-test-db.sql`
|
||
```sql
|
||
-- 仅在 pgdata 卷首次初始化时执行一次。以 shaguabijia_app(超级用户)连 shaguabijia 库运行。
|
||
CREATE DATABASE shaguabijia_test OWNER shaguabijia_app;
|
||
```
|
||
|
||
### 4.3 新增 `scripts/ensure_pg.py`(纯标准库 + docker CLI,跨平台)
|
||
对外同时暴露**可导入函数** `ensure()`(供 `conftest.py` 直接调)和 **CLI 入口** `if __name__ == "__main__": sys.exit(0 if ensure() else 1)`(供 `run` 以 `python -m scripts.ensure_pg` 跑)。`ensure()` 从 `app.core.config.settings` 读 `DATABASE_URL`,解析 host/port,主流程:
|
||
1. **sqlite 守卫**:若 `DATABASE_URL` 以 `sqlite` 开头 → 打印"dev 已切 PG,请把 .env 的 DATABASE_URL 改成 `postgresql+psycopg://shaguabijia_app:shaguabijia_dev_pw@localhost:5432/shaguabijia`"→ 非 0 退出(D4)。
|
||
2. **TCP 探测** `host:port`(stdlib `socket`,超时 1s)。通 → 打印"✅ PG 已就绪"直接返回(幂等:PG 已在跑时开销≈一次握手)。
|
||
3. 不通 → `docker version` 探 CLI;缺失 → 中文报错"请先安装 Docker Desktop:https://www.docker.com/products/docker-desktop/" → 非 0 退出。
|
||
4. `docker info` 探守护进程;不通 → 按平台启动:
|
||
- Windows:`start "" "%ProgramFiles%\Docker\Docker\Docker Desktop.exe"`(找不到则报错让用户手动开)
|
||
- macOS:`open -a Docker`
|
||
- Linux:不自动 sudo,打印 `sudo systemctl start docker` 让用户执行后重试
|
||
然后轮询 `docker info` 直到就绪或超时(默认 120s,每 3s 一次,打印进度)。
|
||
5. `docker compose up -d`(Compose 在镜像缺失时**自动拉取**,首用拉 alpine ~90MB;有进度输出)。
|
||
6. 轮询 healthcheck(`docker inspect` 的 health 状态)/ TCP 直到 PG 接受连接(默认 60s 超时)。
|
||
7. **幂等确保测试库存在**(兼容"老 pgdata 卷没跑过 initdb"的情况):
|
||
`docker compose exec -T postgres psql -U shaguabijia_app -tc "SELECT 1 FROM pg_database WHERE datname='shaguabijia_test'"`,不存在则 `CREATE DATABASE shaguabijia_test OWNER shaguabijia_app`。
|
||
|
||
失败即清晰中文报错 + 非 0 退出,**全程不回退 SQLite**。所有超时可用环境变量覆盖(如 `ENSURE_PG_DOCKER_TIMEOUT`)。
|
||
|
||
### 4.4 `run.bat` / `run.sh` 接线
|
||
在 `alembic upgrade head` **之前**插一行调用,失败即退出:
|
||
- `run.sh`:`"$PY" -m scripts.ensure_pg`(`set -e` 已在,失败自动退出)
|
||
- `run.bat`:`call "%PY%" -m scripts.ensure_pg` + `if errorlevel 1 exit /b 1`
|
||
|
||
其余逻辑不动(`mkdir data` 保留给 media 等落盘目录)。
|
||
|
||
### 4.5 `.env.example` 默认切 PG
|
||
把
|
||
```ini
|
||
DATABASE_URL=sqlite:///./data/app.db
|
||
```
|
||
改为
|
||
```ini
|
||
# 本地开发/测试统一用 Docker PG(run.bat/run.sh 会自动拉起容器;详见 docs/database/postgres-migration.md §本地 Docker 一键起)。
|
||
# 生产用原生 PG,由 scripts/init_postgres.py 写入强随机密码的连接串。
|
||
DATABASE_URL=postgresql+psycopg://shaguabijia_app:shaguabijia_dev_pw@localhost:5432/shaguabijia
|
||
```
|
||
|
||
### 4.6 `tests/conftest.py` 切 PG
|
||
调整顶部顺序(仍必须在 `import app.*` 之前完成 env 设定):
|
||
1. 设 `os.environ["DATABASE_URL"] = "postgresql+psycopg://shaguabijia_app:shaguabijia_dev_pw@localhost:5432/shaguabijia_test"`(测试库,永不碰 dev 业务库)。
|
||
2. 调 `scripts.ensure_pg.ensure()`(保证容器在 + 测试库在;PG 已在时几乎零开销)。
|
||
3. `import app...`。
|
||
session 级 autouse fixture:改为 **`Base.metadata.drop_all(engine)` → `create_all(engine)`(开头先清干净,防持久卷里上一次跑残留的表/数据)→ yield → 结束 `drop_all`**;删掉临时 SQLite 文件相关代码。
|
||
|
||
> 预期:部分用例会因 PG 的严格性变红(SQLite 宽松、PG 严格),按迁移指南 §2.2 逐个修——常见为:字符串/整数隐式比较、`datetime.utcnow()` naive vs `TIMESTAMPTZ`、事务边界(`current transaction is aborted`)。这既是工作量也是本次改造的**直接收益**(暴露真 bug)。实现阶段需为"跑 pytest 并修红用例"单列步骤。
|
||
|
||
### 4.7 `app/db/session.py`
|
||
**无需改动**——`_is_sqlite` 为假时自动走 PG 池化分支。
|
||
|
||
### 4.8 文档
|
||
- `docs/database/postgres-migration.md` 增一节「本地 Docker 一键起 PG(推荐)」,指向 compose + `ensure_pg`,并说明它替代了 §1.1 的手动 brew/apt 装 PG。
|
||
- `CLAUDE.md` 的 DB 段注明:dev/test = Docker PG(`run` 自动拉起);prod = 原生 PG(`init_postgres.py`)。
|
||
- `scripts/init_postgres.py` 顶部注释补一句"本脚本面向生产原生 PG;本地开发用 docker-compose + scripts/ensure_pg"。
|
||
|
||
---
|
||
|
||
## 5. 连接参数汇总
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 镜像 | `postgres:16-alpine` |
|
||
| 容器名 | `shaguabijia-pg` |
|
||
| 宿主端口 | `5432` |
|
||
| 超级/业务用户 | `shaguabijia_app` |
|
||
| dev 密码 | `shaguabijia_dev_pw`(本地非机密) |
|
||
| 业务库(dev 运行) | `shaguabijia` |
|
||
| 测试库(pytest) | `shaguabijia_test` |
|
||
| dev `DATABASE_URL` | `postgresql+psycopg://shaguabijia_app:shaguabijia_dev_pw@localhost:5432/shaguabijia` |
|
||
| test `DATABASE_URL` | `postgresql+psycopg://shaguabijia_app:shaguabijia_dev_pw@localhost:5432/shaguabijia_test` |
|
||
| 数据持久化 | 命名卷 `pgdata` |
|
||
|
||
---
|
||
|
||
## 6. 失败处理矩阵(无 SQLite 兜底)
|
||
|
||
| 情形 | ensure_pg 行为 |
|
||
|---|---|
|
||
| `DATABASE_URL` 是 sqlite | 打印应改成的 PG 串 → 非 0 退出 |
|
||
| PG 已在跑(TCP 通) | 打印"已就绪" → 返回 0(跳过 docker) |
|
||
| 无 docker CLI | 提示装 Docker Desktop + 官网链接 → 非 0 退出 |
|
||
| docker 守护进程未起 | 尝试按平台启动 Docker Desktop,轮询到就绪;超时则报错 → 非 0 退出 |
|
||
| 镜像缺失 | `docker compose up -d` 自动拉取(不额外处理) |
|
||
| 容器起了但 PG 未 ready | 轮询 healthcheck 到超时;超时报错 → 非 0 退出 |
|
||
| 老 pgdata 卷缺测试库 | 幂等 `CREATE DATABASE shaguabijia_test` |
|
||
|
||
---
|
||
|
||
## 7. 风险与边界
|
||
|
||
- **端口占用(原生 PG 撞 5432)**:D3 选了 5432。若开发机已有原生 PG 监听 5432,step 2 的 TCP 探测会判"PG 已在"并复用它——但那个实例可能没有 `shaguabijia`/`shaguabijia_test` 库或用户,后续 `alembic upgrade head` / 测试会报连不上库或认证失败。**缓解**:文档提示"本机别再单独跑原生 PG";报错信息里提示检查是不是撞了原生 PG。
|
||
- **首次启动慢**:首用需 Docker Desktop 冷启(~30–60s)+ 拉镜像(~数十秒~数分钟,视网络)。`ensure_pg` 全程打印进度,超时可配。
|
||
- **`DATABASE_URL` 环境变量优先级**:pydantic-settings 里 shell 环境变量优先于 `.env`。若开发者 shell 残留旧的 `DATABASE_URL`(如指向 sqlite),会盖过 `.env`。sqlite 守卫(D4)能挡住 sqlite 残留;但若残留的是另一个 PG 串,则以它为准——文档提示。
|
||
- **持久卷脏状态**:测试用 drop_all→create_all 开头清库,避免上次崩溃残留污染;dev 业务库随卷留存(符合预期)。
|
||
- **Docker 未安装/公司网络拉镜像受限**:硬失败并给出明确指引;不提供 SQLite 退路是刻意选择(D4/目标)。
|
||
|
||
---
|
||
|
||
## 8. 验收标准
|
||
|
||
1. 全新机器(装了 Docker Desktop、`.env` 从 `.env.example` 复制)执行 `run.bat`(或 `run.sh`):自动拉起 Docker→起 PG 容器→建库→`alembic upgrade head`→uvicorn 起在 8770,无手动装 PG 步骤。
|
||
2. `docker ps` 见 `shaguabijia-pg` 健康;`psql`/客户端能连 `shaguabijia` 与 `shaguabijia_test` 两个库。
|
||
3. PG 已在跑时再次 `run`,`ensure_pg` 秒过(不重复拉容器)。
|
||
4. `pytest` 连 `shaguabijia_test` 跑;红用例全部修绿(PG 严格性暴露的问题)。
|
||
5. `.env` 的 `DATABASE_URL` 改回 sqlite 时,`run`/`pytest` 硬失败并打印正确的 PG 串。
|
||
6. 能在 `admin/repositories/` 里写一段 PG 专有聚合 SQL(如带 `FILTER (WHERE ...)` 的聚合),`run` 下手动跑通、相应 pytest 也通过——即"双库兼容负担消失"的实证。
|
||
|
||
---
|
||
|
||
## 9. 待实现清单(供 writing-plans 拆解)
|
||
|
||
- [ ] 新增 `docker-compose.yml`
|
||
- [ ] 新增 `docker/initdb/01-create-test-db.sql`
|
||
- [ ] 新增 `scripts/ensure_pg.py`(TCP 探测 / 启 Docker Desktop 轮询 / compose up / 等 healthy / 幂等建测试库 / sqlite 守卫)
|
||
- [ ] `run.sh`、`run.bat` 接入 `ensure_pg`
|
||
- [ ] `.env.example` 的 `DATABASE_URL` 切 PG
|
||
- [ ] `tests/conftest.py` 切 `shaguabijia_test` + 调 `ensure_pg` + fixture 改 drop/create
|
||
- [ ] 跑 `pytest`,按迁移指南 §2.2 修红用例
|
||
- [ ] 文档:`postgres-migration.md` 增「本地 Docker 一键起」节;`CLAUDE.md` DB 段;`init_postgres.py` 注释
|
||
- [ ] `.gitignore` 确认 `data/` 已忽略(compose 用命名卷,不落项目目录,无需额外忽略)
|
||
|
||
---
|
||
|
||
## 10. 增补(2026-07-27):D4 反转 —— 显式 SQLite 逃生舱
|
||
|
||
> 背景:§2 的 D4 定为「dev 下 `DATABASE_URL` 仍是 sqlite → 硬失败」,目的是彻底断掉 SQLite 退路。实践中这对「本机装不了 Docker」的开发者过于刚性——直接被卡死、连跑都跑不起来。本次(2026-07-27 对话)把 D4 从「硬失败」松成「**显式逃生舱**」:工具**从不替你静默切库**,但会在没 Docker 时告诉你怎么手动降级,且降级时每次启动都醒目告警。
|
||
|
||
### 10.1 决策更新
|
||
|
||
| # | 原决策 | 新决策 | 理由 |
|
||
|---|---|---|---|
|
||
| D4′ | dev sqlite URL → 硬失败退出 | **放行 + 每次打印醒目降级横幅**(仍非静默) | 已手动改 `.env`=sqlite = 开发者的显式选择,尊重它;但吼一嗓子防止忘了自己在降级、把 PG 专有 SQL 提交上去 |
|
||
| D8(新) | (无) | 无 docker CLI 时,报错里**追加逃生舱指路**(改 `.env`=sqlite),但仍非 0 退出 | 「显式」的关键:工具不替你切库,只指路;开发者改完 `.env` 再跑一次才真正降级 |
|
||
|
||
**未变**:D1(测试仍只跑 PG)、D2-D3、D5-D7 全部保留。逃生舱**只作用于 `run.sh`/`run.bat` 运行时**;`pytest` 仍写死连 PG 测试库(`conftest.py` 传 PG URL,sqlite 分支根本不触发),没 Docker 就 `raise`、跑不了完整套件——这正是 D1「测试上 PG 才能暴露真 bug」的初衷,刻意不给逃生舱。
|
||
|
||
### 10.2 代码改动(仅 `scripts/ensure_pg.py` 的 `ensure()`)
|
||
|
||
1. **sqlite 分支**(原 `return False`)→ 打印多行降级横幅后 `return True`。横幅点明:PG 专有 SQL/严格类型在此模式**不被验证**、提交前须在有 Docker 的机器上用 PG 复跑、装好 Docker 后把 `DATABASE_URL` 改回 PG 串。
|
||
2. **无 docker CLI 分支**(原仅提示装 Docker + `return False`)→ 追加一句「装不了 Docker?把 `.env` 的 `DATABASE_URL` 改成 `sqlite:///./data/app.db` 可降级运行」;**仍 `return False`**(run 脚本照常退出,开发者需显式改 .env 再跑)。
|
||
3. **常量**:新增 `SQLITE_URL = "sqlite:///./data/app.db"`(逃生舱指路用);`SQLITE_FIX_HINT` 重命名 `PG_URL`(降级横幅"改回 PG"引用)。
|
||
4. 更新模块 docstring 中「全程无 SQLite 兜底」一句,改述为「无 Docker/sqlite URL 时【显式】降级 SQLite(带醒目告警),测试侧不降级」。
|
||
|
||
**其余全不动**:`run.sh`/`run.bat`(sqlite 下 `ensure` 返 True → 照常 `alembic upgrade head` + uvicorn)、`docker-compose.yml`、`app/db/session.py`(SQLite 引擎分支本就保留为 fallback)、`tests/conftest.py`、`.env.example`(默认仍 PG)。
|
||
|
||
### 10.3 改完后行为矩阵(覆盖用户列的 5 场景)
|
||
|
||
| 场景 | `DATABASE_URL` | ensure_pg 行为 |
|
||
|---|---|---|
|
||
| ① 无 Docker | PG(默认) | 报错 + 指逃生舱 → 退出;开发者改 `.env`=sqlite → 再跑 → **放行 + 降级横幅**,alembic/uvicorn 跑 SQLite |
|
||
| ② 有 Docker 未启动 | PG | 启 Docker Desktop → `compose up` → 等 ready → 建测试库(**不变**) |
|
||
| ③ 有 Docker 已启动 | PG | `compose up` → 等 ready(**不变**) |
|
||
| ④ PG 已在跑 | PG | TCP 通 → 秒过跳过 Docker(**不变**) |
|
||
| ⑤ PG 起来后 | 任意 | run 脚本 `alembic upgrade head`(**不变**;SQLite 走 `render_as_batch`) |
|
||
|
||
### 10.4 风险
|
||
|
||
- **降级被忽视**:横幅仅在 `run` 启动时打印一次;若开发者用 IDE 直接起 uvicorn(绕过 run 脚本)则看不到。缓解:横幅足够醒目 + 文档强调;**不**引入 app 启动期重复告警(YAGNI)。
|
||
- **测试无 Docker 跑不了**:刻意保留(D1)。文档提示无 Docker 者:要么装 Docker 跑全量测试,要么只在 CI/有 Docker 的机器上验证 PG 相关改动。
|
||
|
||
### 10.5 Redis 前瞻(不在本次)
|
||
|
||
§2 未涉及 Redis。②③ 场景未来若加 Redis 实例:在 `docker-compose.yml` 增 `redis` 服务即可,`docker compose up -d` 天然带起;仅当启动期有组件依赖 Redis 才需给 `ensure_pg` 加 redis readiness 探测。本次不做,方案对它友好。
|
||
|
||
### 10.6 附带修复:`_docker_cli_ok` 守护进程误判(2026-07-27)
|
||
|
||
诊断「装了 Docker Desktop 却报未检测到 docker」时发现的真 bug:`_docker_cli_ok()` 原用 `docker version`
|
||
判断 CLI 是否存在,但该命令**要连 daemon**,守护进程没起时退非零 → 把「Docker 装了但没启动」
|
||
误判成「没装 CLI」,`ensure()` 直接打印"请安装 Docker Desktop"并 `return False`,**绕过了专为需求②
|
||
写的 `_start_docker_daemon()` 自动拉起逻辑**——需求②(有 Docker 未启动 → 自动启动)因此从未真正生效。
|
||
修复:改用 `docker --version`(纯客户端、不连 daemon、退 0)。`_docker_daemon_ok()` 仍用 `docker info`
|
||
(正确,该检查本就依赖 daemon)。实测机器:Docker Desktop 20.10.12 已装但引擎未起,修复前 `_docker_cli_ok()`
|
||
误报 False,修复后 True。
|
||
|
||
### 10.7 附带修复:固定 compose 项目名 + 清理残留同名容器(2026-07-27)
|
||
|
||
诊断「`docker compose up` 报 `container name "/shaguabijia-pg" already in use`」时发现的又一 bug:compose
|
||
项目名默认取运行目录 basename,在不同目录/worktree(如 `local-dev-postgres-docker` vs `shaguabijia-app-server`)
|
||
之间切换会各自成一个项目;而 `docker-compose.yml` 写死了 `container_name: shaguabijia-pg`(全局唯一名),
|
||
于是新项目 `up` 时要创建同名容器 → 撞上旧项目留下的那个 → 冲突。副作用:`pgdata` 卷也按项目名分裂
|
||
成 `local-dev-postgres-docker_pgdata` / `shaguabijia-app-server_pgdata`,数据被切成两半。
|
||
|
||
修复(均在 `scripts/ensure_pg.py`,`docker-compose.yml` 不动、容器名仍是 `shaguabijia-pg`):
|
||
1. 模块级 `os.environ.setdefault("COMPOSE_PROJECT_NAME", "shaguabijia")` —— 钉死项目名,无论从哪个
|
||
目录/worktree 跑都是同一个项目、同一个卷 `shaguabijia_pgdata`,所有 `docker compose up/exec` 一致。
|
||
2. `_compose_up()` 前置 `_remove_stale_container()`:若存在「同名但不属于本项目」的残留容器,先 `docker rm -f`
|
||
再 up(靠 `docker ps --filter name/label` 判归属;数据在命名卷里,删容器不丢)。旧目录/worktree 留下的
|
||
残留容器就此自动清掉,不需手动干预。
|
||
|
||
影响:本次修复后首跑,旧的 `shaguabijia-pg`(属项目 `local-dev-postgres-docker`)会被自动删除、在项目
|
||
`shaguabijia` 下重建,挂载全新的 `shaguabijia_pgdata`(空库,`alembic upgrade head` 重建表)。旧数据仍留在
|
||
`local-dev-postgres-docker_pgdata` 卷里(未删,可恢复);确认不需要后可 `docker volume rm` 清理两个旧卷。
|