22 KiB
四套主题 Docker 生产部署任务
目标:在同一台服务器、同一套 Docker Compose 栈内,同时运行 4 套玩家端主题(4 个 player 容器 + 1 套共用 api/admin/db)。
适用分支:main(暗金)、theme-2(Pinnacle 蓝白)、theme-3(统一移动端视觉)、theme-4(海军蓝暗色极简)。
关联文档:Docker部署指南.md、docker/镜像构建与导出.md、AGENTS.md「主题分支」章节。
一、架构总览
域名 A(主站) → 127.0.0.1:8082 → thebet365-player 镜像 tag: main
域名 B(theme-2) → 127.0.0.1:8083 → thebet365-player2 镜像 tag: theme-2
域名 C(theme-3) → 127.0.0.1:8084 → thebet365-player3 镜像 tag: theme-3
域名 D(theme-4) → 127.0.0.1:8085 → thebet365-player4 镜像 tag: theme-4
管理后台 → 127.0.0.1:8081 → thebet365-admin 镜像 tag: latest(或统一 IMAGE_TAG)
↓
thebet365-api(Docker 内网,不映射宿主机端口)
↓
thebet365-postgres + thebet365-redis
| 要点 | 说明 |
|---|---|
| 不是「一个容器四套皮肤」 | 是 4 个 player 容器,各跑各自构建好的静态资源 |
| 后端共用 | api / postgres / redis / uploads 只有一套,账号与余额互通 |
| 本地开发 | 仍只需 pnpm dev:player(:5173),切分支预览不同主题;不必本地开 4 端口 |
deploy-update.sh |
默认只维护 一套 api/player/admin;多主题 player2~4 需 单独 load + compose up |
| 邀请注册链接 | 当前仅 VITE_PLAYER_URL 单域名;需 阶段 F 实现后台按主题选链接(见第十三节) |
二、任务清单(实施顺序)
按顺序勾选,下次上线可直接照着做。
阶段 A — 仓库改动(一次性)
- A1 新增
docker-compose.themes.yml(player2 / player3 / player4 服务定义) - A2 更新
.env.docker.example(PLAYER2_*~PLAYER4_*、CORS_ORIGINS示例) - A3 更新
docker-compose.prod.yml中player使用PLAYER_IMAGE_TAG(与IMAGE_TAG解耦,见下文片段) - A4(可选)在
docs/Docker部署指南.md增加「多主题」小节链接到本文 - A5(可选)在
AGENTS.md文档索引增加本文链接
阶段 B — 各主题分支同步 main 功能(发版前每个 theme 分支各做一遍)
- B1 在
theme-2/theme-3/theme-4同步 API、Admin、shared、迁移(禁止整目录覆盖 player) - B2 Player 侧只做逻辑/i18n 增量合并,保留各分支
styles.css与主题资源 - B3 各分支本地
pnpm build或pnpm dev:player冒烟 - B4
main分支照常维护;若 api/admin 有变更,四个 player 镜像可共用同一 api/admin 包
阶段 C — 本地打镜像(Windows 构建机)
- C1 四个分支分别打 player 镜像(tag 互不相同)
- C2 打 api + admin 一包(在
main或任意已同步分支,tag 如latest) - C3 核对
.manifest.txt中git_commit与分支一致 - C4 上传 tar 到服务器(勿覆盖
.env.docker)
阶段 D — 服务器首次启用多主题
- D1 备份:执行
deploy-update前会自动备份;首次改 compose 前建议手动./scripts/backup-prod.sh - D2
docker load四个 player tar + api/admin tar - D3 编辑
.env.docker(端口、镜像 tag、CORS、域名) - D4
docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d - D5 宝塔为 4 个玩家域名 + 1 个管理域名配置反代
- D6 执行下方「验收清单」
阶段 E — 日常更新(重复)
- 仅换某一主题 UI → 只 rebuild/load 对应 player tag →
up -d playerN - API/Admin/迁移变更 →
deploy-update.sh更新 api/admin;必要时四个 player 无需重建 - 发版后强刷浏览器 / 清 CDN
阶段 F — 邀请链接按主题选择(功能开发,可与 D 并行)
- F1 API:
SystemConfig存四套玩家站域名配置(见第十三节) - F2 管理端「全局设置」可编辑主题名称 + 公网 URL + 默认项
- F3 邀请面板 / 邀请历史:下拉选择主题后再复制注册链接
- F4 代理端邀请弹窗同样可读主题列表并选择(只读,不能改 URL)
- F5 部署四套主题后,在全局设置填齐 4 个 https 域名并验收邀请链接
三、仓库待实现:docker-compose.themes.yml
任务 A1:在仓库根目录新建此文件,与
docker-compose.prod.yml合并使用。
# 四套主题扩展 — 与 docker-compose.prod.yml 一起使用:
# docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d
services:
player2:
image: thebet365-player:${PLAYER2_IMAGE_TAG:-theme-2}
container_name: thebet365-player2
depends_on:
api:
condition: service_healthy
ports:
- '${BIND_ADDR:-127.0.0.1}:${PLAYER2_PORT:-8083}:80'
healthcheck:
test: ['CMD-SHELL', 'wget -q -O /dev/null http://127.0.0.1/ || exit 1']
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
restart: unless-stopped
networks:
- thebet365
player3:
image: thebet365-player:${PLAYER3_IMAGE_TAG:-theme-3}
container_name: thebet365-player3
depends_on:
api:
condition: service_healthy
ports:
- '${BIND_ADDR:-127.0.0.1}:${PLAYER3_PORT:-8084}:80'
healthcheck:
test: ['CMD-SHELL', 'wget -q -O /dev/null http://127.0.0.1/ || exit 1']
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
restart: unless-stopped
networks:
- thebet365
player4:
image: thebet365-player:${PLAYER4_IMAGE_TAG:-theme-4}
container_name: thebet365-player4
depends_on:
api:
condition: service_healthy
ports:
- '${BIND_ADDR:-127.0.0.1}:${PLAYER4_PORT:-8085}:80'
healthcheck:
test: ['CMD-SHELL', 'wget -q -O /dev/null http://127.0.0.1/ || exit 1']
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
restart: unless-stopped
networks:
- thebet365
任务 A3:调整 docker-compose.prod.yml 的 player 服务
将 player.image 从 ${IMAGE_TAG} 改为独立变量(避免与 api/admin 的 IMAGE_TAG 绑死):
player:
image: thebet365-player:${PLAYER_IMAGE_TAG:-main}
# build / ports / healthcheck 等其余保持不变
player2~4不要写build:,生产只 load 预构建镜像。
四、.env.docker 配置模板
任务 A2:追加到
.env.docker.example;服务器.env.docker按真实域名填写。
# ── 共用后端 ──
IMAGE_TAG=latest
ADMIN_PORT=8081
BIND_ADDR=127.0.0.1
# 管理端邀请链接默认指向的主玩家站(勿带末尾 /)
VITE_PLAYER_URL=https://www.example.com
# 四个玩家站域名都需列入(逗号分隔,无空格或统一 trim)
CORS_ORIGINS=https://www.example.com,https://theme2.example.com,https://theme3.example.com,https://theme4.example.com,https://admin.example.com
# ── 四套 player 镜像 tag 与宿主机端口 ──
PLAYER_IMAGE_TAG=main
PLAYER_PORT=8082
PLAYER2_IMAGE_TAG=theme-2
PLAYER2_PORT=8083
PLAYER3_IMAGE_TAG=theme-3
PLAYER3_PORT=8084
PLAYER4_IMAGE_TAG=theme-4
PLAYER4_PORT=8085
修改 CORS_ORIGINS 后必须重启 api:
docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker restart api
五、分支合并代码(main → theme-*)
发版前在每个
theme-*分支执行。目标:功能与 main 一致,皮肤不变。
5.1 禁止事项
| 禁止 | 原因 |
|---|---|
git merge main 后直接发 player |
易把 main 暗金样式大量带入 |
git checkout main -- apps/player/ |
整目录覆盖会冲掉主题 CSS/组件 |
git checkout main -- apps/player/src/i18n/ |
会冲掉主题分支已有措辞 |
5.2 推荐:整目录同步(API / Admin / shared / 迁移)
在目标主题分支上(示例 theme-2):
git checkout theme-2
git fetch origin
# 同步后端与共享包(可按需增减路径)
git checkout origin/main -- apps/api
git checkout origin/main -- apps/admin
git checkout origin/main -- packages/shared
git checkout origin/main -- pnpm-lock.yaml
git checkout origin/main -- pnpm-workspace.yaml
# 若有新迁移,务必带上
git checkout origin/main -- apps/api/prisma/migrations
git checkout origin/main -- apps/api/prisma/schema.prisma
git status # 确认没有误改 apps/player 大段样式文件
git commit -m "sync: api/admin/shared/migrations from main"
5.3 Player:只做逻辑与文案增量
方式 1 — 按文件 cherry-pick(有明确 commit 时)
git log origin/main --oneline -- apps/player/src/composables apps/player/src/stores
git cherry-pick <commit-hash> # 冲突时保留 theme 分支的 styles / 主题组件
方式 2 — 按目录选择性 checkout(仅非 UI 目录)
# 示例:只同步 stores、composables、api 封装(执行前确认路径在 main 有变更)
git checkout origin/main -- apps/player/src/stores
git checkout origin/main -- apps/player/src/composables
git checkout origin/main -- apps/player/src/api
方式 3 — i18n 只合并新增 key
- 对比
apps/player/src/i18n/*.ts,手工把 main 新增的 key 补进三语文件 - 不要用 main 文件整文件覆盖
5.4 合并后本地验证(每个 theme 分支)
pnpm install
pnpm db:generate # 若 schema 有变
pnpm --filter @thebet365/shared build
pnpm --filter @thebet365/player build
pnpm dev:api # 另开终端
pnpm dev:player # http://localhost:5173 目视确认仍是本主题皮肤
5.5 main 分支
- 日常功能开发在
main完成后再同步到各 theme 分支 main自身 player 镜像 tag 建议使用main或latest(与.env.docker中PLAYER_IMAGE_TAG一致即可)
六、构建与上传镜像
6.1 四个 player 镜像(Windows CMD)
cd C:\path\to\thebet365
git checkout main
docs\docker\build-and-export-player.bat --tag main
git checkout theme-2
docs\docker\build-and-export-player.bat --tag theme-2
git checkout theme-3
docs\docker\build-and-export-player.bat --tag theme-3
git checkout theme-4
docs\docker\build-and-export-player.bat --tag theme-4
产物(项目根目录,已在 .gitignore):
| 文件 | 加载后镜像名 |
|---|---|
thebet365-player-main.tar |
thebet365-player:main |
thebet365-player-theme-2.tar |
thebet365-player:theme-2 |
thebet365-player-theme-3.tar |
thebet365-player:theme-3 |
thebet365-player-theme-4.tar |
thebet365-player:theme-4 |
6.2 api + admin 一包
git checkout main
docs\docker\build-and-export-images.bat --tag latest
或 Linux:./docs/docker/build-and-export-images.sh --tag latest
6.3 上传至服务器
目录示例:/www/wwwroot/thebet365
| 必传 | 说明 |
|---|---|
| 四个 player tar | 四套皮肤 |
thebet365-images-latest.tar(或带版本 tag) |
api + admin |
docker-compose.prod.yml |
若仓库有更新 |
docker-compose.themes.yml |
首次多主题必传 |
scripts/ |
若部署脚本有更新 |
勿覆盖:.env.docker、Docker 数据卷、backups/
七、服务器部署命令
7.1 首次启用四套主题
cd /www/wwwroot/thebet365
chmod +x scripts/*.sh
# 导入全部镜像
docker load -i thebet365-images-latest.tar
docker load -i thebet365-player-main.tar
docker load -i thebet365-player-theme-2.tar
docker load -i thebet365-player-theme-3.tar
docker load -i thebet365-player-theme-4.tar
# 编辑 .env.docker(见第四节模板)后启动
docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d
若当前环境已由 deploy-first.sh 跑通,只需 load 新 player 镜像 + 合并 compose + up -d player2 player3 player4,并调整 .env.docker。
7.2 更新共用 api/admin
仍用现有脚本(不会自动更新 player2~4):
./scripts/deploy-update.sh --images thebet365-images-latest.tar --tag latest
之后确认多主题 compose 仍生效:
docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d
7.3 只更新某一主题(例如 theme-4)
docker load -i thebet365-player-theme-4.tar
docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d player4
其他主题容器不受影响。
7.4 回滚某一 player
# 加载旧 tag 的 tar 后
docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d player3
api 回滚仍用 ./scripts/rollback.sh --to <tag>(不回滚数据库)。
八、宝塔 / Nginx 反代
每个玩家站 只反代到对应端口,容器内已处理 /api 与 /uploads,宝塔无需再写 API 规则。
| 宝塔网站 | proxy_pass |
|---|---|
| 主站(main) | http://127.0.0.1:8082 |
| theme-2 站 | http://127.0.0.1:8083 |
| theme-3 站 | http://127.0.0.1:8084 |
| theme-4 站 | http://127.0.0.1:8085 |
| 管理后台 | http://127.0.0.1:8081 |
示例:
location / {
proxy_pass http://127.0.0.1:8082;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
四套站点分别申请 SSL;CORS_ORIGINS 使用 https 域名。
九、验收清单
部署完成后逐项检查:
docker compose ... ps— api、admin、player、player2、player3、player4 均为 healthy- 四个玩家域名首页 UI 皮肤互不相同
- 各站登录同一账号,余额一致
- 各站下注、充值、站内信、公告正常
- 管理后台
:8081正常;员工菜单与 Inbox 开关已配置(见 Docker部署指南.md 第八节步骤 5) npx prisma migrate status(在 api 容器内)无 pending 迁移- 浏览器 Network 无 CORS 报错(若有,检查
CORS_ORIGINS并 restart api) - theme-4 独有组件(如悬浮客服)仅在 theme-4 站出现
- 阶段 F 完成后:邀请面板切换四套主题,复制链接分别打开对应域名
/register?code=...
十、常见问题
| 现象 | 处理 |
|---|---|
| 某站 502 | docker compose ps 看对应 player 是否 healthy;docker logs thebet365-player3 |
| 接口 401/403 仅某一域名 | 检查 CORS_ORIGINS 是否包含该 https 域名 |
| 四套变成同一皮肤 | 检查是否四个 tag 打错或 .env.docker 的 PLAYER*_IMAGE_TAG 写错 |
deploy-update 后 player2 消失 |
脚本未管理 themes compose;重新 up -d 并带 -f docker-compose.themes.yml |
| 邀请链接域名不对 | 未做阶段 F:改 VITE_PLAYER_URL 重建 admin(仅一个默认站);已做阶段 F:到「全局设置 → 玩家主题站点」核对 URL,邀请面板选对应主题 |
构建 ENOENT public/球员 |
清理 packages/shared/public 下除 flags、players 外的中文目录后重试 |
十一、与本地开发的区别
| 场景 | 做法 |
|---|---|
| 日常改某一主题 UI | 切到对应分支 → pnpm dev:player(5173) |
| 本地同时对比 4 套 | 可选:多开终端 --port 5173/5174/5175/5176(非必须) |
| 生产 4 套并存 | 4 个 player 容器 + 4 个端口 + 4 个域名 |
十二、邀请链接按主题选择(阶段 F 详细设计)
现状:
apps/admin/src/utils/invite-link.ts用构建时注入的VITE_PLAYER_URL拼链接;四套主题并存时后台无法选择注册落地页。
目标:管理员(及代理)生成/复制邀请链接时,下拉选择要落地的玩家主题站;配置存数据库,改域名不必重建 admin 镜像。
12.1 行为说明
| 角色 | 能力 |
|---|---|
平台管理员(settings.manage) |
在「全局设置」维护主题列表:显示名、公网 URL、是否启用、哪一项为默认 |
| 管理员 / 代理 | 在「邀请」弹窗选主题 → 复制链接 {所选站}/register?code=xxx |
| 玩家 | 无感知;任意主题站注册,同一邀请码、同一后端 |
邀请码本身与主题无关(仍走现有 InvitesService);仅注册链接里的域名随所选主题变化。
12.2 数据模型(SystemConfig)
配置键:player.theme_sites
值:JSON 字符串,结构示例:
{
"themes": [
{ "id": "main", "label": "暗金主站", "baseUrl": "https://www.example.com", "enabled": true, "isDefault": true },
{ "id": "theme-2", "label": "Pinnacle 蓝白", "baseUrl": "https://theme2.example.com", "enabled": true, "isDefault": false },
{ "id": "theme-3", "label": "统一移动端", "baseUrl": "https://theme3.example.com", "enabled": true, "isDefault": false },
{ "id": "theme-4", "label": "海军蓝极简", "baseUrl": "https://theme4.example.com", "enabled": true, "isDefault": false }
]
}
规则:
id与 Git 分支 / Docker 镜像 tag 对齐(main、theme-2、theme-3、theme-4)baseUrl勿带末尾/;保存时 API 侧 trim + 校验https?://- 有且仅有一个
isDefault: true;未配置时 API 回退VITE_PLAYER_URL(兼容旧环境) enabled: false的主题不出现在邀请下拉里
12.3 API 任务(apps/api)
F1 — SystemConfigService 新增:
// apps/api/src/shared/config/system-config.service.ts
getPlayerThemeSites(): Promise<PlayerThemeSitesSettings>
updatePlayerThemeSites(data): Promise<PlayerThemeSitesSettings>
listEnabledPlayerThemes(): Promise<PlayerThemePublicOption[]> // 仅 id/label/baseUrl/isDefault
F1 — Admin 控制器:
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /admin/settings/player-themes |
settings.manage |
完整配置(含 disabled) |
| PUT | /admin/settings/player-themes |
settings.manage |
保存;写审计 UPDATE_PLAYER_THEME_SITES |
| GET | /admin/player-themes/options |
登录员工/代理即可 | 仅 enabled 主题,供邀请 UI |
代理门户若走 /manage/*,在 auth.controller 或 manage 控制器增加同等 GET options(复用 service)。
校验:
- 至少保留 1 个
enabled主题 - URL 格式合法;禁止重复
baseUrl - 更新后无需重启 api(读 DB)
12.4 管理端任务(apps/admin)
F2 — GlobalSettingsView.vue 增加卡片「玩家主题站点」:
- 表格编辑:label、baseUrl、enabled、设为默认
- 保存调用
PUT /admin/settings/player-themes - 与第四节四个域名保持一致(部署后首次必填)
F3 — 邀请 UI:
涉及文件:
apps/admin/src/utils/invite-link.ts— 改为buildPlayerRegisterUrl(code, baseUrl?)apps/admin/src/components/InviteCodePanel.vue— 加载 options,el-select选主题,registerUrl随选择变化apps/admin/src/components/InviteHistoryPanel.vue— 复制链接前同样选主题(或记住上次选择)
交互建议:
- 默认选中 API 返回的
isDefault主题 localStorage键admin_invite_theme_id记住上次选择(可选)- 无配置时回退
VITE_PLAYER_URL,与现网行为一致
F4 — i18n(三语同步):
在 admin-messages.ts / admin-pages*.ts 增加 key,例如:
invite.theme_site— 注册落地主题invite.theme_site_hint— 选择玩家看到的注册页面风格settings.player_themes— 玩家主题站点settings.player_themes_hint— 与四套 Docker 玩家域名对应
12.5 与 Docker 四套主题的对应关系
部署完四套 player 后,在 全局设置 填:
| id | 对应容器 | 示例 baseUrl |
|---|---|---|
main |
thebet365-player :8082 |
https://www.example.com |
theme-2 |
thebet365-player2 :8083 |
https://theme2.example.com |
theme-3 |
thebet365-player3 :8084 |
https://theme3.example.com |
theme-4 |
thebet365-player4 :8085 |
https://theme4.example.com |
VITE_PLAYER_URL 仍可保留为 构建默认值 与 未配置 DB 时的回退;多主题上线后以 DB 配置为准。
12.6 阶段 F 验收
- 全局设置保存 4 个 URL 后刷新仍生效
- 邀请面板切换主题,链接域名随之变化,邀请码不变
- 四套链接均可打开注册页并带
code参数 - 代理账号邀请弹窗可选主题(不能进全局设置改 URL)
- 禁用某主题后下拉里消失
- 未配置 DB 时行为与现网一致(
VITE_PLAYER_URL)
12.7 实施顺序建议
F1 API 配置读写 → F2 全局设置页 → F3 邀请面板 → F4 代理端 → 部署四套主题后填 URL → 12.6 验收
可与 阶段 A~D(Docker 多 player) 并行开发;功能不依赖 player2~4 已上线,但验收需要四个域名可访问。
十三、文档维护
| 变更类型 | 更新本文 |
|---|---|
| 新增 theme-5 | 复制 player4 模式,追加端口 8086 |
| 修改 deploy 脚本支持多 player | 更新第七节命令 |
| 分支合并策略变化 | 更新第五节 |
下次实施入口:
- 基础设施:阶段 A → B → C → D → 第九节验收
- 邀请多主题:阶段 F(第十二节)可与 A~D 并行,四套域名就绪后做 12.6 验收