# frontend-miniapp 正式环境部署手册 最后更新:2026-07-15 ## 适用范围 本文档适用于将本地仓库 `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 写入仓库文档或提交记录。 ## 部署前检查 进入项目目录: ```powershell cd 'F:\深圳自然博物馆\frontend-miniapp' ``` 检查分支、远程仓库和本地改动: ```powershell git status --short --branch --untracked-files=all git log -1 --pretty=format:'%h %s' git remote -v ``` 正式环境当前依赖一组本地生产补丁,拉取远程代码前必须保护。常见补丁文件包括: ```text .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 ``` ## 拉取远程最新代码 先临时保存本地生产补丁: ```powershell $stamp = Get-Date -Format 'yyyyMMddHHmmss' $msg = "pre-deploy-local-prod-fixes-$stamp" if (git status --porcelain) { git stash push -u -m $msg Write-Output "STASHED=$msg" } ``` 拉取远程最新代码: ```powershell git fetch origin git pull --ff-only origin master 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 ``` 如出现冲突,先解决冲突并重新确认生产配置,再继续构建。 ## 本地构建 安装依赖并做类型检查: ```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 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' ``` ## 执行同步脚本 ```powershell ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash /data/scripts/sync-frontend-nodes.sh' ``` 预期最后输出: ```text All frontend worker nodes synced. ``` 同步脚本会同步 `/data/nginx/html`,同时也会处理 `/data/apps/sgs-map` 并通过 PM2 重启 `sgs-map`。这是当前脚本行为,部署 mobile 时也会看到相关输出。 ## 线上验证 公网验证: ```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" ``` 入口 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 应一致。 ## 回滚 主节点部署脚本会在 `/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" 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' ``` 回滚后仍需执行同步脚本: ```powershell ssh -o StrictHostKeyChecking=no root@172.20.14.21 'bash /data/scripts/sync-frontend-nodes.sh' ``` ## 已遇到的问题与处理 ### 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 '<', "=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 但浏览器空白 现象: ```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 语法校验和正式环境字符串扫描。