# README-1Panel —— photo-live 生产部署指南(1Panel 单机双地域) > P5-prep 产物(无凭证前置批):本指南只描述操作步骤与占位值,不含任何真实密钥。 > 目标形态:**cn 服务器**(1Panel:MySQL/Redis/OpenResty + photo-live-cn 编排) > 与 **ov 服务器**(1Panel:MySQL/Redis/OpenResty + photo-live-ov 编排)。 > 同步架构(已从代码核实):sync 独立进程**只跑 cn 侧**,双库直连 + 双 API 调用; > ov 侧 inbox(内容接收)与 emergency(紧急停用)内嵌在 api-ov 进程内。 | 产物 | 说明 | | --- | --- | | `docker/server.Dockerfile` | 单镜像 `photo-live-server` 多角色:api-cn / api-ov / worker / sync / migrate-deploy / seed-admin | | `docker/web.Dockerfile` | `photo-live-web`:nginx 承载 viewer(/)+admin(/admin/)+/v1 反代+静态长缓存 | | `docker-compose.cn.yml` / `docker-compose-ov.yml` | 两地域编排(本目录) | | `.env.production.example-cn` / `-ov` | 变量全量清单(仓库根,复制后填值) | | `RUNBOOK.md` | 发布/迁移/回滚/紧急停用运行手册 | --- ## 1. 镜像导入(两条路径任选) **版本约定**:tag = `git短sha-日期`(如 `12059d9-20260921`),同一 tag 双镜像配套, 回滚即换 tag(见 RUNBOOK.md)。 ### 路径 A:本地构建 → tar 上传 → 1Panel 导入(无镜像仓库时) ```bash # 本地(仓库根)构建;tag 按上方约定替换 docker build -f docker/server.Dockerfile -t photo-live-server:12059d9-20260921 . docker build -f docker/web.Dockerfile \ --build-arg VITE_VIEWER_BASE=https://你的viewer域名 \ --build-arg VITE_EMBED_ORIGIN=https://你的viewer域名 \ -t photo-live-web:12059d9-20260921 . docker save photo-live-server:12059d9-20260921 | gzip > photo-live-server.tar.gz docker save photo-live-web:12059d9-20260921 | gzip > photo-live-web.tar.gz # 上传两台服务器后,1Panel → 容器 → 镜像 → 导入(选择 tar.gz) ``` > web 镜像的 `VITE_VIEWER_BASE`/`VITE_EMBED_ORIGIN` 是**构建期**变量(admin 分享卡 > 与嵌入代码拼 URL 用);cn/ov 两地域各用各自域名构建一次。漏传只影响分享卡外链, > 不影响 API 路由(API 基址=同源 /v1,由运行时 `API_UPSTREAM` 决定)。 ### 路径 B:推镜像仓库 → 服务器 pull(有仓库时) 1Panel → 容器 → 仓库 → 创建(阿里云 ACR 个人版即可)→ 之后编排文件里 image 直接写 `registry.cn-hangzhou.aliyuncs.com/你的命名空间/photo-live-server:tag`,或在服务器 `docker pull` 后 `docker tag` 成编排里用的名字。 ## 2. MySQL 建库 / 授权(两台各执行对应段) 1Panel → 数据库 → MySQL(8.0+)→ 创建数据库。**库名/字符集**: ```sql -- cn 服务器(库名 photo_cn) CREATE DATABASE photo_cn CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin; CREATE USER 'photo'@'%' IDENTIFIED BY '强密码'; GRANT ALL PRIVILEGES ON photo_cn.* TO 'photo'@'%'; FLUSH PRIVILEGES; -- ov 服务器(库名 photo_ov) CREATE DATABASE photo_ov CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin; CREATE USER 'photo_ov'@'%' IDENTIFIED BY '强密码'; GRANT ALL PRIVILEGES ON photo_ov.* TO 'photo_ov'@'%'; FLUSH PRIVILEGES; ``` - `utf8mb4_0900_bin`(大小写敏感)是账号/token/publicToken 语义的前提,需 **MySQL ≥ 8.0.30**(决策 D12:先在两台 ECS 上 `SELECT VERSION();` 登记精确版本; 若低于 8.0.30,改用 `utf8mb4_bin`(同样大小写敏感),并重跑语义相关验收)。 - 授权 host:`'%'` 便于容器网段接入;生产更稳妥做法是限定网段 (`'172.%'` 容器网 + cn 服务器出口 IP)。**ov 库必须额外放行 cn 服务器出口 IP 的 3306**(sync 进程直连 ov 库),建议叠加 SSL 或隧道(P5 实测定案)。 ## 3. Redis 连接 1Panel → 数据库 → Redis → 创建(默认库即可)。连接串二选一: - **容器网络互连(推荐)**:photo-live 编排与 1Panel 的 Redis 同在 `1panel-network`,host 直接写 Redis **容器名**(`docker ps` 查名,如 `1Panel-redis-xxxx`),端口写容器内端口 6379。 - **宿主机 IP 直连**:host 写宿主机内网 IP;1Panel 应用端口默认只绑 127.0.0.1, 需在 1Panel 端口设置放开监听地址或改绑 0.0.0.0(注意防火墙)。 填入 env:cn `REDIS_URL_CN`;ov `REDIS_URL_OV`。 ## 4. Compose 编排(1Panel → 容器 → 编排 → 创建编排) 1. 文件管理器在 1Panel 数据目录旁新建目录(示例 `/opt/photo-live/`),上传: - `docker-compose.cn.yml`(cn)/ `docker-compose-ov.yml`(ov) - 由仓库根 `.env.production.example-cn`/`-ov` 复制改名的 `.env.production-cn`/`.env.production-ov`(**与 compose 同目录**,已填值) 2. 1Panel → 容器 → 编排 → 创建编排 → 路径选择该目录(或直接粘贴 compose 内容, 再把 env 文件放到 1Panel 显示的编排目录内)。 3. 默认编排挂**外部网络 `1panel-network`**(1Panel 应用网络,容器名互通 MySQL/ Redis)。若你的 1Panel 网络名不同(`docker network ls` 核对),改 compose 末段 `name:`;若用宿主机 IP 连接,把 networks 整段换成 `photo-net: {}` 并在 env 里 用 IP 形态的连接串。 4. 启动前确认镜像 tag:默认 `photo-live-server:latest`/`photo-live-web:latest`; 建议在 1Panel 编排的环境变量里固定 `SERVER_IMAGE`/`WEB_IMAGE` 为具体版本 tag。 ## 5. OpenResty 网站 + 证书反代到 web 容器 1Panel → 网站 → 创建网站 → 反向代理: - cn:主域名 `photo.example.cn`(viewer)→ 代理 `http://127.0.0.1:8088`; 管理端 `admin.photo.example.cn` → 同一后端 `http://127.0.0.1:8088`(admin 走 hash 路由 `/admin/` 路径,也可与 viewer 同域直接用 `photo.example.cn/admin/`)。 - ov:`photo.example.com` → `http://127.0.0.1:8089`。 - 证书:网站设置 → HTTPS → 申请/绑定证书,开启「强制 HTTPS」。 - OpenResty 侧注意: - **不要重复缓存** `/v1/`(API 动态;图片 URL 本身是带签名的 OSS 直链,不经过 这层);静态资源缓存已在 photo-live-web 容器内配置好。 - WebSocket(`/v1/realtime`)透传:1Panel 反代默认透传 Upgrade 头;若自建 OpenResty 配置,需保留 `proxy_set_header Upgrade/Connection`(参考 `nginx.conf.template` 的 /v1/ 段)。 - 若 web 端口改绑了非回环地址,注意防火墙只放行 OpenResty 出口。 ## 6. 迁移执行(migrate-deploy 角色,一次性容器) ```bash # cn 服务器:迁移 cn 库(env 文件与 compose 同目录;--rm 用完即删) docker run --rm --env-file /opt/photo-live/.env.production-cn \ --network 1panel-network \ photo-live-server:12059d9-20260921 migrate-deploy cn # ov 服务器:迁移 ov 库 docker run --rm --env-file /opt/photo-live/.env.production-ov \ --network 1panel-network \ photo-live-server:12059d9-20260921 migrate-deploy ov # 首次引导 admin 账号(cn;幂等,已存在则跳过) docker run --rm --env-file /opt/photo-live/.env.production-cn \ --network 1panel-network \ photo-live-server:12059d9-20260921 seed-admin ``` `migrate-deploy` 支持区域参数 `cn | ov | all`(缺省 all——单机双库演练用;生产两台 各跑各的)。Prisma migrate deploy 只向前应用未执行过的迁移,已在 RUNBOOK.md 固化为发布步骤与回滚原则。 ## 7. 首次部署顺序(cn 与 ov 并行各自的 1-2,汇合于 3-6) 1. **DB 就绪**:两台各建库/授权(§2)+ Redis(§3)。 2. **镜像导入**:两台各自导入 server+web 镜像(§1)。 3. **迁移**:cn `migrate-deploy cn` → ov `migrate-deploy ov`(§6)。 4. **起栈**:ov 先 `docker-compose-ov.yml` up(api-ov + web-ov)→ cn `docker-compose.cn.yml` up(api-cn + worker + sync + web)。sync 依赖 ov API 与 ov 库可达,故 cn 栈最后起。 5. **域名/证书**:OpenResty 反代 + HTTPS(§5),DNS 解析两地域各自接入。 6. **引导与演练**:cn `seed-admin`(§6)→ 管理端登录 → 建测试相册 → `scripts/prod-smoke.sh <账号> <密码>` 四点冒烟 (健康/登录/上传/公开读,ov 未部署时 `--ov` 跳过 ov 段)→ 观察同步水位 (sync 日志 `outbox batch pushed`)→ 清理测试数据(软删照片即可)。 日常发布/回滚见 `RUNBOOK.md`。