Merge main into theme-2 and resolve conflicts for desktop architecture

This commit is contained in:
2026-06-30 10:53:34 +08:00
170 changed files with 26378 additions and 10031 deletions

View File

@@ -0,0 +1,13 @@
@echo off
REM Thin launcher for build-and-export-all-themes.ps1
REM CMD reads batch files line-by-line from disk; git checkout to theme-* removes
REM this file from the working tree. PowerShell loads the script into memory first.
REM
REM Usage:
REM docs\docker\build-and-export-all-themes.bat
REM docs\docker\build-and-export-all-themes.bat --use-cache
REM docs\docker\build-and-export-all-themes.bat --export-only
REM docs\docker\build-and-export-all-themes.bat --skip-api-admin
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0build-and-export-all-themes.ps1" %*
exit /b %ERRORLEVEL%

View File

@@ -0,0 +1,285 @@
# 四套主题 player 一键打包(自动切换 Git 分支)
# PowerShell 会在启动时将整份脚本载入内存git checkout 后仍可继续执行。
# 用法(项目根目录或本目录均可):
# .\docs\docker\build-and-export-all-themes.ps1
# .\docs\docker\build-and-export-all-themes.ps1 -UseCache
# .\docs\docker\build-and-export-all-themes.ps1 -ExportOnly
# .\docs\docker\build-and-export-all-themes.ps1 -SkipApiAdmin
# 或通过 bat 启动(参数支持 --use-cache 等 CMD 风格):
# docs\docker\build-and-export-all-themes.bat --use-cache
param(
[switch]$UseCache,
[switch]$ExportOnly,
[switch]$SkipApiAdmin,
[switch]$SkipBundle,
[switch]$Help
)
$ErrorActionPreference = "Stop"
foreach ($arg in $args) {
switch ($arg.ToLowerInvariant()) {
"--use-cache" { $UseCache = $true; continue }
"--export-only" { $ExportOnly = $true; continue }
"--skip-api-admin" { $SkipApiAdmin = $true; continue }
"--skip-bundle" { $SkipBundle = $true; continue }
"-h" { $Help = $true; continue }
"--help" { $Help = $true; continue }
"/?" { $Help = $true; continue }
default { throw "Unknown argument: $arg" }
}
}
function Show-Help {
Write-Host @"
Usage: docs\docker\build-and-export-all-themes.ps1 [options]
-UseCache, --use-cache Use Docker build cache (default: --no-cache)
-ExportOnly, --export-only Skip build, export existing images only
-SkipApiAdmin, --skip-api-admin
Only build 4 player themes, skip api/admin
-SkipBundle, --skip-bundle Skip thebet365-full-themes-latest.tar (6-in-1 bundle)
-Help, -h, --help Show this help
Output (project root):
thebet365-player-main.tar
thebet365-player-theme-2.tar
thebet365-player-theme-3.tar
thebet365-player-theme-4.tar
thebet365-images-latest.tar (api + admin, unless -SkipApiAdmin)
thebet365-full-themes-latest.tar (api + admin + 4 player themes, unless -SkipBundle)
"@
}
if ($Help) {
Show-Help
exit 0
}
$Root = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path
$Themes = @(
@{ Branch = "main"; Tag = "main" },
@{ Branch = "theme-2"; Tag = "theme-2" },
@{ Branch = "theme-3"; Tag = "theme-3" },
@{ Branch = "theme-4"; Tag = "theme-4" }
)
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
throw "docker not found. Please start Docker Desktop first."
}
if (-not (Test-Path (Join-Path $Root "docs\docker\build-and-export-images.bat"))) {
throw "Missing build script: docs\docker\build-and-export-images.bat"
}
function Invoke-Git {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$GitArgs)
$prevErrorAction = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
try {
& git @GitArgs 2>$null | Out-Null
return $LASTEXITCODE
} finally {
$ErrorActionPreference = $prevErrorAction
}
}
function Get-GitOutput {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$GitArgs)
$prevErrorAction = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
try {
return ((& git @GitArgs 2>$null | Out-String).Trim())
} finally {
$ErrorActionPreference = $prevErrorAction
}
}
Set-Location $Root
$OriginalBranch = Get-GitOutput rev-parse --abbrev-ref HEAD
if ([string]::IsNullOrWhiteSpace($OriginalBranch)) {
throw "Cannot determine current Git branch. Run this script from the repo root."
}
Write-Host "[INFO] Current branch: $OriginalBranch"
$DidStash = $false
$diffExit = Invoke-Git diff --quiet
$cachedExit = Invoke-Git diff --cached --quiet
if ($diffExit -ne 0 -or $cachedExit -ne 0) {
Write-Host "[INFO] Stashing local changes before branch switching..."
$stashExit = Invoke-Git stash push -m "build-all-themes auto-stash"
if ($stashExit -eq 0) {
$DidStash = $true
Write-Host "[INFO] Changes stashed."
} else {
Write-Warning "git stash failed. Branch switching may fail if there are conflicts."
}
}
$FailedThemes = [System.Collections.Generic.List[string]]::new()
$SuccessCount = 0
function Invoke-BuildExport {
param(
[string]$Service = "",
[string]$Tag
)
$argList = @()
if (-not [string]::IsNullOrWhiteSpace($Service)) {
$argList += "--service"
$argList += $Service
}
$argList += "--tag"
$argList += $Tag
if ($UseCache) { $argList += "--use-cache" }
if ($ExportOnly) { $argList += "--export-only" }
Set-Location $Root
$batPath = Join-Path $Root "docs\docker\build-and-export-images.bat"
$argText = ($argList | ForEach-Object {
if ($_ -match '\s') { "`"$_`"" } else { $_ }
}) -join ' '
& cmd.exe /c "`"$batPath`" $argText"
return $LASTEXITCODE
}
function Invoke-PlayerThemeBuild {
param(
[string]$Branch,
[string]$Tag
)
Write-Host ""
Write-Host "----------------------------------------------------------------"
Write-Host "[INFO] Theme: $Branch (image tag: $Tag)"
Write-Host "----------------------------------------------------------------"
Set-Location $Root
$checkoutExit = Invoke-Git checkout $Branch
if ($checkoutExit -ne 0) {
Write-Host "[ERROR] Cannot switch to branch $Branch -- skipping"
$script:FailedThemes.Add($Branch) | Out-Null
return
}
Write-Host "[INFO] Switched to $Branch"
Set-Location $Root
if ((Invoke-BuildExport -Service "player" -Tag $Tag) -ne 0) {
Write-Host "[ERROR] Player build failed for $Branch"
$script:FailedThemes.Add($Branch) | Out-Null
return
}
Write-Host "[OK] thebet365-player:$Tag exported successfully"
$script:SuccessCount++
}
foreach ($theme in $Themes) {
Invoke-PlayerThemeBuild -Branch $theme.Branch -Tag $theme.Tag
}
Write-Host ""
Write-Host "[INFO] Restoring original branch: $OriginalBranch"
Set-Location $Root
if ((Invoke-Git checkout $OriginalBranch) -eq 0) {
Write-Host "[INFO] Restored to $OriginalBranch"
} else {
Write-Warning "Could not restore branch $OriginalBranch. Run: git checkout $OriginalBranch"
}
if ($DidStash) {
Write-Host "[INFO] Restoring stashed changes..."
if ((Invoke-Git stash pop) -eq 0) {
Write-Host "[INFO] Stash restored."
} else {
Write-Warning "git stash pop failed. Run: git stash pop"
}
}
if (-not $SkipApiAdmin) {
Write-Host ""
Write-Host "[INFO] Building api + admin (tag: latest) on branch main..."
Set-Location $Root
if ((Invoke-Git checkout main) -ne 0) {
Write-Host "[ERROR] Cannot switch to branch main for api/admin build"
$FailedThemes.Add("api/admin") | Out-Null
} else {
Set-Location $Root
if ((Invoke-BuildExport -Service "api-admin" -Tag "latest") -ne 0) {
Write-Host "[ERROR] api/admin build failed"
$FailedThemes.Add("api/admin") | Out-Null
} else {
$SuccessCount++
}
}
Write-Host ""
Write-Host "[INFO] Restoring original branch: $OriginalBranch"
Set-Location $Root
Invoke-Git checkout $OriginalBranch | Out-Null
}
if (-not $SkipBundle -and -not $SkipApiAdmin -and $FailedThemes.Count -eq 0) {
Write-Host ""
Write-Host "[INFO] Exporting full bundle (api + admin + 4 player themes)..."
Set-Location $Root
if ((Invoke-BuildExport -Service "full-themes" -Tag "latest") -ne 0) {
Write-Host "[ERROR] Full bundle export failed"
$FailedThemes.Add("full-bundle") | Out-Null
} else {
Write-Host "[OK] thebet365-full-themes-latest.tar exported successfully"
$SuccessCount++
}
}
Write-Host ""
Write-Host "================================================================"
Write-Host " BUILD SUMMARY"
Write-Host "================================================================"
Write-Host " Succeeded: $SuccessCount task(s)"
if ($FailedThemes.Count -gt 0) {
Write-Host " Failed: $($FailedThemes -join ' ')"
Write-Host ""
Write-Host " Tip: Retry failed themes manually:"
Write-Host " git checkout <branch>"
Write-Host " docs\docker\build-and-export-images.bat --service player --tag <tag>"
Write-Host "================================================================"
exit 1
}
Write-Host ""
Write-Host " All done! Output files in project root:"
Write-Host " thebet365-player-main.tar"
Write-Host " thebet365-player-theme-2.tar"
Write-Host " thebet365-player-theme-3.tar"
Write-Host " thebet365-player-theme-4.tar"
if (-not $SkipApiAdmin) {
Write-Host " thebet365-images-latest.tar (api + admin)"
}
if (-not $SkipBundle -and -not $SkipApiAdmin) {
Write-Host " thebet365-full-themes-latest.tar (api + admin + 4 themes, upload this only)"
}
Write-Host ""
Write-Host " Server import (single bundle):"
Write-Host " docker load -i thebet365-full-themes-latest.tar"
Write-Host " docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d"
Write-Host ""
Write-Host " Or import separate tars:"
if (-not $SkipApiAdmin) {
Write-Host " docker load -i thebet365-images-latest.tar"
}
Write-Host " docker load -i thebet365-player-main.tar"
Write-Host " docker load -i thebet365-player-theme-2.tar"
Write-Host " docker load -i thebet365-player-theme-3.tar"
Write-Host " docker load -i thebet365-player-theme-4.tar"
Write-Host "================================================================"
exit 0

View File

@@ -8,6 +8,9 @@ REM docs\docker\build-and-export-images.bat --service admin
REM docs\docker\build-and-export-images.bat --tag v1.2.3
REM docs\docker\build-and-export-images.bat --use-cache
REM docs\docker\build-and-export-images.bat --export-only
REM
REM 四套主题玩家端一键打包(自动切分支,推荐):
REM docs\docker\build-and-export-all-themes.bat
cd /d "%~dp0..\.."
@@ -66,7 +69,7 @@ goto parse_args
echo.
echo Usage: docs\docker\build-and-export-images.bat [options]
echo.
echo --service SVC api ^| player ^| admin ^| all (default: all)
echo --service SVC api ^| player ^| admin ^| api-admin ^| full-themes ^| all (default: all)
echo --tag TAG Image tag (default: latest)
echo --use-cache Use Docker build cache
echo --export-only Skip build, export existing images only
@@ -78,6 +81,9 @@ echo docs\docker\build-and-export-images.bat --service admin
echo docs\docker\build-and-export-admin.bat
echo docs\docker\build-and-export-admin.bat --export-only
echo.
echo 四套主题玩家端批量打包(自动切分支,推荐):
echo docs\docker\build-and-export-all-themes.bat
echo.
exit /b 0
:args_done
@@ -111,8 +117,7 @@ if errorlevel 1 exit /b 1
if not defined OUTPUT call :default_output
set "OUTPUT_PATH=%OUTPUT%"
echo %OUTPUT_PATH%| findstr /r "^[A-Za-z]:\\" >nul
if errorlevel 1 set "OUTPUT_PATH=%CD%\%OUTPUT%"
if /i not "%OUTPUT:~1,1%"==":" set "OUTPUT_PATH=%CD%\%OUTPUT%"
where docker >nul 2>&1
if errorlevel 1 (
@@ -120,7 +125,7 @@ if errorlevel 1 (
exit /b 1
)
if "%EXPORT_ONLY%"=="0" goto do_build
if "%EXPORT_ONLY%"=="0" if /i not "%SERVICE%"=="full-themes" goto do_build
goto do_export
:do_build
@@ -129,6 +134,8 @@ set "IMAGE_TAG=%TAG%"
if "%NO_CACHE%"=="1" goto build_no_cache
if /i "%SERVICE%"=="all" (
docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build api player admin
) else if /i "%SERVICE%"=="api-admin" (
docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build api admin
) else (
docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build %SERVICE%
)
@@ -137,6 +144,8 @@ goto build_done
:build_no_cache
if /i "%SERVICE%"=="all" (
docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build --no-cache api player admin
) else if /i "%SERVICE%"=="api-admin" (
docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build --no-cache api admin
) else (
docker compose -f "%COMPOSE_FILE%" --env-file "%ENV_FILE%" build --no-cache %SERVICE%
)
@@ -151,6 +160,10 @@ if errorlevel 1 (
echo ==^> Exporting %SERVICE% to %OUTPUT_PATH%
if /i "%SERVICE%"=="all" (
docker save thebet365-api:%TAG% thebet365-player:%TAG% thebet365-admin:%TAG% -o "%OUTPUT_PATH%"
) else if /i "%SERVICE%"=="api-admin" (
docker save thebet365-api:%TAG% thebet365-admin:%TAG% -o "%OUTPUT_PATH%"
) else if /i "%SERVICE%"=="full-themes" (
docker save thebet365-api:%TAG% thebet365-admin:%TAG% thebet365-player:main thebet365-player:theme-2 thebet365-player:theme-3 thebet365-player:theme-4 -o "%OUTPUT_PATH%"
) else (
docker save thebet365-%SERVICE%:%TAG% -o "%OUTPUT_PATH%"
)
@@ -181,16 +194,34 @@ for %%F in ("%OUTPUT_PATH%") do set /a SIZE_MB=%%~zF/1048576
echo git_dirty=!GIT_DIRTY!
echo tar=!TAR_NAME!
echo tar_sha256=!TAR_SHA256!
if /i not "!SERVICE!"=="all" (
echo image=thebet365-!SERVICE!:%TAG%
echo image_id=!SVC_IMAGE_ID!
) else (
if /i "!SERVICE!"=="all" (
echo api_image=thebet365-api:%TAG%
echo api_image_id=!API_IMAGE_ID!
echo player_image=thebet365-player:%TAG%
echo player_image_id=!PLAYER_IMAGE_ID!
echo admin_image=thebet365-admin:%TAG%
echo admin_image_id=!ADMIN_IMAGE_ID!
) else if /i "!SERVICE!"=="api-admin" (
echo api_image=thebet365-api:%TAG%
echo api_image_id=!API_IMAGE_ID!
echo admin_image=thebet365-admin:%TAG%
echo admin_image_id=!ADMIN_IMAGE_ID!
) else if /i "!SERVICE!"=="full-themes" (
echo api_image=thebet365-api:%TAG%
echo api_image_id=!API_IMAGE_ID!
echo admin_image=thebet365-admin:%TAG%
echo admin_image_id=!ADMIN_IMAGE_ID!
echo player_main_image=thebet365-player:main
echo player_main_image_id=!PLAYER_MAIN_IMAGE_ID!
echo player_theme_2_image=thebet365-player:theme-2
echo player_theme_2_image_id=!PLAYER_THEME2_IMAGE_ID!
echo player_theme_3_image=thebet365-player:theme-3
echo player_theme_3_image_id=!PLAYER_THEME3_IMAGE_ID!
echo player_theme_4_image=thebet365-player:theme-4
echo player_theme_4_image_id=!PLAYER_THEME4_IMAGE_ID!
) else (
echo image=thebet365-!SERVICE!:%TAG%
echo image_id=!SVC_IMAGE_ID!
)
)
@@ -201,6 +232,12 @@ echo Server load and recreate:
if /i "%SERVICE%"=="all" (
echo docker load -i !TAR_NAME!
echo docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --force-recreate api player admin
) else if /i "%SERVICE%"=="api-admin" (
echo docker load -i !TAR_NAME!
echo docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --force-recreate api admin
) else if /i "%SERVICE%"=="full-themes" (
echo docker load -i !TAR_NAME!
echo docker compose -f docker-compose.prod.yml -f docker-compose.themes.yml --env-file .env.docker up -d
) else (
echo docker load -i !TAR_NAME!
echo docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --force-recreate %SERVICE%
@@ -217,6 +254,10 @@ exit /b 0
:default_output
if /i "%SERVICE%"=="all" (
set "OUTPUT=thebet365-images-%TAG%.tar"
) else if /i "%SERVICE%"=="api-admin" (
set "OUTPUT=thebet365-images-%TAG%.tar"
) else if /i "%SERVICE%"=="full-themes" (
set "OUTPUT=thebet365-full-themes-%TAG%.tar"
) else (
set "OUTPUT=thebet365-%SERVICE%-%TAG%.tar"
)
@@ -224,10 +265,13 @@ exit /b 0
:validate_service
if /i "%SERVICE%"=="all" exit /b 0
if /i "%SERVICE%"=="api-admin" exit /b 0
if /i "%SERVICE%"=="full-themes" exit /b 0
if /i "%SERVICE%"=="api" exit /b 0
if /i "%SERVICE%"=="player" exit /b 0
if /i "%SERVICE%"=="admin" exit /b 0
echo ERROR: invalid --service: %SERVICE% (use api, player, admin, or all)
echo ERROR: invalid --service: %SERVICE% (use api, player, admin, api-admin, full-themes, or all)
echo TIP: 四套主题一键打包请使用 docs\docker\build-and-export-all-themes.bat
exit /b 1
:validate_tag
@@ -266,10 +310,19 @@ set "API_IMAGE_ID="
set "PLAYER_IMAGE_ID="
set "ADMIN_IMAGE_ID="
set "SVC_IMAGE_ID="
set "PLAYER_MAIN_IMAGE_ID="
set "PLAYER_THEME2_IMAGE_ID="
set "PLAYER_THEME3_IMAGE_ID="
set "PLAYER_THEME4_IMAGE_ID="
for /f "delims=" %%I in ('docker image inspect thebet365-api:%TAG% --format "{{.Id}}" 2^>nul') do set "API_IMAGE_ID=%%I"
for /f "delims=" %%I in ('docker image inspect thebet365-player:%TAG% --format "{{.Id}}" 2^>nul') do set "PLAYER_IMAGE_ID=%%I"
for /f "delims=" %%I in ('docker image inspect thebet365-admin:%TAG% --format "{{.Id}}" 2^>nul') do set "ADMIN_IMAGE_ID=%%I"
if /i not "%SERVICE%"=="all" (
if /i "%SERVICE%"=="full-themes" (
for /f "delims=" %%I in ('docker image inspect thebet365-player:main --format "{{.Id}}" 2^>nul') do set "PLAYER_MAIN_IMAGE_ID=%%I"
for /f "delims=" %%I in ('docker image inspect thebet365-player:theme-2 --format "{{.Id}}" 2^>nul') do set "PLAYER_THEME2_IMAGE_ID=%%I"
for /f "delims=" %%I in ('docker image inspect thebet365-player:theme-3 --format "{{.Id}}" 2^>nul') do set "PLAYER_THEME3_IMAGE_ID=%%I"
for /f "delims=" %%I in ('docker image inspect thebet365-player:theme-4 --format "{{.Id}}" 2^>nul') do set "PLAYER_THEME4_IMAGE_ID=%%I"
) else if /i not "%SERVICE%"=="all" (
for /f "delims=" %%I in ('docker image inspect thebet365-%SERVICE%:%TAG% --format "{{.Id}}" 2^>nul') do set "SVC_IMAGE_ID=%%I"
)
exit /b 0

View File

@@ -1,13 +1,17 @@
# 构建 api / player / admin 生产镜像并导出为 tar
# 用法(在项目根目录或本目录执行均可):
# ?? api / player / admin ???????? tar
# ??????????????????:
# .\docs\docker\build-and-export-images.ps1
# .\docs\docker\build-and-export-images.ps1 -Service admin
# .\docs\docker\build-and-export-images.ps1 -Tag v1.2.3
# .\docs\docker\build-and-export-images.ps1 -UseCache
# .\docs\docker\build-and-export-images.ps1 -ExportOnly
#
# ????????????????????????????????????
# docs\docker\build-and-export-all-themes.bat
param(
[ValidateSet("all", "api", "player", "admin")]
[ValidateSet("all", "api", "player", "admin", "api-admin")]
[string]$Service = "all",
[string]$Tag = $env:IMAGE_TAG,
[switch]$UseCache,
@@ -27,27 +31,27 @@ function Get-DefaultTag { "latest" }
function Get-DefaultOutput {
param([string]$Svc, [string]$ImageTag)
if ($Svc -eq "all") { return "thebet365-images-$ImageTag.tar" }
if ($Svc -eq "all" -or $Svc -eq "api-admin") { return "thebet365-images-$ImageTag.tar" }
return "thebet365-$Svc-$ImageTag.tar"
}
function Assert-ImageTag {
param([string]$Value)
if ($Value -notmatch '^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$') {
throw "镜像 tag 不合法: $Value"
throw "?? tag ???? $Value"
}
}
if (-not (Test-Path $ComposeFile)) {
throw "未找到 $ComposeFile(当前目录: $Root"
throw "????$ComposeFile?????? $Root??
}
if (-not (Test-Path $EnvFile)) {
if (Test-Path ".env.docker.example") {
Write-Warning "未找到 .env.docker使用 .env.docker.example(生产请复制并修改密钥)"
Write-Warning "????.env.docker????.env.docker.example????????????"
$EnvFile = ".env.docker.example"
} else {
throw "未找到 .env.docker 或 .env.docker.example"
throw "????.env.docker ??.env.docker.example"
}
}
@@ -56,7 +60,11 @@ if ([string]::IsNullOrWhiteSpace($Tag)) {
}
Assert-ImageTag $Tag
$BuildServices = if ($Service -eq "all") { $AllServices } else { @($Service) }
$BuildServices = switch ($Service) {
"all" { $AllServices }
"api-admin" { @("api", "admin") }
default { @($Service) }
}
$SaveImages = $BuildServices | ForEach-Object { "thebet365-${_}:${Tag}" }
if ([string]::IsNullOrWhiteSpace($Output)) {
@@ -64,7 +72,7 @@ if ([string]::IsNullOrWhiteSpace($Output)) {
}
if (-not $ExportOnly) {
Write-Host "==> 构建镜像: $($BuildServices -join ', ') (tag: $Tag)"
Write-Host "==> ????: $($BuildServices -join ', ') (tag: $Tag)"
$buildArgs = @(
"compose", "-f", $ComposeFile, "--env-file", $EnvFile, "build"
)
@@ -76,7 +84,7 @@ if (-not $ExportOnly) {
try {
$env:IMAGE_TAG = $Tag
& docker @buildArgs
if ($LASTEXITCODE -ne 0) { throw "docker build 失败,退出码 $LASTEXITCODE" }
if ($LASTEXITCODE -ne 0) { throw "docker build ?????? $LASTEXITCODE" }
} finally {
$env:IMAGE_TAG = $oldImageTag
}
@@ -84,9 +92,9 @@ if (-not $ExportOnly) {
$OutputPath = if ([System.IO.Path]::IsPathRooted($Output)) { $Output } else { Join-Path $Root $Output }
Write-Host "==> 导出镜像 ($Service) -> $OutputPath"
Write-Host "==> ???? ($Service) -> $OutputPath"
& docker save @SaveImages -o $OutputPath
if ($LASTEXITCODE -ne 0) { throw "docker save 失败,退出码 $LASTEXITCODE" }
if ($LASTEXITCODE -ne 0) { throw "docker save ?????? $LASTEXITCODE" }
$manifestPath = if ($OutputPath.EndsWith(".tar")) {
$OutputPath.Substring(0, $OutputPath.Length - 4) + ".manifest.txt"
@@ -117,7 +125,7 @@ $manifestLines = @(
)
foreach ($svc in $BuildServices) {
$imageId = (& docker image inspect "thebet365-${svc}:${Tag}" --format "{{.Id}}").Trim()
if ($Service -eq "all") {
if ($Service -eq "all" -or $Service -eq "api-admin") {
$manifestLines += "${svc}_image=thebet365-${svc}:${Tag}"
$manifestLines += "${svc}_image_id=$imageId"
} else {
@@ -128,13 +136,17 @@ foreach ($svc in $BuildServices) {
$manifestLines | Set-Content -Encoding UTF8 $manifestPath
$sizeMb = [math]::Round((Get-Item $OutputPath).Length / 1MB, 2)
Write-Host "完成: $OutputPath (${sizeMb} MB)"
Write-Host "??: $OutputPath (${sizeMb} MB)"
Write-Host "Manifest: $manifestPath"
$recreate = if ($Service -eq "all") { "api player admin" } else { $Service }
$recreate = switch ($Service) {
"all" { "api player admin" }
"api-admin" { "api admin" }
default { $Service }
}
Write-Host @"
:
????????:
docker load -i $([System.IO.Path]::GetFileName($OutputPath))
docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --force-recreate $recreate
"@

View File

@@ -25,7 +25,7 @@ default_tag() { echo "latest"; }
default_output() {
local svc="$1" image_tag="$2"
if [[ "$svc" == "all" ]]; then
if [[ "$svc" == "all" || "$svc" == "api-admin" ]]; then
echo "thebet365-images-${image_tag}.tar"
else
echo "thebet365-${svc}-${image_tag}.tar"
@@ -39,8 +39,8 @@ validate_tag() {
validate_service() {
case "$1" in
all|api|player|admin) ;;
*) echo "错误: --service 无效: $1(可选 api / player / admin / all" >&2; exit 1 ;;
all|api|player|admin|api-admin) ;;
*) echo "错误: --service 无效: $1(可选 api / player / admin / api-admin / all" >&2; exit 1 ;;
esac
}
@@ -49,7 +49,7 @@ usage() {
用法: docs/docker/build-and-export-images.sh [选项]
选项:
--service SVC api | player | admin | all默认 all
--service SVC api | player | admin | api-admin | all默认 all
--tag TAG 镜像 tag默认 latest
--use-cache 构建时使用 Docker 缓存(默认 --no-cache
--export-only 跳过构建,仅导出已有指定 tag 镜像
@@ -105,6 +105,8 @@ OUTPUT="${OUTPUT:-$(default_output "$SERVICE" "$TAG")}"
if [[ "$SERVICE" == "all" ]]; then
BUILD_SERVICES=(api player admin)
elif [[ "$SERVICE" == "api-admin" ]]; then
BUILD_SERVICES=(api admin)
else
BUILD_SERVICES=("$SERVICE")
fi
@@ -161,7 +163,7 @@ fi
echo "tar=$(basename "$OUTPUT_PATH")"
echo "tar_sha256=$CHECKSUM"
for svc in "${BUILD_SERVICES[@]}"; do
if [[ "$SERVICE" == "all" ]]; then
if [[ "$SERVICE" == "all" || "$SERVICE" == "api-admin" ]]; then
echo "${svc}_image=thebet365-${svc}:$TAG"
echo "${svc}_image_id=$(docker image inspect "thebet365-${svc}:$TAG" --format '{{.Id}}')"
else
@@ -182,6 +184,8 @@ echo "Manifest: $MANIFEST_PATH"
RECREATE="$SERVICE"
if [[ "$SERVICE" == "all" ]]; then
RECREATE="api player admin"
elif [[ "$SERVICE" == "api-admin" ]]; then
RECREATE="api admin"
fi
cat <<EOF

View File

@@ -0,0 +1,30 @@
@echo off
REM Build and export main player theme only.
REM Automatically switches to 'main' branch, builds, and restores original branch.
REM Usage: docs\docker\build-and-export-player-main.bat [options]
cd /d "%~dp0..\.."
set "BRANCH=main"
set "TAG=main"
set "ORIGINAL_BRANCH="
for /f "delims=" %%B in ('git rev-parse --abbrev-ref HEAD 2^>nul') do set "ORIGINAL_BRANCH=%%B"
if not defined ORIGINAL_BRANCH (
echo ERROR: Cannot determine current Git branch.
exit /b 1
)
echo [INFO] Switching to branch %BRANCH%...
git checkout %BRANCH% >nul 2>&1
if errorlevel 1 (
echo [ERROR] Cannot switch to branch %BRANCH%
exit /b 1
)
call docs\docker\build-and-export-images.bat --service player --tag %TAG% %*
set "BUILD_STATUS=%ERRORLEVEL%"
echo [INFO] Restoring original branch: %ORIGINAL_BRANCH%
git checkout %ORIGINAL_BRANCH% >nul 2>&1
exit /b %BUILD_STATUS%

View File

@@ -0,0 +1,30 @@
@echo off
REM Build and export theme-2 player theme only.
REM Automatically switches to 'theme-2' branch, builds, and restores original branch.
REM Usage: docs\docker\build-and-export-player-theme-2.bat [options]
cd /d "%~dp0..\.."
set "BRANCH=theme-2"
set "TAG=theme-2"
set "ORIGINAL_BRANCH="
for /f "delims=" %%B in ('git rev-parse --abbrev-ref HEAD 2^>nul') do set "ORIGINAL_BRANCH=%%B"
if not defined ORIGINAL_BRANCH (
echo ERROR: Cannot determine current Git branch.
exit /b 1
)
echo [INFO] Switching to branch %BRANCH%...
git checkout %BRANCH% >nul 2>&1
if errorlevel 1 (
echo [ERROR] Cannot switch to branch %BRANCH%
exit /b 1
)
call docs\docker\build-and-export-images.bat --service player --tag %TAG% %*
set "BUILD_STATUS=%ERRORLEVEL%"
echo [INFO] Restoring original branch: %ORIGINAL_BRANCH%
git checkout %ORIGINAL_BRANCH% >nul 2>&1
exit /b %BUILD_STATUS%

View File

@@ -0,0 +1,30 @@
@echo off
REM Build and export theme-3 player theme only.
REM Automatically switches to 'theme-3' branch, builds, and restores original branch.
REM Usage: docs\docker\build-and-export-player-theme-3.bat [options]
cd /d "%~dp0..\.."
set "BRANCH=theme-3"
set "TAG=theme-3"
set "ORIGINAL_BRANCH="
for /f "delims=" %%B in ('git rev-parse --abbrev-ref HEAD 2^>nul') do set "ORIGINAL_BRANCH=%%B"
if not defined ORIGINAL_BRANCH (
echo ERROR: Cannot determine current Git branch.
exit /b 1
)
echo [INFO] Switching to branch %BRANCH%...
git checkout %BRANCH% >nul 2>&1
if errorlevel 1 (
echo [ERROR] Cannot switch to branch %BRANCH%
exit /b 1
)
call docs\docker\build-and-export-images.bat --service player --tag %TAG% %*
set "BUILD_STATUS=%ERRORLEVEL%"
echo [INFO] Restoring original branch: %ORIGINAL_BRANCH%
git checkout %ORIGINAL_BRANCH% >nul 2>&1
exit /b %BUILD_STATUS%

View File

@@ -0,0 +1,30 @@
@echo off
REM Build and export theme-4 player theme only.
REM Automatically switches to 'theme-4' branch, builds, and restores original branch.
REM Usage: docs\docker\build-and-export-player-theme-4.bat [options]
cd /d "%~dp0..\.."
set "BRANCH=theme-4"
set "TAG=theme-4"
set "ORIGINAL_BRANCH="
for /f "delims=" %%B in ('git rev-parse --abbrev-ref HEAD 2^>nul') do set "ORIGINAL_BRANCH=%%B"
if not defined ORIGINAL_BRANCH (
echo ERROR: Cannot determine current Git branch.
exit /b 1
)
echo [INFO] Switching to branch %BRANCH%...
git checkout %BRANCH% >nul 2>&1
if errorlevel 1 (
echo [ERROR] Cannot switch to branch %BRANCH%
exit /b 1
)
call docs\docker\build-and-export-images.bat --service player --tag %TAG% %*
set "BUILD_STATUS=%ERRORLEVEL%"
echo [INFO] Restoring original branch: %ORIGINAL_BRANCH%
git checkout %ORIGINAL_BRANCH% >nul 2>&1
exit /b %BUILD_STATUS%

View File

@@ -14,8 +14,14 @@
|------|----------|
| `docs/docker/build-and-export-images.bat` | WindowsCMD构建全部 |
| `docs/docker/build-and-export-api.bat` | Windows仅 api可双击 |
| `docs/docker/build-and-export-player.bat` | Windows仅 player可双击 |
| `docs/docker/build-and-export-player.bat` | Windows单分支 player可双击 |
| `docs/docker/build-and-export-player-main.bat` | Windows仅主站/暗金主题,自动切分支) |
| `docs/docker/build-and-export-player-theme-2.bat` | Windows仅 theme-2 蓝白主题,自动切分支) |
| `docs/docker/build-and-export-player-theme-3.bat` | Windows仅 theme-3 移动主题,自动切分支) |
| `docs/docker/build-and-export-player-theme-4.bat` | Windows仅 theme-4 海军蓝极简,自动切分支) |
| `docs/docker/build-and-export-admin.bat` | Windows仅 admin可双击 |
| `docs/docker/build-and-export-all-themes.ps1` | Windows**四套主题 player 一键打包**,自动切分支,推荐) |
| `docs/docker/build-and-export-all-themes.bat` | Windows同上薄包装启动器双击可用 |
| `docs/docker/build-and-export-images.ps1` | WindowsPowerShell |
| `docs/docker/build-and-export-images.sh` | Linux / macOS / Git Bash |
@@ -118,6 +124,81 @@ docs\docker\build-and-export-images.bat --export-only
---
## 三、四套主题玩家端打包
> 关联文档:[四套主题Docker部署任务.md](../四套主题Docker部署任务.md)「阶段 C」
### 一键打包(推荐)
脚本会自动:切到各主题分支 → 构建 player 镜像 → 导出 tar → 切回原分支 → 构建 api/admin。
> **为何用 PowerShell** 本脚本仅存在于 `main` 分支;执行过程中会 `git checkout theme-*`CMD 批处理会从磁盘逐行读取切分支后脚本文件消失会导致中断。PowerShell 启动时已将整份脚本载入内存,可安全跨分支执行。`.bat` 仅为薄包装,内部转调 `.ps1`。
```powershell
cd C:\path\to\thebet365
.\docs\docker\build-and-export-all-themes.ps1
```
或双击 / CMD
```bat
docs\docker\build-and-export-all-themes.bat
```
可选参数PowerShell 与 bat 均支持):
| 参数 | 说明 |
|------|------|
| `-UseCache` / `--use-cache` | 使用 Docker 层缓存,加快重复构建速度 |
| `-ExportOnly` / `--export-only` | 跳过构建,仅导出本地已有镜像 |
| `-SkipApiAdmin` / `--skip-api-admin` | 只打四套 player跳过 api/admin |
| `-SkipBundle` / `--skip-bundle` | 跳过六合一包,仅保留分散 tar |
### 产物
| tar 文件 | 加载后镜像名 |
|----------|----------------|
| `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` |
| `thebet365-images-latest.tar` | `thebet365-api:latest` + `thebet365-admin:latest` |
| **`thebet365-full-themes-latest.tar`** | **上面全部 6 个镜像(上传这一个即可)** |
也可单独导出六合一包(本地镜像已齐时):
```bat
docs\docker\build-and-export-images.bat --service full-themes --export-only --tag latest
```
### 分支要求
- 执行前确保 `main``theme-2``theme-3``theme-4` 四个分支在本地均已拉取
- 工作区若有未提交变更,脚本会给出 WARN建议先 `git stash` 后再运行
- 若某一分支切换失败,该分支会被跳过并标记失败,其余分支仍继续构建
### 手动逐一打包(有明确失败时)
```bat
git checkout main
docs\docker\build-and-export-images.bat --service player --tag main
git checkout theme-2
docs\docker\build-and-export-images.bat --service player --tag theme-2
git checkout theme-3
docs\docker\build-and-export-images.bat --service player --tag theme-3
git checkout theme-4
docs\docker\build-and-export-images.bat --service player --tag theme-4
REM 最后打 api + admin
git checkout main
docs\docker\build-and-export-images.bat --tag latest
```
---
## 四、构建产物
| 镜像名 | 说明 |

View File

@@ -37,11 +37,11 @@
### 阶段 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` 文档索引增加本文链接
- [x] **A1** 新增 `docker-compose.themes.yml`player2 / player3 / player4 服务定义)
- [x] **A2** 更新 `.env.docker.example``PLAYER2_*` ~ `PLAYER4_*``CORS_ORIGINS` 示例)
- [x] **A3** 更新 `docker-compose.prod.yml``player` 使用 `PLAYER_IMAGE_TAG`(与 `IMAGE_TAG` 解耦,见下文片段)
- [x] **A4**(可选)在 `docs/Docker部署指南.md` 增加「多主题」小节链接到本文
- [x] **A5**(可选)在 `AGENTS.md` 文档索引增加本文链接
### 阶段 B — 各主题分支同步 main 功能(发版前每个 theme 分支各做一遍)
@@ -276,7 +276,18 @@ pnpm dev:player # http://localhost:5173 目视确认仍是本主题皮
## 六、构建与上传镜像
### 6.1 四个 player 镜像Windows CMD
### 6.1 四个 player 镜像
**一键打包(推荐,仅 `main` 分支提供脚本):**
```powershell
cd C:\path\to\thebet365
.\docs\docker\build-and-export-all-themes.ps1 -SkipApiAdmin
```
`docs\docker\build-and-export-all-themes.bat --skip-api-admin`(内部转调 PowerShell避免切分支后 bat 从磁盘消失)。
**手动逐步Windows CMD**
```bat
cd C:\path\to\thebet365