Some checks failed
CI / verify (push) Has been cancelled
Sync the CDN origin, five-node cache policy, audio compatibility routing, validation, rollback, and current branch baseline. Made-with: Proma
802 lines
36 KiB
Markdown
802 lines
36 KiB
Markdown
# frontend-miniapp 正式环境部署手册
|
||
|
||
最后更新:2026-07-28(同步 CDN 源站域名、五节点缓存策略、讲解音频兼容路由及 CDN 验收/回滚流程)
|
||
|
||
## 适用范围
|
||
|
||
本文档适用于将本地仓库 `E:\深圳自然博物馆\workplace\frontend-miniapp` 的 H5 产物部署到深圳自然博物馆正式环境。
|
||
|
||
- 正式域名:`https://guide.sznhmuseum.org.cn`
|
||
- CDN 源站域名:`https://guide-real.sznhmuseum.org.cn`(只供 CDN 回源和源站排障,不作为游客入口)
|
||
- 源站公网 IP:`218.17.240.244`
|
||
- 主节点:`172.20.14.21`
|
||
- 部署目录:`/data/nginx/html/mobile`
|
||
- Nginx 容器:`sgs-nginx`(`nginx/1.30.3`,Docker host 网络)
|
||
- Nginx 生效配置目录:`/data/nginx/conf.d`(容器内 `/etc/nginx/conf.d`,只读挂载)
|
||
- 同步脚本:`/data/scripts/sync-frontend-nodes.sh`
|
||
- Nginx 配置变更记录:2026-07-28,`.21` 公网入口及五台 `17083` 已完成 CDN 源站、分层缓存、静态资源 404 防 SPA 回退和音频兼容路由适配
|
||
- 同步节点:`172.20.14.23`、`172.20.14.26`、`172.20.14.27`、`172.20.14.33`
|
||
- 远程仓库:`https://git.whaoyue.com/lyf/frontend-miniapp.git`
|
||
- 本次文档同步基线:分支 `codex/import-sgs-frontend-mobile-20260727`,提交 `fc93b8d7d32a208a037be576ce3b9da2db3c1fe3`(仅表示仓库同步基线,不表示该提交已部署到生产)
|
||
|
||
不要把服务器密码、证书私钥、token、真实后台账号、腾讯地图正式 key 写入仓库文档或提交记录。
|
||
|
||
## 部署前检查
|
||
|
||
进入项目目录:
|
||
|
||
```powershell
|
||
cd 'E:\深圳自然博物馆\workplace\frontend-miniapp'
|
||
```
|
||
|
||
检查分支、远程仓库和本地改动:
|
||
|
||
```powershell
|
||
git status --short --branch --untracked-files=all
|
||
git log -1 --pretty=format:'%h %s'
|
||
git remote -v
|
||
```
|
||
|
||
正式环境当前依赖一组本地生产补丁,拉取远程代码前必须保护。常见补丁文件包括:
|
||
|
||
```text
|
||
.env.development
|
||
.env.production
|
||
src/config/dataSource.ts
|
||
src/data/adapters/guideDataAdapter.ts
|
||
src/data/providers/staticMuseumContentProvider.ts
|
||
src/env.d.ts
|
||
src/repositories/ExplainRepository.ts
|
||
src/usecases/explainUseCase.ts
|
||
src/utils/publicUrl.ts
|
||
static/guide-data/exhibits.json
|
||
static/guide-data/guide-contents.json
|
||
static/guide-data/guide-stops.json
|
||
static/guide-data/manifest.json
|
||
```
|
||
|
||
这些文件中的改动属于正式环境本地补丁,拉取远程代码时不要直接丢弃。当前正式环境相关配置要点:
|
||
|
||
- `VITE_PUBLIC_SAME_ORIGIN_ASSET_HOST=172.20.14.21`,避免正式包混入测试服务器 `1.92.206.90`。
|
||
- `VITE_EXPLAIN_ALLOW_STATIC_FALLBACK=true`,讲解数据接口异常时允许使用静态兜底数据。
|
||
- `VITE_SGS_SDK_ORIGIN=https://guide.sznhmuseum.org.cn`,正式域名不再使用 `:4430`。
|
||
- `static/guide-data/manifest.json` 中 `sourceHost` 应为 `172.20.14.21`。
|
||
- 腾讯地图正式 key 只通过环境变量 `VITE_TENCENT_MAP_KEY` 注入,不能写入文档或提交记录。
|
||
|
||
## 拉取远程最新代码
|
||
|
||
先临时保存本地生产补丁:
|
||
|
||
```powershell
|
||
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
|
||
$msg = "pre-deploy-prod-local-fixes-$stamp"
|
||
if (git status --porcelain) {
|
||
git stash push -u -m $msg
|
||
Write-Output "STASHED=$msg"
|
||
}
|
||
```
|
||
|
||
执行成功时会看到类似输出,表示本地补丁已 stash:
|
||
|
||
```text
|
||
Saved working directory and index state On master: pre-deploy-prod-local-fixes-YYYYMMDDHHmmss
|
||
STASHED=pre-deploy-prod-local-fixes-YYYYMMDDHHmmss
|
||
```
|
||
|
||
拉取目标发布分支。发布前必须由变更单或版本记录明确分支名,不得默认把 `master` 当作本次目标:
|
||
|
||
```powershell
|
||
$branch = 'codex/import-sgs-frontend-mobile-20260727'
|
||
git fetch origin
|
||
git switch $branch
|
||
git pull --ff-only origin $branch
|
||
git log -1 --pretty=format:'%h %s'
|
||
```
|
||
|
||
恢复生产补丁:
|
||
|
||
```powershell
|
||
$stash = git stash list | Select-String $msg | Select-Object -First 1
|
||
if ($stash) {
|
||
$stashRef = ($stash.Line -split ':')[0]
|
||
git stash pop $stashRef
|
||
}
|
||
git status --short --branch --untracked-files=all
|
||
```
|
||
|
||
如出现冲突,先解决冲突并重新确认生产配置,再继续构建。
|
||
|
||
恢复后重点检查 `.env.production`、`static/guide-data/manifest.json` 和 `src/utils/publicUrl.ts`,确认正式环境仍指向 `172.20.14.21` 和 `https://guide.sznhmuseum.org.cn`。
|
||
|
||
## Nginx 与 CDN 当前生产配置(2026-07-28)
|
||
|
||
本节以 `172.20.14.21` 上 `docker exec sgs-nginx nginx -T` 的实际生效结果为准。主 Nginx 使用 Docker host 网络,公共域名不直接读取宿主静态目录,而是通过 upstream `sgs_mobile_pool` 转发到五台 `17083` 工作节点:
|
||
|
||
```text
|
||
游客 HTTPS -> guide.sznhmuseum.org.cn CDN -> guide-real.sznhmuseum.org.cn:443
|
||
-> 218.17.240.244 / 172.20.14.21 sgs-nginx
|
||
-> sgs_mobile_pool -> 172.20.14.21/.23/.26/.27/.33:17083
|
||
```
|
||
|
||
`.21:/data/nginx/conf.d/guide-public.conf` 的 HTTP/HTTPS `server_name` 均包含 `guide.sznhmuseum.org.cn` 和 `guide-real.sznhmuseum.org.cn`。通配符证书 `*.sznhmuseum.org.cn` 覆盖两者,有效期至 2027-01-14。80 端口只保留 ACME challenge 直出和 `/_analytics/` 统一 404,其余请求 301 到正式 HTTPS 域名。游客 HTTP 到 HTTPS 的跳转仍应在 CDN 边缘启用;CDN 使用 HTTPS 回源时,不能依赖源站 80 端口完成游客侧跳转。
|
||
|
||
CDN 控制台应使用以下回源基线:
|
||
|
||
- 回源地址、Host 和 SNI:`guide-real.sznhmuseum.org.cn`;
|
||
- 回源协议/端口:HTTPS `443`;
|
||
- 启用源站证书校验;
|
||
- 游客域名始终为 `guide.sznhmuseum.org.cn`;
|
||
- 不把 `guide-real` 写入前端构建变量或对外链接。
|
||
|
||
当前移动端工作节点 `17083` 的关键规则如下:
|
||
|
||
```nginx
|
||
server {
|
||
listen 17083;
|
||
root /usr/share/nginx/html/mobile;
|
||
|
||
location = /healthz {
|
||
access_log off;
|
||
return 200 "ok\n";
|
||
}
|
||
|
||
# 构建后的带 hash 入口和静态依赖:一年不可变缓存
|
||
location ^~ /assets/ {
|
||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||
try_files $uri =404;
|
||
}
|
||
|
||
# 实际配置还按可变性拆分以下资源;变更时以 nginx -T 为准:
|
||
# - index.html、SPA fallback、/engine/index.html、guide-data、固定名 SDK 入口:no-store
|
||
# - engine chunks、字体、模型及普通静态文件:30 天
|
||
# - 缺失静态资源:404,且不附加长期缓存头
|
||
# - 页面路由:仅页面请求允许回退 /index.html,回退响应为 no-store
|
||
}
|
||
```
|
||
|
||
注意:
|
||
|
||
- `/assets/`、`/static/nav-assets/`、`/static/Fonts/` 和其他 `/static/` 路径必须优先 `try_files $uri =404`,不能把缺失的 JSON、GLB、字体返回为 `index.html`。
|
||
- `index.html`、SPA fallback、`/engine/index.html`、`/static/guide-data/` 和固定文件名 SDK 入口必须为 `Cache-Control: no-store`;带 hash 的 `/assets/` 才使用一年不可变缓存。
|
||
- engine chunk、字体、模型和普通静态文件使用 30 天缓存;缺失资源返回 404,不能带长期缓存头。
|
||
- `guide-public.conf` 的 80/443 入口包含 ACME challenge 和 `/_analytics/` 404 规则;不要把公共域名改回单节点静态 root。
|
||
- 当前 upstream 使用 `least_conn`,单节点由 `max_fails=3 fail_timeout=10s` 被动摘除。
|
||
- 所有 `/_analytics/` 路径必须保持 404;不得恢复 Tracker、Matomo 注入、匿名访问通知、`visitor.js` 或 `privacy.html`。
|
||
|
||
2026-07-24 逐节点直连 `17083` 复核时发现配置漂移:`.21` 缓存策略正确,`.23/.26/.27/.33` 缺少 `/assets/` 长期 immutable 缓存头和 `/static/Fonts/` 专用规则。四台异常节点已分别备份配置、补齐对应 `17083` location,并在 `nginx -t` 通过后平滑 reload。2026-07-27 至 28 日又完成 CDN 分层缓存适配,并同步更新 `.21:/data/scripts/sync-frontend-nodes.sh` 的 `write_worker_conf()` 模板。当前五台均满足:
|
||
|
||
- 字体返回 `Cache-Control: public, max-age=2592000`;
|
||
- 带 hash 的 assets 返回 `Cache-Control: public, max-age=31536000, immutable`;
|
||
- HTML、SPA fallback、guide-data 和固定名 SDK 入口返回 `Cache-Control: no-store`;
|
||
- engine chunk、字体、模型及普通静态文件返回 30 天缓存;
|
||
- 缺失静态资源返回 HTTP `404` 且不附加长期缓存头;
|
||
- 字体 ETag 条件请求返回 HTTP `304`,且 `304` 响应仍保留 30 天缓存头;
|
||
- 20 个 `17080-17083 /healthz` 端点全部正常,五台 `sgs-nginx` 容器均未重启。
|
||
|
||
因此,不能只通过公网域名抽样或只检查 `/healthz` 判断五节点配置一致;`least_conn` 可能掩盖单节点漂移。发布验收必须执行本文后面的五节点直连缓存检查。
|
||
|
||
### Nginx 配置变更步骤
|
||
|
||
Nginx 配置变更不是普通静态文件发布的一部分,必须单独备份、检查和 reload:
|
||
|
||
```powershell
|
||
$script = @'
|
||
set -euo pipefail
|
||
stamp=$(date +%Y%m%d%H%M%S)
|
||
backup=/data/nginx/_backups/nginx-conf-before-mobile-$stamp.tar.gz
|
||
mkdir -p /data/nginx/_backups
|
||
|
||
tar -czf "$backup" -C /data/nginx conf.d
|
||
|
||
docker exec sgs-nginx nginx -t
|
||
docker exec sgs-nginx nginx -s reload
|
||
printf 'NGINX_CONFIG_BACKUP=%s\\n' "$backup"
|
||
'@
|
||
$script | ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash -s'
|
||
```
|
||
|
||
确认配置变更后再执行静态文件发布;如果修改的是工作节点 `17083` 配置,必须在每个受影响节点分别备份、执行 `nginx -t`、平滑 reload 和直连响应头验证,不能假定 `.21` reload 会同步其他节点。静态文件发布完成后仍需执行五节点 `/healthz`、缓存头、ETag/304、资源 MIME 和 404 验证。
|
||
|
||
## 本地构建
|
||
|
||
安装依赖并做类型检查:
|
||
|
||
```powershell
|
||
corepack pnpm install --frozen-lockfile
|
||
$env:NODE_OPTIONS='--max-old-space-size=16384'
|
||
corepack pnpm type-check
|
||
```
|
||
|
||
设置正式构建变量。腾讯地图 key 只通过环境变量注入,不写入仓库:
|
||
|
||
```powershell
|
||
$env:NODE_OPTIONS='--max-old-space-size=16384'
|
||
$env:VITE_TENCENT_MAP_KEY='<正式腾讯地图 Web Key>'
|
||
$env:VITE_PUBLIC_SAME_ORIGIN_ASSET_HOST='172.20.14.21'
|
||
$env:VITE_EXPLAIN_ALLOW_STATIC_FALLBACK='true'
|
||
```
|
||
|
||
执行正式 H5 构建:
|
||
|
||
```powershell
|
||
corepack pnpm build:h5
|
||
```
|
||
|
||
`build:h5` 会执行:
|
||
|
||
1. `uni build -p h5 --mode production`
|
||
2. `node scripts/finalize-h5-build.cjs --require-tencent-map-key`
|
||
|
||
`finalize-h5-build.cjs` 会自动完成:
|
||
|
||
- 拷贝 `static/nav-assets`、`static/guide-data`、`static/guide`、`static/icons`、`static/explain`、`static/sgs-map-sdk`、`static/three` 和字体资源。
|
||
- 使用 Node 按 UTF-8 安全替换构建产物里的 `__REPLACE_WITH_TENCENT_MAP_WEB_KEY__`。
|
||
- 对全部构建 JS 执行 `node --check`,防止语法错误产物上线。
|
||
- 扫描并阻断 `1.92.206.90`、`guide.whaoyue.com`、腾讯地图 key 占位符等不应进入正式环境的字符串。
|
||
- 校验核心静态 JSON 和关键图标文件存在。
|
||
|
||
如 terser 压缩阶段出现内存不足,可使用不压缩构建:
|
||
|
||
```powershell
|
||
corepack pnpm run build:h5:no-minify
|
||
```
|
||
|
||
严禁再用 PowerShell `Get-Content` / `Set-Content` 手工替换构建后的 JS。PowerShell 默认编码容易破坏 UTF-8 中文注释或字符串,曾导致线上入口 JS 语法错误、页面空白。
|
||
|
||
## 构建产物检查
|
||
|
||
构建脚本会自动输出类似:
|
||
|
||
```text
|
||
H5_ENTRY=assets/index-xxxx.js
|
||
H5_ENTRY_SIZE=246232
|
||
H5_JS_SYNTAX_CHECKED=...
|
||
H5_TENCENT_PLACEHOLDER_REPLACED_FILES=2
|
||
H5_BAD_STRING_FILE_COUNT=0
|
||
H5_MANIFEST_SOURCE_HOST=172.20.14.21
|
||
H5_GUIDE_CONTENTS_ROWS=720
|
||
H5_EXHIBITS_ROWS=4700
|
||
H5_GUIDE_STOPS_ROWS=398
|
||
```
|
||
|
||
如果需要手工复查入口 JS:
|
||
|
||
```powershell
|
||
$dist = Join-Path (Get-Location) 'dist\build\h5'
|
||
$html = Get-Content -LiteralPath (Join-Path $dist 'index.html') -Raw
|
||
$entry = [regex]::Match($html, 'src="/?([^"]*assets/index-[^"]+\.js)"').Groups[1].Value
|
||
node --check (Join-Path $dist $entry)
|
||
```
|
||
|
||
预期无错误输出。
|
||
|
||
## 打包与上传
|
||
|
||
```powershell
|
||
$ts = Get-Date -Format 'yyyyMMddHHmmss'
|
||
New-Item -ItemType Directory -Force -Path '.tmp' | Out-Null
|
||
$pkg = ".tmp\frontend-miniapp-h5-$ts.tar.gz"
|
||
if (Test-Path $pkg) { Remove-Item -LiteralPath $pkg -Force }
|
||
tar -czf $pkg -C 'dist\build\h5' .
|
||
scp -o StrictHostKeyChecking=no $pkg root@172.20.14.21:/tmp/frontend-miniapp-h5.tar.gz
|
||
```
|
||
|
||
## 主节点部署
|
||
|
||
部署时必须保留:
|
||
|
||
- `/data/nginx/html/mobile/gpL0svkeao.txt`
|
||
- `/data/nginx/html/mobile/.well-known`
|
||
|
||
执行:
|
||
|
||
```powershell
|
||
$script = @'
|
||
set -euo pipefail
|
||
pkg=/tmp/frontend-miniapp-h5.tar.gz
|
||
target=/data/nginx/html/mobile
|
||
backup_dir=/data/nginx/html/_backups
|
||
ts=$(date +%Y%m%d%H%M%S)
|
||
backup="$backup_dir/mobile-before-deploy-$ts.tar.gz"
|
||
tmp="/tmp/frontend-miniapp-h5-$ts"
|
||
|
||
test -s "$pkg"
|
||
mkdir -p "$target" "$backup_dir"
|
||
tar -czf "$backup" -C "$target" .
|
||
rm -rf "$tmp"
|
||
mkdir -p "$tmp"
|
||
tar -xzf "$pkg" -C "$tmp"
|
||
test -f "$tmp/index.html"
|
||
|
||
find "$target" -mindepth 1 -maxdepth 1 ! -name 'gpL0svkeao.txt' ! -name '.well-known' -exec rm -rf {} +
|
||
cp -a "$tmp"/. "$target"/
|
||
chmod -R a+rX "$target"
|
||
|
||
entry=$(grep -o "assets/index-[^\"]*\.js" "$target/index.html" | head -n 1 || true)
|
||
test -n "$entry"
|
||
test -f "$target/$entry"
|
||
node --check "$target/$entry"
|
||
|
||
if grep -R -I -E '1\.92\.206\.90|guide\.whaoyue\.com|__REPLACE_WITH_TENCENT_MAP_WEB_KEY__|http://1\.92\.206\.90:9000' "$target" >/tmp/mobile_bad_matches.txt; then
|
||
cat /tmp/mobile_bad_matches.txt >&2
|
||
exit 1
|
||
fi
|
||
|
||
# 仅替换静态文件,不 reload Nginx;公共域名通过 sgs_mobile_pool 命中五台 17083 工作节点。
|
||
# 如本次同时修改了 /data/nginx/conf.d,必须另行执行 nginx -t 后 reload。
|
||
|
||
printf 'DEPLOY_BACKUP=%s\n' "$backup"
|
||
printf 'DEPLOY_ENTRY=%s\n' "$entry"
|
||
printf 'DEPLOY_TARGET=%s\n' "$target"
|
||
printf 'PRESERVED_GP=%s\n' "$(test -f "$target/gpL0svkeao.txt" && echo yes || echo no)"
|
||
printf 'PRESERVED_WELL_KNOWN=%s\n' "$(test -d "$target/.well-known" && echo yes || echo no)"
|
||
'@
|
||
$script | ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash -s'
|
||
```
|
||
|
||
## 全量同步脚本(仅在 Nginx 配置已复核时使用)
|
||
|
||
```powershell
|
||
ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash /data/scripts/sync-frontend-nodes.sh'
|
||
```
|
||
|
||
执行前必须注意:该脚本不仅同步 `/data/nginx/html`,还会通过 `write_worker_conf()` 重写五台工作节点的 `/data/nginx/conf.d/sgs-worker.conf`。脚本模板已于 2026-07-28 同步当前 `17083` CDN 分层缓存策略,但每次使用前仍必须复核脚本与现网配置。**普通移动端静态发布不得在未复核脚本内嵌配置前直接运行该脚本。**
|
||
|
||
继续使用同步脚本前先检查它是否已经包含本次规则:
|
||
|
||
```powershell
|
||
ssh -o StrictHostKeyChecking=no root@172.20.14.21 'grep -n -E "no-store|static/guide-data|engine/index.html|31536000|2592000|try_files \\$uri =404" /data/scripts/sync-frontend-nodes.sh'
|
||
```
|
||
|
||
如果检查未同时覆盖 no-store、30 天缓存、一年 immutable 和静态 404 规则,应先在维护窗口审核并更新 `write_worker_conf()`,再执行 `bash -n /data/scripts/sync-frontend-nodes.sh`、逐节点 `nginx -t` 和 `/healthz` 验证。不要用 `.21` 的整份 `sgs-worker.conf` 覆盖其他节点,因为 `.21` 的 `17080` 还包含本节点专用 CSP 配置;只同步审核后的 `17083` 移动端配置块。
|
||
|
||
如果本次仅发布 H5 静态文件、没有修改 Nginx 配置,推荐采用“仅同步 `/data/nginx/html/mobile`”的流程,不要触发同步脚本中的 `write_worker_conf()`、`/data/apps/sgs-map` 同步和 PM2 重启。静态文件替换完成后不需要 Nginx reload;只有配置变更才执行 `nginx -t` 后 reload。
|
||
|
||
若必须按现有脚本执行,必须确认最终输出:
|
||
|
||
```text
|
||
All frontend worker nodes synced.
|
||
```
|
||
|
||
同步脚本会同步整个 `/data/nginx/html`,同时也会处理 `/data/apps/sgs-map` 并通过 PM2 重启 `sgs-map`。这是当前脚本行为,部署 mobile 时也会看到相关输出;除非已复核脚本内嵌 Nginx 配置,否则不要把它作为普通 mobile 发布命令。
|
||
|
||
### 仅同步 mobile 静态目录(推荐)
|
||
|
||
普通 H5 发布不需要重新加载 Nginx,也不需要同步地图应用。主节点部署通过后,将同一个已校验的 `$pkg` 分发到四个工作节点;每台节点独立备份并保留 `gpL0svkeao.txt` 与 `.well-known`:
|
||
|
||
```powershell
|
||
$workers = @('172.20.14.23','172.20.14.26','172.20.14.27','172.20.14.33')
|
||
$workerScript = @'
|
||
set -euo pipefail
|
||
pkg=/tmp/frontend-miniapp-h5.tar.gz
|
||
target=/data/nginx/html/mobile
|
||
backup_dir=/data/nginx/html/_backups
|
||
ts=$(date +%Y%m%d%H%M%S)
|
||
backup="$backup_dir/mobile-before-deploy-$ts.tar.gz"
|
||
tmp="/tmp/frontend-miniapp-h5-$ts"
|
||
|
||
test -s "$pkg"
|
||
mkdir -p "$target" "$backup_dir" "$tmp"
|
||
tar -czf "$backup" -C "$target" .
|
||
tar -xzf "$pkg" -C "$tmp"
|
||
test -f "$tmp/index.html"
|
||
|
||
find "$target" -mindepth 1 -maxdepth 1 ! -name 'gpL0svkeao.txt' ! -name '.well-known' -exec rm -rf {} +
|
||
cp -a "$tmp"/. "$target"/
|
||
chmod -R a+rX "$target"
|
||
|
||
entry=$(grep -o 'assets/index-[^"]*\.js' "$target/index.html" | head -n 1)
|
||
test -n "$entry"
|
||
test -f "$target/$entry"
|
||
node --check "$target/$entry"
|
||
curl -fsS http://127.0.0.1:17083/healthz
|
||
printf 'WORKER_BACKUP=%s\nWORKER_ENTRY=%s\n' "$backup" "$entry"
|
||
rm -rf "$tmp" "$pkg"
|
||
'@
|
||
|
||
foreach ($node in $workers) {
|
||
scp -o StrictHostKeyChecking=no $pkg "root@${node}:/tmp/frontend-miniapp-h5.tar.gz"
|
||
$workerScript | ssh -o StrictHostKeyChecking=no root@$node 'bash -s'
|
||
}
|
||
```
|
||
|
||
同步完成后逐节点检查:
|
||
|
||
```powershell
|
||
$nodes = @('172.20.14.21','172.20.14.23','172.20.14.26','172.20.14.27','172.20.14.33')
|
||
foreach ($node in $nodes) {
|
||
ssh root@$node "curl -fsS http://127.0.0.1:17083/healthz && test -f /data/nginx/html/mobile/index.html"
|
||
}
|
||
```
|
||
|
||
此流程不修改 `/data/nginx/conf.d`,不执行 `nginx -s reload`,也不操作 `/data/apps/sgs-map` 或 PM2。只有 Nginx 配置变更才需要先 `nginx -t` 再 reload。
|
||
|
||
## 讲解音频与 CDN 兼容
|
||
|
||
### 当前链路
|
||
|
||
讲解音频的正确生产链路为:
|
||
|
||
```text
|
||
/app-api/gis/guide/stop/info 或 /app-api/gis/guide/audio/play-info
|
||
-> 返回 https://guide.sznhmuseum.org.cn/minio/museum-assets/...mp3
|
||
-> CDN
|
||
-> .21 /minio/ 代理
|
||
-> sgs_minio / museum-assets 对象
|
||
```
|
||
|
||
`.21:/data/nginx/conf.d/guide-public.conf` 当前还保留以下兼容措施:
|
||
|
||
- `/app-api/gis/sdk/minio/museum-assets/` 直接代理到 `sgs_minio/museum-assets/`,绕过会返回拒绝访问 HTML 的 Java SDK 媒体代理;
|
||
- 对 `/app-api/gis/guide/stop/info` 和 `/app-api/gis/guide/audio/play-info` 精确启用 no-store 响应改写,将相对 `/minio/museum-assets/...` 地址改为正式域名绝对 URL;
|
||
- `/minio/museum-assets/tts-audio/audio/` 保留 Range 请求能力,正常响应应为 `audio/mpeg` 和 HTTP `206`。
|
||
|
||
上述 Nginx 兼容层属于生产止血措施。长期方案应在以下两项中择一实施并完成回归测试:
|
||
|
||
1. 前端修正 `src/utils/publicUrl.ts`,不再把 `/minio/` 强制转换为 `/app-api/gis/sdk/minio/`;
|
||
2. Java SDK 媒体代理修复权限、状态码、Content-Type 和 Range 转发。
|
||
|
||
在长期方案上线、CDN 污染对象完成清理且原 SDK 媒体 URL 验证正常前,不得删除当前兼容路由或 API URL 改写。
|
||
|
||
### 故障识别
|
||
|
||
已确认的错误特征为:请求 MP3 时收到 HTTP `206`,但 `Content-Type` 为 `text/html;charset=utf-8`、响应约 726 字节,正文提示页面被管理员禁止。该响应来自 Java SDK 媒体代理的拒绝访问页;CDN 曾缓存该错误对象。只看 HTTP 状态码会误判成功,音频验收必须同时检查 MIME、Range、长度和文件头。
|
||
|
||
## 线上验证
|
||
|
||
公网验证:
|
||
|
||
```powershell
|
||
$base = 'https://guide.sznhmuseum.org.cn'
|
||
curl.exe -L -s -o NUL -w "home=%{http_code} %{content_type} %{size_download}`n" "$base/"
|
||
curl.exe -L -s -o NUL -w "guide_contents=%{http_code} %{content_type} %{size_download}`n" "$base/static/guide-data/guide-contents.json"
|
||
curl.exe -L -s -o NUL -w "exhibits=%{http_code} %{content_type} %{size_download}`n" "$base/static/guide-data/exhibits.json"
|
||
curl.exe -L -s -o NUL -w "manifest=%{http_code} %{content_type} %{size_download}`n" "$base/static/guide-data/manifest.json"
|
||
curl.exe -L -s -o NUL -w "brand_icon=%{http_code} %{content_type} %{size_download}`n" "$base/static/guide/museum-brand-icons.webp"
|
||
curl.exe -L -s -o NUL -w "shortcut_icon=%{http_code} %{content_type} %{size_download}`n" "$base/static/icons/poi/shortcut-icons.svg"
|
||
curl.exe -L -s -o NUL -w "verify_txt=%{http_code} %{content_type} %{size_download}`n" "$base/gpL0svkeao.txt"
|
||
|
||
# 缓存/MIME/404:assets 应 immutable;JSON 应为 application/json;缺失 static 不能返回 SPA HTML
|
||
curl.exe -sI "$base/assets/<本次入口文件名>.js" | Select-String 'HTTP/|Content-Type|Cache-Control'
|
||
curl.exe -sI "$base/static/guide-data/manifest.json" | Select-String 'HTTP/|Content-Type|Cache-Control'
|
||
curl.exe -sI "$base/static/nav-assets/<真实资源路径>.glb" | Select-String 'HTTP/|Content-Type|Cache-Control'
|
||
curl.exe -sI "$base/static/__not_exists__.json" | Select-String 'HTTP/|Content-Type'
|
||
```
|
||
|
||
预期:
|
||
|
||
- `/assets/<hash>.js`:HTTP `200`,`Cache-Control` 含 `max-age=31536000, immutable`;
|
||
- `/`、`/index.html`、`/engine/index.html` 和 `/static/guide-data/*.json`:HTTP `200`,`Cache-Control` 为 no-store;
|
||
- manifest:HTTP `200`,`Content-Type: application/json`;
|
||
- 真实 GLB:HTTP `200`,`Content-Type: model/gltf-binary`;
|
||
- `/static/__not_exists__.json`:HTTP `404`,不得返回 `200 text/html`,也不得附加长期缓存头。
|
||
|
||
入口 JS 验证:
|
||
|
||
```powershell
|
||
$tmp = Join-Path (Get-Location) '.tmp\public-mobile-verify'
|
||
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
|
||
$htmlPath = Join-Path $tmp 'index.html'
|
||
$entryPath = Join-Path $tmp 'entry.js'
|
||
curl.exe -L -s "$base/" -o $htmlPath
|
||
$html = Get-Content -LiteralPath $htmlPath -Raw
|
||
$entry = [regex]::Match($html, 'src="/?([^"]*assets/index-[^"]+\.js)"').Groups[1].Value
|
||
curl.exe -L -s "$base/$entry" -o $entryPath
|
||
node --check $entryPath
|
||
@('1.92.206.90','guide.whaoyue.com','__REPLACE_WITH_TENCENT_MAP_WEB_KEY__') |
|
||
Where-Object { (Get-Content -LiteralPath $entryPath -Raw).Contains($_) }
|
||
```
|
||
|
||
预期 `node --check` 通过,旧测试地址和占位符检查无输出。
|
||
|
||
五台节点一致性验证:
|
||
|
||
```powershell
|
||
$nodes = @('172.20.14.21','172.20.14.23','172.20.14.26','172.20.14.27','172.20.14.33')
|
||
$entryPath = "/data/nginx/html/mobile/$entry"
|
||
foreach ($node in $nodes) {
|
||
ssh -o StrictHostKeyChecking=no root@$node "node --check '$entryPath' >/dev/null && sha256sum '$entryPath' && stat -c %s '$entryPath'"
|
||
}
|
||
```
|
||
|
||
五台节点的 size 和 SHA256 应一致。另需逐节点验证 `17083` Nginx 配置与资源行为。以下命令从各节点当前部署目录动态选择真实字体和 hash asset,不依赖某次构建的固定文件名:
|
||
|
||
```powershell
|
||
$verify17083 = @'
|
||
set -euo pipefail
|
||
base=/data/nginx/html/mobile
|
||
font=$(find "$base/static/Fonts" -type f -print -quit)
|
||
asset=$(find "$base/assets" -maxdepth 1 -type f -print -quit)
|
||
test -n "$font"
|
||
test -n "$asset"
|
||
font_uri=${font#"$base"}
|
||
asset_uri=${asset#"$base"}
|
||
|
||
docker exec sgs-nginx nginx -t >/dev/null
|
||
curl -fsS http://127.0.0.1:17083/healthz >/dev/null
|
||
|
||
font_headers=$(curl -sSI "http://127.0.0.1:17083$font_uri")
|
||
printf '%s\n' "$font_headers" | grep -Eq '^HTTP/[^ ]+ 200'
|
||
printf '%s\n' "$font_headers" | grep -Eiq '^Cache-Control:[[:space:]]*public, max-age=2592000([[:space:]]|$)'
|
||
etag=$(printf '%s\n' "$font_headers" | sed -n 's/^[Ee][Tt][Aa][Gg]:[[:space:]]*//p' | tr -d '\r' | head -n 1)
|
||
test -n "$etag"
|
||
|
||
not_modified_headers=$(curl -sSI -H "If-None-Match: $etag" "http://127.0.0.1:17083$font_uri")
|
||
printf '%s\n' "$not_modified_headers" | grep -Eq '^HTTP/[^ ]+ 304'
|
||
printf '%s\n' "$not_modified_headers" | grep -Eiq '^Cache-Control:[[:space:]]*public, max-age=2592000([[:space:]]|$)'
|
||
|
||
asset_headers=$(curl -sSI "http://127.0.0.1:17083$asset_uri")
|
||
printf '%s\n' "$asset_headers" | grep -Eq '^HTTP/[^ ]+ 200'
|
||
printf '%s\n' "$asset_headers" | grep -Eiq '^Cache-Control:.*max-age=31536000.*immutable'
|
||
|
||
missing_code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:17083/static/__not_exists__.json)
|
||
test "$missing_code" = 404
|
||
printf 'PASS font=%s asset=%s etag=%s\n' "$font_uri" "$asset_uri" "$etag"
|
||
'@
|
||
|
||
foreach ($node in $nodes) {
|
||
$verify17083 | ssh -o StrictHostKeyChecking=no root@$node 'bash -s'
|
||
}
|
||
```
|
||
|
||
预期五台均输出 `PASS`。任一节点缺少字体或 asset 缓存头、字体条件请求不是 `304`、`304` 未保留缓存头,或缺失静态资源不是 `404`,均应中止发布验收并先处理配置漂移。
|
||
|
||
注意:`.21` 的整份 `sgs-worker.conf` 哈希可以与其余节点不同,因为它还承载额外的 `17080` CSP 配置;验收目标是五台 `17083` 移动端配置块和响应行为一致。不要用 `.21` 的整份配置覆盖其他节点。
|
||
|
||
### 源站、音频与安全负向验证
|
||
|
||
使用 `--resolve` 绕过 CDN 直连源站,确认 Host/SNI、入口缓存和音频 Range 行为:
|
||
|
||
```powershell
|
||
$origin = '218.17.240.244'
|
||
$originHost = 'guide-real.sznhmuseum.org.cn'
|
||
$public = 'https://guide.sznhmuseum.org.cn'
|
||
$audio = '/minio/museum-assets/tts-audio/audio/2026/07/17/cdb263222e7749009db1768b49477283.mp3'
|
||
|
||
curl.exe --resolve "${originHost}:443:${origin}" -sI "https://${originHost}/"
|
||
curl.exe --resolve "${originHost}:443:${origin}" -sI "https://${originHost}/engine/index.html"
|
||
curl.exe --resolve "${originHost}:443:${origin}" -sI "https://${originHost}/static/guide-data/manifest.json"
|
||
curl.exe --resolve "${originHost}:443:${origin}" -sS -D - -o NUL -H 'Range: bytes=0-31' "https://${originHost}${audio}"
|
||
curl.exe -sS -D - -o NUL -H 'Range: bytes=0-31' "${public}${audio}"
|
||
```
|
||
|
||
音频源站和 CDN 响应均应满足:HTTP `206`、`Content-Type: audio/mpeg`、有效 `Content-Range`,且不得出现约 726 字节的 `text/html`。随后在 390 x 844 移动浏览器中打开英文展项 `865546638960193536`,确认播放器时间从 `0:00` 推进、可完整播放并正常结束。
|
||
|
||
安全负向检查:
|
||
|
||
```powershell
|
||
@('/_analytics/visitor.js','/_analytics/privacy.html','/_analytics/matomo.js','/_analytics/matomo.php') |
|
||
ForEach-Object { curl.exe -s -o NUL -w "%{http_code} $_`n" "$public$_" }
|
||
```
|
||
|
||
四项必须全部返回 HTTP `404`。
|
||
|
||
### CDN 控制台发布后检查
|
||
|
||
CDN 控制台操作不能由源站部署脚本代替。每次部署涉及 HTML、guide-data、engine 固定入口或音频路由时,至少刷新:
|
||
|
||
```text
|
||
https://guide.sznhmuseum.org.cn/
|
||
https://guide.sznhmuseum.org.cn/index.html
|
||
https://guide.sznhmuseum.org.cn/engine/index.html
|
||
https://guide.sznhmuseum.org.cn/static/guide-data/
|
||
```
|
||
|
||
若处理过讲解音频代理,还需刷新:
|
||
|
||
```text
|
||
https://guide.sznhmuseum.org.cn/app-api/gis/sdk/minio/museum-assets/
|
||
```
|
||
|
||
CDN 规则应保持:
|
||
|
||
- `/assets/*`:遵循源站一年 immutable;
|
||
- `/minio/museum-assets/tts-audio/audio/*`:30 天;仅在确认文件名内容不可变时才可提升为一年 immutable;
|
||
- `/app-api/gis/guide/audio/*`、`/app-api/gis/guide/stop/*`:TTL 0;
|
||
- `/app-api/gis/sdk/minio/museum-assets/*`:污染对象清理和直源验证完成前 TTL 0;
|
||
- HTML、SPA fallback、`/engine/index.html`、guide-data 和固定名 SDK 入口:TTL 0 或严格遵循源站 no-store;
|
||
- 媒体缓存必须校验成功状态和正确 Content-Type,禁止把 `text/html` 当作音频缓存。
|
||
|
||
同时在 CDN 边缘启用游客 HTTP 到 HTTPS 的 301。控制台变更后重新执行公网与 `--resolve` 对照检查,以区分 CDN 和源站响应。
|
||
|
||
## 回滚
|
||
|
||
主节点部署脚本会在 `/data/nginx/html/_backups` 下生成备份,例如:
|
||
|
||
```text
|
||
/data/nginx/html/_backups/mobile-before-deploy-YYYYMMDDHHmmss.tar.gz
|
||
```
|
||
|
||
回滚主节点:
|
||
|
||
```powershell
|
||
$backup = '/data/nginx/html/_backups/mobile-before-deploy-YYYYMMDDHHmmss.tar.gz'
|
||
$script = @"
|
||
set -euo pipefail
|
||
target=/data/nginx/html/mobile
|
||
backup='$backup'
|
||
test -f "`$backup"
|
||
find "`$target" -mindepth 1 -maxdepth 1 ! -name 'gpL0svkeao.txt' ! -name '.well-known' -exec rm -rf {} +
|
||
tar -xzf "`$backup" -C "`$target"
|
||
chmod -R a+rX "`$target"
|
||
# 仅恢复静态文件,无需 reload Nginx;如同时回滚 conf.d,必须先 nginx -t 再 reload。
|
||
"@
|
||
$script | ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash -s'
|
||
```
|
||
|
||
回滚后应使用前述“仅同步 mobile 静态目录”流程把回滚版本同步到四个工作节点,并重新验证五节点入口 JS 的 SHA256。只有已确认 `/data/scripts/sync-frontend-nodes.sh` 内嵌 Nginx 配置与生产一致时,才允许用全量脚本同步。
|
||
|
||
如需回滚 CDN/Nginx 适配,使用对应变更备份,不能用旧整目录覆盖现网:
|
||
|
||
- CDN 源站集群适配:`/data/nginx/_backups/cdn-origin-cluster-20260727223921`;
|
||
- SDK MinIO 音频绕过:`/data/nginx/_backups/audio-sdk-minio-bypass-20260727232518`;
|
||
- 音频 API URL 改写:`/data/nginx/_backups/audio-api-url-rewrite-20260727233006`。
|
||
|
||
回滚顺序固定为:确认影响范围,备份当前配置,只恢复对应文件或 location,逐节点执行 `docker exec sgs-nginx nginx -t`,平滑 reload,再验证五节点 `/healthz`、正式域名、源站域名、音频 Range 和 `/_analytics/` 404。若 CDN 已缓存回滚前对象,还需在控制台刷新对应 URL;仅回滚源站不会自动清除 CDN 对象。
|
||
|
||
## 已遇到的问题与处理
|
||
|
||
### 1. terser 压缩阶段内存不足
|
||
|
||
现象:
|
||
|
||
```text
|
||
[vite:terser] Worker terminated due to reaching memory limit: JS heap out of memory
|
||
```
|
||
|
||
处理:
|
||
|
||
- 设置 `NODE_OPTIONS=--max-old-space-size=16384`。
|
||
- 优先使用 `corepack pnpm build:h5`。
|
||
- 如仍 OOM,使用 `corepack pnpm run build:h5:no-minify`,该命令仍会执行安全后处理和 `node --check`。
|
||
|
||
### 2. 测试服务器 IP 混入正式环境
|
||
|
||
现象:
|
||
|
||
```text
|
||
Mixed Content: requested insecure image 'http://1.92.206.90:9000/...'
|
||
```
|
||
|
||
处理:
|
||
|
||
- 正式环境使用 `VITE_PUBLIC_SAME_ORIGIN_ASSET_HOST=172.20.14.21`。
|
||
- 构建后必须扫描 `1.92.206.90`、`guide.whaoyue.com`。
|
||
- 静态资源通过同源路径 `/museum-assets/...` 访问。
|
||
|
||
### 3. 腾讯地图接口 CORS
|
||
|
||
现象:
|
||
|
||
```text
|
||
Access to XMLHttpRequest at 'https://apis.map.qq.com/...' has been blocked by CORS policy
|
||
```
|
||
|
||
处理:
|
||
|
||
- 避免前端直接跨域请求不可 CORS 的接口。
|
||
- 需要由 Nginx 或后端代理转发第三方地图 API,前端访问同源路径。
|
||
- 构建产物中的腾讯地图 key 占位符必须替换为正式 key。
|
||
|
||
### 4. 3D 模型或 JSON 返回 HTML
|
||
|
||
现象:
|
||
|
||
```text
|
||
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
|
||
```
|
||
|
||
常见原因:
|
||
|
||
- 静态资源未复制到 `dist/build/h5/static/...`。
|
||
- Nginx 对静态资源路径错误回退到 `index.html`。
|
||
- 部署目录缺文件。
|
||
|
||
处理:
|
||
|
||
- 构建后必须执行 `scripts/finalize-h5-build.cjs`,它会调用 `copy-h5-nav-assets.cjs`。
|
||
- 验证 `/static/guide-data/*.json`、`/static/nav-assets/...`、`/static/three/...` 返回正确资源。
|
||
- Nginx 静态资源路径应优先 `try_files $uri =404`,不要对资源文件回退 SPA。
|
||
|
||
### 5. 域名验证文件被覆盖
|
||
|
||
风险:
|
||
|
||
- 全量替换 `/data/nginx/html/mobile` 时误删 `gpL0svkeao.txt` 或 `.well-known`。
|
||
|
||
处理:
|
||
|
||
- 清理目录时排除 `gpL0svkeao.txt` 和 `.well-known`。
|
||
- 部署后验证 `https://guide.sznhmuseum.org.cn/gpL0svkeao.txt` 返回 200。
|
||
|
||
### 6. 同步脚本输出 Node engine warning
|
||
|
||
现象:
|
||
|
||
```text
|
||
npm warn EBADENGINE Unsupported engine
|
||
package: 'camera-controls@3.1.2'
|
||
required: { node: '>=22.0.0', npm: '>=10.5.1' }
|
||
current: { node: 'v20.19.5', npm: '10.8.2' }
|
||
```
|
||
|
||
处理:
|
||
|
||
- 当前为已知非阻断 warning。
|
||
- 只要脚本最终输出 `All frontend worker nodes synced.`,且 PM2 中 `sgs-map` 为 `online`,本次 mobile 静态部署可继续验收。
|
||
|
||
### 7. 同步脚本会重启 sgs-map
|
||
|
||
现象:
|
||
|
||
- 部署 mobile 时,脚本仍会同步 `/data/apps/sgs-map` 并重启 PM2 进程 `sgs-map`。
|
||
|
||
处理:
|
||
|
||
- 当前按脚本现状执行。
|
||
- 验收时关注最终 PM2 状态是否 `online`。
|
||
- 如未来只需同步 mobile,可拆分或新增专用同步脚本,避免不必要地重启 map 服务。
|
||
|
||
### 8. CDN 后讲解音频返回 HTML
|
||
|
||
现象:
|
||
|
||
```text
|
||
HTTP 206
|
||
Content-Type: text/html;charset=utf-8
|
||
Content-Length: 726
|
||
```
|
||
|
||
根因:
|
||
|
||
- 前端把 `/minio/...mp3` 转换成 `/app-api/gis/sdk/minio/...mp3`;
|
||
- Java SDK 媒体代理返回拒绝访问 HTML,但状态仍为 200;
|
||
- CDN 缓存后以 206 响应 Range 请求,造成播放器无法解码。
|
||
|
||
处理:
|
||
|
||
- `.21` 兼容路由将 `/app-api/gis/sdk/minio/museum-assets/` 直接代理到 MinIO;
|
||
- 两个讲解 API 的响应 URL 改写为正式域名 `/minio/museum-assets/...`;
|
||
- CDN 清除污染前,SDK media 路径 TTL 设为 0;
|
||
- 验收同时检查状态、`audio/mpeg`、`Content-Range`、文件头和浏览器播放进度。
|
||
|
||
### 9. 页面返回 200 但浏览器空白
|
||
|
||
现象:
|
||
|
||
```text
|
||
https://guide.sznhmuseum.org.cn/ HTTP 200
|
||
浏览器控制台:SyntaxError: missing ) after argument list
|
||
```
|
||
|
||
本次根因:
|
||
|
||
- 入口 JS 本身语法损坏,不是 Nginx、证书或后端问题。
|
||
- 故障版本中,构建后手工用 PowerShell `Get-Content` / `Set-Content` 替换腾讯地图 key,错误编码重写了 JS,破坏了中文注释或字符串。
|
||
- 典型错误位置表现为 uni 运行时代码附近的中文注释被改写,导致 `node --check` 报 `missing ) after argument list` 或 `Unexpected identifier`。
|
||
|
||
修复:
|
||
|
||
- 重新生成干净 H5 产物。
|
||
- 使用 `scripts/finalize-h5-build.cjs` 按 UTF-8 替换腾讯地图 key。
|
||
- 部署前、主节点替换后、线上验证时都执行 `node --check`。
|
||
|
||
预防:
|
||
|
||
- 不再手工替换构建产物 JS。
|
||
- 标准构建必须使用 `corepack pnpm build:h5` 或 `corepack pnpm run build:h5:no-minify`。
|
||
- 部署脚本中保留 `node --check "$target/$entry"`。
|
||
|
||
## 最近正式部署记录
|
||
|
||
- 2026-07-13:多次部署 `frontend-miniapp` 到 `172.20.14.21:/data/nginx/html/mobile`,同步到 `172.20.14.23/26/27/33`。确认公网域名 `https://guide.sznhmuseum.org.cn` 正常,静态讲解数据 `guide-contents=720`、`exhibits=4700`、`manifest.sourceHost=172.20.14.21`。
|
||
- 2026-07-13:确认 `pnpm build:h5` 在 terser 阶段可能 OOM,正式部署可使用 `build:h5:no-minify` 兜底。
|
||
- 2026-07-13:新增并验证 `static/icons/poi/shortcut-icons.svg` 线上可访问。
|
||
- 2026-07-15:排查并修复页面 200 但空白问题。根因为 PowerShell 手工替换构建 JS 时破坏 UTF-8,已新增 `scripts/finalize-h5-build.cjs` 统一处理资源复制、腾讯地图 key 替换、JS 语法校验和正式环境字符串扫描。
|
||
- 2026-07-20:拉取远程最新代码前已执行 `git stash push -u -m pre-deploy-prod-local-fixes-20260720232941`,确认“本地补丁已 stash”;随后快进到 `735c5cd`,执行 `git stash pop` 恢复正式环境补丁,无冲突。构建入口为 `assets/index-CsFOrYfU.js`,主节点备份为 `/data/nginx/html/_backups/mobile-before-deploy-20260720233343.tar.gz`,同步脚本最终输出 `All frontend worker nodes synced.`。
|
||
- 2026-07-23:移动端 `17083` 目标配置新增 `/assets/` 一年 immutable 缓存、`/static/Fonts/` 30 天缓存、`/static/nav-assets/` MIME/30 天缓存及全部 `/static/` 缺失资源返回 404。普通 H5 发布改为优先仅同步 mobile 静态目录,不再无条件重写 `sgs-worker.conf`、同步地图应用或重启 PM2。
|
||
- 2026-07-24:逐节点直连复核发现 `.23/.26/.27/.33` 存在 `17083` 缓存配置漂移,仅 `.21` 正确;四台异常节点已分别备份并补齐字体和 hash assets 缓存规则,`nginx -t` 后平滑 reload。修复后字体缓存头、hash asset immutable 缓存头及字体 ETag/304 均为 5/5 一致,20 个工作端点和六类对外入口正常,五台 Nginx 容器均未重启。部署验收新增五节点直连缓存头与 ETag/304 检查,避免公网 `least_conn` 抽样漏检单节点漂移。
|
||
- 2026-07-27:从分支 `codex/import-sgs-frontend-mobile-20260727` 部署提交 `f1047414bbf00a254e1c0cf99755556e45a18e6f`,生产入口 `assets/index-DvpqYP-p.js`,SHA256 `b9f099e2d30d71ca691cc64f20ca6808af834b12dce1c9af8838a6aef1eb893d`;五节点一致,`/engine/` 运行时和移动端导览/讲解流程验证通过。
|
||
- 2026-07-27 至 28 日:新增 `guide-real.sznhmuseum.org.cn` CDN 源站域名并完成五节点 `17083` 分层缓存适配;同步脚本模板已更新。随后修复 CDN 后英文讲解音频失败:绕过错误 Java SDK 媒体代理,并把讲解 API 的 `/minio/` URL 改写为正式域名绝对路径。五组语言/声线音频均验证为 HTTP 206、`audio/mpeg` 和正确 MPEG 文件头,展项 `865546638960193536` 英文音频可完整播放至 48.96 秒。CDN 控制台刷新、边缘 HTTP 到 HTTPS 和 Host/SNI 规则仍需按本文清单人工维护。
|
||
- 2026-07-28:目标分支已快进同步到 `fc93b8d7d32a208a037be576ce3b9da2db3c1fe3`,包含讲解列表、宿主环境判断、展厅卡片图片及相关测试更新。本条只记录文档编制时的代码基线;生产仍以最近一次明确部署记录 `f1047414bbf00a254e1c0cf99755556e45a18e6f` 为准,后续上线 `fc93b8d` 时必须另行补充构建产物、五节点哈希和验收记录。
|