补回 swagger(Swashbuckle 6.6.2 + XML 注释)、socket.html 入库、固化本机构建与服务器替换脚本
- Directory.Build.props: 生成 XML 注释供 Swagger 展示中文说明 - AdminHost/FishGameHost: 接入 Swashbuckle,路由 adminswagger / roomswagger - 后台前端 config.js 改为同源相对地址,适配反向代理 - socket.html(SignalR 协议文档)纳入仓库 - deploy/: 本机构建(容器内编译,规避杀软)、服务器替换脚本、Nginx 配置
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# ============================================================
|
||||
# 容器内编译 + 打包镜像(多阶段)
|
||||
#
|
||||
# 用途:本机构建时使用。编译发生在 Docker 的 Linux 虚拟机内,
|
||||
# Windows 上的杀软(如火绒)无法拦截中间产物 AdminApi.dll 等,
|
||||
# 可规避「dll 生成后被删除导致 MSB3030」的问题。
|
||||
#
|
||||
# 由 deploy/build-local.ps1 调用,构建上下文 = 仓库根目录:
|
||||
# docker build -f deploy/Dockerfile.build \
|
||||
# --build-arg PROJECT=<项目csproj相对路径,正斜杠> \
|
||||
# --build-arg SVC_NAME=<服务名,如 admin> \
|
||||
# --build-arg APP_DLL=<入口dll,如 AdminHost.dll> \
|
||||
# --build-arg PUBLIC_HOST=<捕鱼对外地址> \
|
||||
# -t 0524<服务名> .
|
||||
# ============================================================
|
||||
ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0
|
||||
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
|
||||
# ---------- 阶段一:编译发布 ----------
|
||||
FROM ${SDK_IMAGE} AS build
|
||||
ARG PROJECT
|
||||
ARG SVC_NAME
|
||||
ARG PUBLIC_HOST=localhost
|
||||
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||
DOTNET_NOLOGO=1
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
# 编译(不依赖 PUBLIC_HOST,可复用构建缓存)
|
||||
RUN dotnet publish "${PROJECT}" -c Release -o /out --nologo
|
||||
|
||||
# 部署配置覆盖 + 对外地址替换(单独一层:日后只改地址可秒级重建,无需重编译)
|
||||
RUN set -eux; \
|
||||
# 部署版配置覆盖(各 Host 的 Config/system.json 默认不随发布输出)
|
||||
mkdir -p /out/Config; \
|
||||
cp "/src/deploy/config/${SVC_NAME}/system.json" /out/Config/system.json; \
|
||||
cp /src/nlog.config /out/nlog.config; \
|
||||
# 捕鱼对局层:替换客户端要访问的对外地址
|
||||
sed -i "s|__PUBLIC_HOST__|${PUBLIC_HOST}|g" /out/Config/system.json; \
|
||||
# 网关:ocelot 下游地址由 localhost:524x 改写为「容器名:80」
|
||||
if [ -f /out/ocelot.json ]; then \
|
||||
sed -i \
|
||||
-e 's|{ "Host": "localhost", "Port": 5241 }|{ "Host": "0524admin", "Port": 80 }|' \
|
||||
-e 's|{ "Host": "localhost", "Port": 5245 }|{ "Host": "0524user", "Port": 80 }|' \
|
||||
-e 's|{ "Host": "localhost", "Port": 5247 }|{ "Host": "0524customer", "Port": 80 }|' \
|
||||
-e 's|{ "Host": "localhost", "Port": 5242 }|{ "Host": "0524chess", "Port": 80 }|' \
|
||||
-e 's|{ "Host": "localhost", "Port": 5244 }|{ "Host": "0524fishroom", "Port": 80 }|' \
|
||||
-e 's|{ "Host": "localhost", "Port": 5248 }|{ "Host": "0524fishgame", "Port": 80 }|' \
|
||||
/out/ocelot.json; \
|
||||
fi
|
||||
|
||||
# ---------- 阶段二:运行时镜像 ----------
|
||||
FROM ${RUNTIME_IMAGE}
|
||||
ARG APP_DLL
|
||||
|
||||
ENV APP_DLL=${APP_DLL} \
|
||||
ASPNETCORE_URLS=http://+:80 \
|
||||
ASPNETCORE_ENVIRONMENT=Production \
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /out .
|
||||
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["sh", "-c", "exec dotnet ${APP_DLL}"]
|
||||
@@ -0,0 +1,157 @@
|
||||
<#
|
||||
本机构建镜像并导出 tar(编译在 Docker 容器内进行)
|
||||
------------------------------------------------------------------
|
||||
适用场景:服务器无法访问 mcr.microsoft.com;且本机杀软(如火绒)
|
||||
会拦截/删除编译产物(AdminApi.dll 等)。
|
||||
编译与镜像构建全部在容器内完成,Windows 杀软无法拦截中间产物。
|
||||
|
||||
用法:
|
||||
# 1) 全量构建(7 个服务)
|
||||
powershell -ExecutionPolicy Bypass -File deploy\build-local.ps1 -PublicHost game.xxx.com
|
||||
|
||||
# 2) 只构建/打包指定服务(多个逗号分隔)
|
||||
powershell -ExecutionPolicy Bypass -File deploy\build-local.ps1 -PublicHost game.xxx.com -Services admin,fishgame
|
||||
|
||||
# 3) 按「改动的子项目」自动推导需要重建的服务(推荐,避免漏编)
|
||||
powershell -ExecutionPolicy Bypass -File deploy\build-local.ps1 -PublicHost game.xxx.com -ForProject FishGameService
|
||||
|
||||
产物:deploy\dist\ 下的 tar(只含本次构建的镜像)
|
||||
#>
|
||||
param(
|
||||
[string]$PublicHost = "CHANGE_ME",
|
||||
[string]$Tag = (Get-Date -Format "yyyyMMdd-HHmm"),
|
||||
[string[]]$Services = @(),
|
||||
[string]$ForProject = "",
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = Split-Path -Parent $PSScriptRoot
|
||||
$Deploy = Join-Path $Root "deploy"
|
||||
$Dist = Join-Path $Deploy "dist"
|
||||
|
||||
function Info($m) { Write-Host "[本地构建] $m" -ForegroundColor Green }
|
||||
function Warn($m) { Write-Host "[警告] $m" -ForegroundColor Yellow }
|
||||
|
||||
# ---------- 服务定义:服务名 -> 项目文件 / 入口 dll / Host 项目名 ----------
|
||||
$serviceMap = [ordered]@{
|
||||
gateway = @{ proj = "Gateway/Gateway.csproj"; dll = "Gateway.dll"; host = "Gateway" }
|
||||
admin = @{ proj = "AdminSrc/AdminHost/AdminHost.csproj"; dll = "AdminHost.dll"; host = "AdminHost" }
|
||||
user = @{ proj = "UserSrc/UserHost/UserHost.csproj"; dll = "UserHost.dll"; host = "UserHost" }
|
||||
customer = @{ proj = "CustomerSrc/CustomerHost/CustomerHost.csproj"; dll = "CustomerHost.dll"; host = "CustomerHost" }
|
||||
chess = @{ proj = "ChessGameHost/ChessGameHost.csproj"; dll = "ChessGameHost.dll"; host = "ChessGameHost" }
|
||||
fishroom = @{ proj = "FishGameSrc/FishRoomHost/FishRoomHost.csproj"; dll = "FishRoomHost.dll"; host = "FishRoomHost" }
|
||||
fishgame = @{ proj = "FishGameSrc/FishGameHost/FishGameHost.csproj"; dll = "FishGameHost.dll"; host = "FishGameHost" }
|
||||
}
|
||||
|
||||
# ---------- 依赖图:改动某项目 -> 需要重建的服务 ----------
|
||||
function Get-AffectedServices([string]$changedProject) {
|
||||
$refs = @{}
|
||||
Get-ChildItem -Path $Root -Recurse -Filter *.csproj -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$txt = [IO.File]::ReadAllText($_.FullName)
|
||||
$deps = @()
|
||||
foreach ($m in [regex]::Matches($txt, 'ProjectReference\s+Include="([^"]+)"')) {
|
||||
$deps += [IO.Path]::GetFileNameWithoutExtension($m.Groups[1].Value)
|
||||
}
|
||||
$refs[$_.BaseName] = $deps
|
||||
}
|
||||
$result = @()
|
||||
foreach ($name in $serviceMap.Keys) {
|
||||
$hostName = $serviceMap[$name].host
|
||||
if ($hostName -eq $changedProject) { $result += $name; continue }
|
||||
$seen = New-Object System.Collections.Generic.HashSet[string]
|
||||
$stack = New-Object System.Collections.Stack
|
||||
foreach ($d in @($refs[$hostName])) { if ($d) { [void]$stack.Push($d) } }
|
||||
while ($stack.Count -gt 0) {
|
||||
$cur = $stack.Pop()
|
||||
if (-not $seen.Add($cur)) { continue }
|
||||
foreach ($d in @($refs[$cur])) { if ($d) { [void]$stack.Push($d) } }
|
||||
}
|
||||
if ($seen.Contains($changedProject)) { $result += $name }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
# ---------- 1) 决定要构建哪些服务 ----------
|
||||
if ($ForProject) {
|
||||
$targets = @(Get-AffectedServices $ForProject)
|
||||
if ($targets.Count -eq 0) {
|
||||
throw "没有任何 Host 引用项目 '$ForProject'(项目名不带 .csproj)。请检查拼写。"
|
||||
}
|
||||
Info ("按项目 '$ForProject' 推导出需重建的服务: " + ($targets -join ', '))
|
||||
} elseif ($Services.Count -gt 0) {
|
||||
$svcList = @()
|
||||
foreach ($s in $Services) { $svcList += ($s -split ',') }
|
||||
$svcList = @($svcList | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
foreach ($s in $svcList) {
|
||||
if (-not $serviceMap.Contains($s)) { throw "未知服务名 '$s'。可选: $($serviceMap.Keys -join ', ')" }
|
||||
}
|
||||
$targets = @($svcList)
|
||||
Info ("指定构建服务: " + ($targets -join ', '))
|
||||
} else {
|
||||
$targets = @($serviceMap.Keys)
|
||||
Info "全量构建 7 个服务"
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Info ("[DryRun] 将构建的服务: " + ($targets -join ', '))
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---------- 2) 前置校验 + 准备基础镜像(经镜像站拉取并打回官方 tag)----------
|
||||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { throw "未找到 docker,请先安装并启动 Docker Desktop" }
|
||||
$prevEap = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
docker info 2>&1 | Out-Null
|
||||
$dockerOk = ($LASTEXITCODE -eq 0)
|
||||
$ErrorActionPreference = $prevEap
|
||||
if (-not $dockerOk) { throw "Docker 引擎未就绪,请先启动 Docker Desktop 并等托盘图标变绿" }
|
||||
if ($PublicHost -eq "CHANGE_ME") {
|
||||
Warn "未指定 -PublicHost,捕鱼对局层地址会被写成 CHANGE_ME,客户端将连不上"
|
||||
}
|
||||
$baseImages = [ordered]@{
|
||||
"mcr.microsoft.com/dotnet/sdk:8.0" = "mcr.m.daocloud.io/dotnet/sdk:8.0"
|
||||
"mcr.microsoft.com/dotnet/aspnet:8.0" = "mcr.m.daocloud.io/dotnet/aspnet:8.0"
|
||||
}
|
||||
foreach ($official in $baseImages.Keys) {
|
||||
docker image inspect $official *> $null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Info "本地缺少 $official,经镜像站拉取 ..."
|
||||
docker pull $baseImages[$official]
|
||||
if ($LASTEXITCODE -ne 0) { throw "拉取 $official 失败" }
|
||||
docker tag $baseImages[$official] $official
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- 3) 构建镜像(编译在容器内)----------
|
||||
foreach ($name in $targets) {
|
||||
$s = $serviceMap[$name]
|
||||
Info "构建镜像 0524$name ..."
|
||||
docker build -f (Join-Path $Deploy "Dockerfile.build") `
|
||||
--build-arg "PROJECT=$($s.proj)" `
|
||||
--build-arg "SVC_NAME=$name" `
|
||||
--build-arg "APP_DLL=$($s.dll)" `
|
||||
--build-arg "PUBLIC_HOST=$PublicHost" `
|
||||
-t "0524$name" $Root
|
||||
if ($LASTEXITCODE -ne 0) { throw "构建 0524$name 失败" }
|
||||
}
|
||||
|
||||
# ---------- 4) 导出 tar(只含本次构建的镜像)----------
|
||||
New-Item -ItemType Directory -Path $Dist -Force | Out-Null
|
||||
$isAll = ($targets.Count -eq $serviceMap.Count)
|
||||
$suffix = if ($isAll) { "" } else { "-" + ($targets -join "-") }
|
||||
$tar = Join-Path $Dist "buyu-images$suffix-$Tag.tar"
|
||||
$tags = @($targets | ForEach-Object { "0524$_" })
|
||||
Info ("导出镜像到 $tar ...(含: " + ($tags -join ', ') + ")")
|
||||
docker save -o $tar @tags
|
||||
if ($LASTEXITCODE -ne 0) { throw "导出镜像失败" }
|
||||
$sizeMB = [math]::Round((Get-Item $tar).Length / 1MB, 1)
|
||||
|
||||
Write-Host ""
|
||||
Info "完成!镜像包: $tar ($sizeMB MB)"
|
||||
Write-Host "下一步 —— 在本机上传到服务器:" -ForegroundColor Cyan
|
||||
Write-Host " ssh root@<服务器IP> `"mkdir -p /opt/buyu/deploy/dist`"" -ForegroundColor Cyan
|
||||
Write-Host " scp `"$tar`" root@<服务器IP>:/opt/buyu/deploy/dist/" -ForegroundColor Cyan
|
||||
Write-Host "再登录服务器执行(只重启本次涉及的服务):" -ForegroundColor Cyan
|
||||
Write-Host " cd /opt/buyu" -ForegroundColor Cyan
|
||||
Write-Host (" bash deploy/load-and-up.sh deploy/dist/" + (Split-Path $tar -Leaf) + " " + ($targets -join ' ')) -ForegroundColor Cyan
|
||||
@@ -0,0 +1,70 @@
|
||||
<#
|
||||
本机拉取中间件镜像并导出 tar
|
||||
------------------------------------------------------------------
|
||||
适用场景:服务器访问不了 docker.io(拉 mongo/redis/postgres/rabbitmq 报
|
||||
"error from registry: unknown error"),改为在本机拉好后传过去。
|
||||
|
||||
用法:
|
||||
powershell -ExecutionPolicy Bypass -File deploy\build-middleware.ps1
|
||||
产物:
|
||||
deploy\dist\middleware.tar
|
||||
|
||||
随后在服务器执行:
|
||||
docker load -i middleware.tar
|
||||
docker compose -f deploy/docker-compose.yml up -d
|
||||
#>
|
||||
param(
|
||||
[string]$OutFile = "",
|
||||
[string[]]$Images = @("redis:7", "mongo:6.0", "postgres:16", "rabbitmq:3.13-management")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = Split-Path -Parent $PSScriptRoot
|
||||
$Dist = Join-Path $Root "deploy\dist"
|
||||
if (-not $OutFile) { $OutFile = Join-Path $Dist "middleware.tar" }
|
||||
|
||||
$mirrors = @("docker.m.daocloud.io", "docker.1ms.run", "docker.xuanyuan.me", "hub.rat.dev")
|
||||
|
||||
function Info($m) { Write-Host "[中间件] $m" -ForegroundColor Green }
|
||||
|
||||
function Invoke-Quiet([scriptblock]$sb) {
|
||||
$prev = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
& $sb 2>&1 | Out-Null
|
||||
$code = $LASTEXITCODE
|
||||
$ErrorActionPreference = $prev
|
||||
return $code
|
||||
}
|
||||
|
||||
# ---------- 引擎检查 ----------
|
||||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { throw "未找到 docker,请先安装并启动 Docker Desktop" }
|
||||
if ((Invoke-Quiet { docker info }) -ne 0) { throw "Docker 引擎未就绪,请先启动 Docker Desktop 并等托盘图标变绿" }
|
||||
|
||||
# ---------- 拉取镜像(逐个镜像站回退)----------
|
||||
foreach ($img in $Images) {
|
||||
if ((Invoke-Quiet { docker image inspect $img }) -eq 0) { Info "已存在,跳过: $img"; continue }
|
||||
|
||||
$done = $false
|
||||
foreach ($m in $mirrors) {
|
||||
Info "拉取 $img (经 $m)..."
|
||||
if ((Invoke-Quiet { docker pull "$m/library/$img" }) -eq 0) {
|
||||
Invoke-Quiet { docker tag "$m/library/$img" $img } | Out-Null
|
||||
Info "OK: $img <= $m"
|
||||
$done = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $done) { throw "镜像 $img 在所有镜像站都拉取失败,请稍后重试" }
|
||||
}
|
||||
|
||||
# ---------- 导出 tar ----------
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path $OutFile) | Out-Null
|
||||
Info "导出到 $OutFile (体积较大,稍候)..."
|
||||
if ((Invoke-Quiet { docker save -o $OutFile @Images }) -ne 0) { throw "导出镜像失败" }
|
||||
$mb = [math]::Round((Get-Item $OutFile).Length / 1MB, 1)
|
||||
|
||||
Info "完成!$OutFile ($mb MB)"
|
||||
Write-Host "下一步 —— 本机上传:" -ForegroundColor Cyan
|
||||
Write-Host " scp `"$OutFile`" root@<服务器IP>:/opt/buyu/" -ForegroundColor Cyan
|
||||
Write-Host "服务器执行:" -ForegroundColor Cyan
|
||||
Write-Host " cd /opt/buyu && docker load -i middleware.tar && docker compose -f deploy/docker-compose.yml up -d" -ForegroundColor Cyan
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# 服务器侧:加载镜像包并按需启动(无需联网、无需 sdk 镜像)
|
||||
#
|
||||
# 用法:
|
||||
# # 1) 加载完整包并启动全部
|
||||
# bash deploy/load-and-up.sh deploy/dist/buyu-images-<tag>.tar
|
||||
#
|
||||
# # 2) 只加载某个服务的包并只重启该服务(增量更新)
|
||||
# bash deploy/load-and-up.sh deploy/dist/buyu-images-admin-<tag>.tar admin
|
||||
# bash deploy/load-and-up.sh deploy/dist/buyu-images-admin-fishgame-<tag>.tar admin fishgame
|
||||
#
|
||||
# 说明:镜像内的配置(网关 ocelot 下游改写、捕鱼对外地址)已在构建时固化;
|
||||
# 服务器只做「加载镜像 + 按需重启」,不涉及编译。
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
COMPOSE="$ROOT/deploy/docker-compose.yml"
|
||||
TAR="${1:-}"
|
||||
shift || true
|
||||
SERVICES=("$@")
|
||||
|
||||
log() { echo -e "\033[1;32m[部署]\033[0m $*"; }
|
||||
|
||||
[ -n "$TAR" ] || { echo "用法: bash deploy/load-and-up.sh <镜像包.tar> [服务名...]"; exit 1; }
|
||||
[ -f "$TAR" ] || { echo "找不到镜像包: $TAR"; exit 1; }
|
||||
command -v docker >/dev/null 2>&1 || { echo "未检测到 docker,请先安装 Docker"; exit 1; }
|
||||
docker compose version >/dev/null 2>&1 || { echo "未检测到 docker compose v2"; exit 1; }
|
||||
|
||||
log "1/3 加载镜像(tar 较大,需要一点时间)..."
|
||||
docker load -i "$TAR"
|
||||
|
||||
log "2/3 启动容器..."
|
||||
mkdir -p "$ROOT/deploy"/logs/{gateway,admin,user,customer,chess,fishroom,fishgame}
|
||||
mkdir -p "$ROOT/deploy"/uploads/{admin,user,customer}
|
||||
|
||||
if [ ${#SERVICES[@]} -gt 0 ]; then
|
||||
log "仅重建/重启: ${SERVICES[*]}"
|
||||
docker compose -f "$COMPOSE" up -d --force-recreate "${SERVICES[@]}"
|
||||
CHECK=("${SERVICES[@]}")
|
||||
else
|
||||
docker compose -f "$COMPOSE" up -d
|
||||
CHECK=(gateway admin user customer chess fishroom fishgame)
|
||||
fi
|
||||
|
||||
log "3/3 健康检查..."
|
||||
sleep 6
|
||||
declare -A PORT=( [gateway]=8001 [admin]=5241 [user]=5245 [customer]=5247 [chess]=5242 [fishroom]=5244 [fishgame]=5248 )
|
||||
for name in "${CHECK[@]}"; do
|
||||
port="${PORT[$name]:-}"
|
||||
[ -n "$port" ] || continue
|
||||
ok=""
|
||||
for _ in $(seq 1 20); do
|
||||
if curl -fsS -o /dev/null --max-time 2 "http://localhost:$port/health"; then ok="✔"; break; fi
|
||||
sleep 2
|
||||
done
|
||||
echo " ${ok:-✘} $name http://localhost:$port/health"
|
||||
done
|
||||
|
||||
echo ""
|
||||
log "完成。网关统一入口: http://<服务器IP或域名>:8001"
|
||||
@@ -0,0 +1,49 @@
|
||||
# ============================================================
|
||||
# 捕鱼/棋牌平台 - Nginx 反向代理
|
||||
# 放到宝塔站点目录(该目录下的 *.conf 被全局 nginx.conf 的 http 块 include):
|
||||
# /www/server/panel/vhost/nginx/buyu.conf
|
||||
# 生效:nginx -t && systemctl reload nginx
|
||||
# 说明:所有后端都在本机(Docker 映射到 127.0.0.1),Nginx 只代理到本机端口。
|
||||
# ============================================================
|
||||
|
||||
# WebSocket 升级映射(名字加 buyu 前缀,避免与宝塔/其它站点冲突)
|
||||
map $http_upgrade $buyu_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name 47.109.132.12; # 换成你的域名或服务器公网 IP
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
# 管理后台静态页面(AdminHost 的 wwwroot,访问 http://<IP>/admin/index.html)
|
||||
location /admin/ {
|
||||
proxy_pass http://127.0.0.1:5241;
|
||||
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;
|
||||
}
|
||||
|
||||
# 上传的图片(公告图、客服聊天图等)
|
||||
location /uploads/ {
|
||||
proxy_pass http://127.0.0.1:5241;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# 其余全部走网关(HTTP 接口 + SignalR WebSocket)
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade; # SignalR 必需
|
||||
proxy_set_header Connection $buyu_upgrade; # SignalR 必需
|
||||
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;
|
||||
proxy_read_timeout 3600s; # 长连接不要被掐断
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# 0524 平台:按现有约定替换/回滚单个(或多个)服务容器
|
||||
# 位置:服务器 /mnt/web/0524-replace.sh
|
||||
#
|
||||
# 用法:
|
||||
# bash 0524-replace.sh admin # 用 0524admin:latest 重建 admin
|
||||
# bash 0524-replace.sh fishgame admin # 一次替换多个
|
||||
# TAG=dual-20261008 bash 0524-replace.sh admin # 回滚 admin 到旧镜像
|
||||
#
|
||||
# 约定(与线上既有部署保持一致):
|
||||
# - 网络 webapi_backend
|
||||
# - 端口只绑 127.0.0.1
|
||||
# - 配置挂载 /mnt/web/config -> /app/Config(全服务共用)
|
||||
# - 日志挂载 /mnt/web/logs -> /app/logs
|
||||
# - ASPNETCORE_URLS 按各服务内部端口
|
||||
# ============================================================
|
||||
set -u
|
||||
|
||||
NET=webapi_backend
|
||||
CFG=/mnt/web/config
|
||||
LOGS=/mnt/web/logs
|
||||
UPLOADS=/mnt/web/uploads
|
||||
TAG="${TAG:-latest}"
|
||||
ROLLBACK_TAG="${ROLLBACK_TAG:-dual-20261008}"
|
||||
|
||||
ALL="gateway admin user customer chess fishroom fishgame"
|
||||
|
||||
# 容器内部监听端口
|
||||
internal_port() {
|
||||
case "$1" in
|
||||
gateway) echo 8001 ;;
|
||||
admin) echo 5241 ;;
|
||||
user) echo 5245 ;;
|
||||
customer) echo 5247 ;;
|
||||
chess) echo 5242 ;;
|
||||
fishroom) echo 5244 ;;
|
||||
fishgame) echo 8001 ;;
|
||||
esac
|
||||
}
|
||||
# 宿主机映射端口(只绑 127.0.0.1)
|
||||
host_port() {
|
||||
case "$1" in
|
||||
gateway) echo 8001 ;;
|
||||
admin) echo 5241 ;;
|
||||
user) echo 5245 ;;
|
||||
customer) echo 5247 ;;
|
||||
chess) echo 5242 ;;
|
||||
fishroom) echo 5244 ;;
|
||||
fishgame) echo 5248 ;;
|
||||
esac
|
||||
}
|
||||
# 额外的 -v/-e 参数
|
||||
extra_args() {
|
||||
case "$1" in
|
||||
gateway) echo "-v $CFG/ocelot.json:/app/ocelot.json -v $UPLOADS:/app/uploads" ;;
|
||||
admin) echo "-v $UPLOADS:/app/wwwroot/uploads" ;;
|
||||
customer) echo "-v $UPLOADS:/app/wwwroot/uploads" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
# INSTANCE_ID(日志文件名里用于区分实例)
|
||||
instance_id() {
|
||||
case "$1" in
|
||||
gateway) echo "" ;;
|
||||
fishgame) echo "1" ;;
|
||||
*) echo "Container_Port_$(host_port "$1")" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_container() {
|
||||
local svc=$1 tag=$2
|
||||
local ip hp inst extra
|
||||
ip=$(internal_port "$svc"); hp=$(host_port "$svc"); inst=$(instance_id "$svc"); extra=$(extra_args "$svc")
|
||||
|
||||
docker rm -f "0524$svc" >/dev/null 2>&1
|
||||
local envar="ASPNETCORE_URLS=http://+:$ip"
|
||||
[ -n "$inst" ] && envar="$envar -e INSTANCE_ID=$inst"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
docker run -d --name "0524$svc" --restart always --network "$NET" \
|
||||
-e $envar $extra \
|
||||
-p "127.0.0.1:$hp:$ip" \
|
||||
-v "$CFG:/app/Config" -v "$LOGS:/app/logs" -v "$CFG/nlog.config:/app/nlog.config" \
|
||||
"0524$svc:$tag" >/dev/null
|
||||
}
|
||||
|
||||
health() {
|
||||
local hp; hp=$(host_port "$1")
|
||||
for _ in $(seq 1 15); do
|
||||
local c; c=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$hp/health")
|
||||
[ "$c" = "200" ] && return 0
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
replace_one() {
|
||||
local svc=$1
|
||||
echo "=== 替换 $svc (0524$svc:$TAG)==="
|
||||
run_container "$svc" "$TAG"
|
||||
if health "$svc"; then
|
||||
echo " ✔ $svc 健康检查通过"
|
||||
else
|
||||
echo " ✘ $svc 未通过,输出日志并回滚到 $ROLLBACK_TAG"
|
||||
docker logs --tail 40 "0524$svc" 2>&1 | tail -20
|
||||
run_container "$svc" "$ROLLBACK_TAG"
|
||||
health "$svc" && echo " ↩ 已回滚,$svc 恢复正常" || echo " !! 回滚后仍不健康,请人工介入"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "用法: bash 0524-replace.sh <服务...> (可选服务: $ALL)"
|
||||
echo " TAG=dual-20261008 bash 0524-replace.sh <服务...> # 回滚"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for svc in "$@"; do
|
||||
case " $ALL " in
|
||||
*" $svc "*) replace_one "$svc" ;;
|
||||
*) echo "未知服务: $svc(可选: $ALL)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== 当前业务容器 ==="
|
||||
docker ps --format '{{.Names}} => {{.Image}} => {{.Status}}' | grep 0524
|
||||
Reference in New Issue
Block a user