diff --git a/docs/superpowers/specs/2026-07-02-local-cloud-single-source-data-design.md b/docs/superpowers/specs/2026-07-02-local-cloud-single-source-data-design.md new file mode 100644 index 0000000..8d2774d --- /dev/null +++ b/docs/superpowers/specs/2026-07-02-local-cloud-single-source-data-design.md @@ -0,0 +1,143 @@ +# 本地服务器与云服务器单主数据源设计 + +## 背景 + +当前生产部署使用 `docker-compose.prod.yml`,云服务器的数据写入 `postgres_data`、`redis_data`、`server_data` 三个 volume。本地服务器部署使用 `docker-compose.local.yml`,数据写入 `local_postgres_data`、`local_redis_data`、`local_server_data` 三个独立 volume。 + +这意味着本地服务器和云服务器默认是两套数据。本地服务器新增或修改产品、项目、版本、任务、测试用例、Bug 后,云服务器不会自动更新。 + +用户确认本地和云端都会做备份,因此在线业务不需要两套独立主库。备份用于灾备,在线写入源应保持唯一。 + +## 目标 + +- 云服务器 PostgreSQL 作为唯一业务主数据源。 +- 本地服务器访问和修改的业务数据与云服务器一致。 +- 本地服务器上的写入立即落到云端主库,云端打开后能看到同一份数据。 +- 保留本地服务器备份能力,但默认不把本地 PostgreSQL 当作业务写入库。 +- 避免双主同步、定时合并、手工覆盖等高风险机制。 + +## 非目标 + +- 不做本地数据库和云端数据库的双向同步。 +- 不支持离线写入后再自动合并到云端。 +- 不把云端 PostgreSQL 裸露到公网。 +- 不在本次改动中迁移 AI Provider 配置文件到数据库。 + +## 推荐方案 + +采用“云端单主库 + 本地服务器连接云端主库”的模式。 + +本地服务器仍可运行 `web`、`server`、`redis`、`nginx`,但 `server` 的 `DATABASE_URL` 指向云端主库。业务数据通过 NestJS `DataModule` 写入云端 PostgreSQL 的 `app_data` 表。前端仍走同域 `/api/v1`,由本地 Nginx 转发到本地 server;本地 server 再连接云端数据库。 + +```text +本地浏览器 + -> 本地 Nginx /api + -> 本地 NestJS server + -> 云端 PostgreSQL 主库 + +云端浏览器 + -> 云端 Nginx /api + -> 云端 NestJS server + -> 云端 PostgreSQL 主库 +``` + +## 部署配置设计 + +### 1. 新增本地共享主库 env 示例 + +新增 `.env.local-server.cloud-data.example`,用于说明本地服务器连接云端主库时应填写的配置。 + +关键字段: + +- `DATABASE_URL`:云端 PostgreSQL 连接串,推荐通过 VPN、专线、内网穿透或 SSH tunnel 暴露给本地服务器。 +- `NEXT_PUBLIC_API_URL=/api/v1`:本地浏览器仍访问本地 Nginx。 +- `NEXTAUTH_URL`:本地服务器访问地址,例如 `http://192.168.1.50:8080`。 +- `REDIS_URL=redis://redis:6379`:Redis 可继续本地运行,当前业务主数据不依赖 Redis 持久化同步。 + +### 2. 调整本地 Compose 的数据库依赖 + +`docker-compose.local.yml` 当前强制启动并依赖本地 `postgres`。这会让本地部署天然变成另一套业务数据。 + +调整后: + +- `postgres` 改为可选 profile,例如 `profiles: ['local-db']`。 +- `server.depends_on` 不再强依赖 `postgres`,只保留对 `redis` 的健康依赖。 +- 默认本地服务器部署要求显式提供 `DATABASE_URL`。 +- 如果确实要跑完全离线的本地演示库,可以通过 profile 启动本地 Postgres。 + +预期命令: + +```bash +# 本地服务器连接云端主库 +docker compose --env-file .env.local-server.cloud-data -f docker-compose.local.yml up -d + +# 完全本地演示库 +docker compose --env-file .env.local-server -f docker-compose.local.yml --profile local-db up -d +``` + +### 3. 更新部署文档 + +`docs/deployment.md` 增加“本地服务器连接云端主库”章节,明确: + +- 推荐模式是云端单主库。 +- 本地服务器默认不应使用独立业务库。 +- 本地和云端备份是灾备,不是双主同步。 +- 云端数据库连接必须走安全通道。 + +`docs/architecture.md` 更新生产部署边界,说明本地服务器可作为云端主库的第二个应用入口。 + +`docs/decisions.md` 增加设计决策:在线业务数据采用单主数据源,不做双主同步。 + +`docs/workflow.md` 更新部署流程,避免用户把本地服务器误当成独立生产库。 + +## 安全边界 + +推荐连接方式按优先级排序: + +1. VPN 或组网工具,使本地服务器通过私网访问云端数据库。 +2. SSH tunnel,只在本地服务器和云服务器之间建立转发。 +3. 云安全组只允许本地服务器固定公网 IP 访问数据库端口。 + +不推荐: + +- PostgreSQL `5432` 对公网开放给任意来源。 +- 多台服务器共享同一个弱密码数据库账户。 +- 在 Git 中提交真实 `DATABASE_URL`、数据库密码或 AI Key。 + +## 备份策略 + +备份保留两地: + +- 云端定期备份主库。 +- 本地定期拉取云端主库备份或保存导出的快照。 + +备份文件不参与实时业务写入。发生故障时由人工选择某个备份恢复为新的主库,恢复前先停止其他写入入口,避免恢复期间出现新的分叉。 + +## 数据一致性与冲突 + +因为只有云端 PostgreSQL 是主库,本地和云端不会出现数据库级双写冲突。 + +应用层仍保留现有 `app_data` 乐观锁: + +- 多个用户同时修改同一个业务文档时,后端返回 `409 APP_DATA_CONFLICT`。 +- 前端不自动覆盖服务端新版本。 + +该机制继续处理多人同时编辑,而不是处理两套数据库同步。 + +## 验证方式 + +完成实现后需要验证: + +1. 本地服务器启动后,`curl http://localhost:8080/api/v1/config/ai` 返回 200。 +2. 本地服务器创建一条业务数据,例如产品或任务。 +3. 云服务器页面刷新后能看到同一条数据。 +4. 云服务器修改同一条数据后,本地服务器刷新能看到变化。 +5. 停止本地 `postgres` 容器后,本地服务器仍能读写业务数据。 +6. `pnpm deploy:verify` 通过。 +7. 前端和后端类型检查通过。 + +## 风险与处理 + +- 云端主库不可达时,本地服务器不能写业务数据。这是单主设计的预期行为,错误信息应通过现有 API 失败提示暴露。 +- 如果本地仍误启独立 Postgres 且 `DATABASE_URL` 指向它,会再次形成两套数据。文档和 env 示例必须把默认推荐改成云端主库。 +- AI Provider 配置当前仍是 server volume 文件。本地和云端 AI 配置可能不一致;这是本次非目标。后续如果需要,也应把 AI 配置迁入数据库或明确只在云端维护。