# 外网与本地访问地址共享同一数据设计

## 背景

用户澄清后的真实问题不是“两台服务器之间同步数据”，而是：同一套项目系统通过外网地址访问、通过本地/内网地址访问时，应该看到同一份业务数据。

当前仓库同时提供：

- `docker-compose.prod.yml`：云服务器生产栈，使用 `postgres_data`、`redis_data`、`server_data`。
- `docker-compose.local.yml`：本地服务器/LAN 栈，使用 `local_postgres_data`、`local_redis_data`、`local_server_data`。

如果为了同一套业务同时启动这两套栈，外网地址和本地地址很可能分别打到不同的 Compose project、不同的 PostgreSQL volume，因此会看到两套数据。

## 目标

- 外网访问地址和本地/内网访问地址进入同一套运行中的服务栈。
- 同一套服务栈只使用一个 PostgreSQL 主数据 volume。
- 用户从任意入口创建或修改产品、项目、版本、任务、测试用例、Bug 后，从另一个入口刷新能看到相同数据。
- 文档明确区分“同一套系统的多个访问入口”和“独立本地演示环境”。
- 避免把这个需求误实现为双向同步或跨服务器数据库复制。

## 非目标

- 不做本地数据库和云端数据库的双向同步。
- 不要求本地服务器连接云服务器数据库。
- 不迁移 AI Provider 配置文件到数据库。
- 不引入后台同步任务、冲突合并队列或数据库复制方案。

## 推荐方案

采用“单部署栈，多访问入口”的模式。

无论用户从外网域名、服务器公网 IP、局域网 IP，还是本机地址访问，都应该进入同一个 Nginx，然后由该 Nginx 转发到同一个 `web` 和 `server`，最终读写同一个 PostgreSQL。

```text
外网地址 / 域名
  -> 同一个 Nginx
  -> 同一个 NestJS server
  -> 同一个 PostgreSQL volume

本地 / 内网地址
  -> 同一个 Nginx
  -> 同一个 NestJS server
  -> 同一个 PostgreSQL volume
```

前端继续使用同域 API：

```env
NEXT_PUBLIC_API_URL=/api/v1
```

这样浏览器从哪个地址打开页面，就向该地址下的 `/api/v1` 发请求。只要这些地址最终落到同一套 Nginx/server，数据天然一致。

## 部署模式

### 1. 生产/正式业务栈

正式业务只运行一套 Compose，例如云服务器上运行 `docker-compose.prod.yml`。

该栈可以同时支持：

- 外网域名：`https://pm.example.com`
- 公网 IP：`http://x.x.x.x`
- 内网 IP：`http://192.168.1.50`
- 本机访问：`http://localhost`

这些访问方式只要都进入同一个 Nginx，就会共享同一份数据。

### 2. 本地/LAN 演示栈

`docker-compose.local.yml` 只能用于一套独立的本地/LAN 演示环境。它默认使用 `local_*` volumes，不会和生产栈共享数据。

文档必须明确：如果用户希望外网地址和本地地址看到同一份正式业务数据，不要同时运行 local 栈和 prod 栈来承载同一套业务。

## 配置设计

### Nginx

当前 `deploy/nginx/default.conf.template` 只有一个 `server` block，并使用：

```nginx
server_name ${SERVER_NAME};
```

因为同一个 Nginx 容器只有这一套 80 端口 server block，即使 Host 不匹配，Nginx 也会把请求落到该默认 server block。因此外网域名和内网 IP 在多数单站点部署中已经可以进入同一套服务。

文档需要补充：

- `SERVER_NAME` 可以保留主域名。
- 如果服务器上存在外层 Nginx、多站点配置或严格 host 路由，需要把外网域名和内网访问名都转发到同一套 Compose Nginx。
- 不要为外网和本地访问分别启动两套 Compose。

### 前端 API

`NEXT_PUBLIC_API_URL` 应保持：

```env
NEXT_PUBLIC_API_URL=/api/v1
```

不要为外网和本地访问分别写成不同绝对 API 地址。绝对地址可能让不同入口打到不同后端，重新制造数据分叉。

### NextAuth URL

`NEXTAUTH_URL` 继续使用主要正式访问地址。当前系统的业务数据读写不依赖浏览器 localStorage 作为主存储；数据一致性的关键是 `/api/v1` 是否落到同一个后端和数据库。

如果后续接入 OAuth 回调，需要再评估多 Host 登录体验，但这不影响本次业务数据一致性设计。

## 文档与脚本调整

需要更新：

- `docs/deployment.md`：新增“同一部署多入口访问”说明；强调外网/内网要进入同一套 Compose。
- `docs/architecture.md`：生产部署边界改为“一个正式业务栈可以有多个访问入口”。
- `docs/decisions.md`：增加决策，外网/本地地址一致性通过同一部署栈保证，不做数据同步。
- `docs/workflow.md`：部署流程提示不要为同一业务同时启动 prod 和 local 两套栈。
- `.env.production.example`：注释说明 `SERVER_NAME` 是主要域名，内网 IP 通常也会命中同一 Nginx；多站点环境需显式路由。
- `.env.local-server.example`：注释说明该文件用于独立本地/LAN 演示库，不会与生产数据一致。
- `scripts/verify-production-deploy.mjs`：增加部署文档关键语句校验，避免后续文档丢失这个约束。

## 验证方式

实现后需要验证：

1. `pnpm deploy:verify` 通过。
2. `pnpm --filter web type-check` 通过。
3. `pnpm --filter server type-check` 通过。
4. 正式栈启动后，外网地址访问 `/api/v1/config/ai` 返回 200。
5. 同一台服务器的本地/内网地址访问 `/api/v1/config/ai` 返回 200。
6. 从外网地址创建一条业务数据后，从本地/内网地址刷新能看到。
7. 从本地/内网地址修改同一条业务数据后，从外网地址刷新能看到。

## 风险与处理

- 如果外网地址和本地地址分别指向不同 Compose project，数据仍会分叉。处理方式是停止其中一套业务栈，只保留一个正式业务栈。
- 如果 `NEXT_PUBLIC_API_URL` 被配置成绝对 URL，不同入口可能绕到不同后端。处理方式是正式同域部署保持 `/api/v1`。
- 如果机器上还有外层 Nginx 或宝塔面板，多 Host 需要全部反代到同一套 Compose Nginx。
- 本地/LAN 演示栈仍然有价值，但它是独立演示环境，不承诺与正式业务数据一致。
