16 KiB
frontend-miniapp 正式环境部署手册
最后更新:2026-07-21
适用范围
本文档适用于将本地仓库 F:\深圳自然博物馆\frontend-miniapp 的 H5 产物部署到深圳自然博物馆正式环境。
- 正式域名:
https://guide.sznhmuseum.org.cn - 主节点:
172.20.14.21 - 部署目录:
/data/nginx/html/mobile - Nginx 容器:
sgs-nginx - 同步脚本:
/data/scripts/sync-frontend-nodes.sh - 同步节点:
172.20.14.23、172.20.14.26、172.20.14.27、172.20.14.33 - 远程仓库:
http://192.168.0.93:3333/lyf/frontend-miniapp.git
不要把服务器密码、证书私钥、token、真实后台账号、腾讯地图正式 key 写入仓库文档或提交记录。
部署前检查
进入项目目录:
cd 'F:\深圳自然博物馆\frontend-miniapp'
检查分支、远程仓库和本地改动:
git status --short --branch --untracked-files=all
git log -1 --pretty=format:'%h %s'
git remote -v
正式环境当前依赖一组本地生产补丁,拉取远程代码前必须保护。常见补丁文件包括:
.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注入,不能写入文档或提交记录。
拉取远程最新代码
先临时保存本地生产补丁:
$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:
Saved working directory and index state On master: pre-deploy-prod-local-fixes-YYYYMMDDHHmmss
STASHED=pre-deploy-prod-local-fixes-YYYYMMDDHHmmss
拉取远程最新代码:
git fetch origin
git pull --ff-only origin master
git log -1 --pretty=format:'%h %s'
恢复生产补丁:
$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。
本地构建
安装依赖并做类型检查:
corepack pnpm install --frozen-lockfile
$env:NODE_OPTIONS='--max-old-space-size=16384'
corepack pnpm type-check
设置正式构建变量。腾讯地图 key 只通过环境变量注入,不写入仓库:
$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 构建:
corepack pnpm build:h5
build:h5 会执行:
uni build -p h5 --mode productionnode 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 压缩阶段出现内存不足,可使用不压缩构建:
corepack pnpm run build:h5:no-minify
严禁再用 PowerShell Get-Content / Set-Content 手工替换构建后的 JS。PowerShell 默认编码容易破坏 UTF-8 中文注释或字符串,曾导致线上入口 JS 语法错误、页面空白。
构建产物检查
构建脚本会自动输出类似:
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:
$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)
预期无错误输出。
打包与上传
$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
执行:
$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
docker exec sgs-nginx nginx -t
docker exec sgs-nginx nginx -s 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'
执行同步脚本
ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash /data/scripts/sync-frontend-nodes.sh'
预期最后输出:
All frontend worker nodes synced.
同步脚本会同步 /data/nginx/html,同时也会处理 /data/apps/sgs-map 并通过 PM2 重启 sgs-map。这是当前脚本行为,部署 mobile 时也会看到相关输出。
线上验证
公网验证:
$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"
入口 JS 验证:
$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 通过,旧测试地址和占位符检查无输出。
五台节点一致性验证:
$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 应一致。
回滚
主节点部署脚本会在 /data/nginx/html/_backups 下生成备份,例如:
/data/nginx/html/_backups/mobile-before-deploy-YYYYMMDDHHmmss.tar.gz
回滚主节点:
$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"
docker exec sgs-nginx nginx -t
docker exec sgs-nginx nginx -s reload
"@
$script | ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash -s'
回滚后仍需执行同步脚本:
ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash /data/scripts/sync-frontend-nodes.sh'
已遇到的问题与处理
1. terser 压缩阶段内存不足
现象:
[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 混入正式环境
现象:
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
现象:
Access to XMLHttpRequest at 'https://apis.map.qq.com/...' has been blocked by CORS policy
处理:
- 避免前端直接跨域请求不可 CORS 的接口。
- 需要由 Nginx 或后端代理转发第三方地图 API,前端访问同源路径。
- 构建产物中的腾讯地图 key 占位符必须替换为正式 key。
4. 3D 模型或 JSON 返回 HTML
现象:
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
现象:
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. 页面返回 200 但浏览器空白
现象:
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.。