140 lines
6.1 KiB
Markdown
140 lines
6.1 KiB
Markdown
# 外网与本地访问地址共享同一数据设计
|
||
|
||
## 背景
|
||
|
||
用户澄清后的真实问题不是“两台服务器之间同步数据”,而是:同一套项目系统通过外网地址访问、通过本地/内网地址访问时,应该看到同一份业务数据。
|
||
|
||
当前仓库同时提供:
|
||
|
||
- `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 演示栈仍然有价值,但它是独立演示环境,不承诺与正式业务数据一致。
|