统一维护与错误页面并修复移动端顶部文案

新增品牌维护页和404页面并补齐网关路由
将临时维护公告迁到release外运行态覆盖并增加构建门禁
同步Nginx、Pingora、部署脚本与生产运维文档
隐藏移动端顶部SEO介绍并保留桌面端与H1语义
增加维护页、404页和移动端回归测试
This commit is contained in:
2026-07-13 16:26:27 +08:00
parent 36a8719d0f
commit f4153ebfa7
23 changed files with 781 additions and 144 deletions
@@ -3970,6 +3970,7 @@
- 背景:主站 Nginx 和 Pingora 原先会把任意未知路径回退到 `index.html`,导致 soft 404;共享 `index.html` 也缺少首页 SEO headrobots 和 sitemap 请求会落入 SPA fallback。
- 决策:新增真实 `robots.txt` 和仅首页的 `sitemap.xml`,首页共享 head 提供基础 SEO/OG/JSON-LD 文本但不使用未确认的 image/logo URL;首页 DOM 只保留一个稳定产品定位 H1。Nginx 与 Pingora 只允许当前完整 SPA 路径回退 `index.html`,同前缀未知路径必须返回 404`/admin` 继续走独立子应用。路由变化必须同步三套 Nginx、Pingora、route parity matrix 和自动门禁。
- 2026-07-13 补充:浏览器导航到 Web 未知路径时继续保持 HTTP `404`,但正文统一返回 `public/404.html` 品牌页面和 `public/branding/taonier-404-page.png`;API、探针及非 HTML 请求仍返回原有 `404` 响应,不得把品牌页 HTML 混入接口响应。Nginx 最终 Web catch-all 与 Pingora `Accept: text/html` 分支保持一致。
- 影响范围:`index.html``public/robots.txt``public/sitemap.xml`、首页组件、三套 Nginx、Pingora 网关和路由 parity 门禁。
- 验证方式:前端定向测试与构建、`npm run check:nginx-spa-routes``npm run check:pingora-route-parity``npm run check:pingora-gateway-smoke``npm run check:encoding``git diff --check`,部署后同时抽查根级未知路径和 `/creation/not-exist` 等同前缀未知路径。
@@ -4013,3 +4014,11 @@
- 边界:查单使用订单所属用户的小程序 `openid`,不把 AppKey、AppSecret、access token 或 `openid` 下发前端;虚拟支付不得误用微信支付 V3 查单。
- 历史单:升级前遗留的 pending 订单不会被 expiration catch-up 覆盖,使用 `spacetime:wechat-virtual-payment:reconcile` 逐单 dry-run,再使用当次 `applyFingerprint` 明确 `--apply`。脚本每次重读本地订单与微信查单结果,指纹漂移、非 `2/3/4`、单号/金额/支付类型 `order_type=0/7` 不一致或非官方 endpoint 时默认拒绝入账。
- 验证方式:`cargo test -p platform-wechat --manifest-path server-rs/Cargo.toml``cargo test -p api-server --manifest-path server-rs/Cargo.toml virtual_payment_query``npm run check:wechat-virtual-payment-reconcile``npm run check:encoding``git diff --check`
## 2026-07-13 临时维护公告改为 release 外运行态覆盖
- 背景:一次性停服公告曾直接提交到 `public/maintenance.html`,后续 Web Build 将它持续打入 `web.tar.gz`,每次 Web Deploy 或再次进入维护都会重新显示已经过期的公告。
- 决策:`public/maintenance.html` 永久作为无日期、无具体时段的默认维护页,并使用 `public/branding/taonier-maintenance-page.png` 作为品牌视觉;生产 Web 打包必须对最终 `web/maintenance.html` 执行临时文案门禁。临时公告通过 `maintenance-on.sh --page-file <公告HTML>` 原子安装到 `/var/lib/genarrative/maintenance/page.html`Nginx 与 Pingora 优先读取该运行态文件,缺失时回退 Web 制品默认页。
- 生命周期:新维护窗口未提供 `--page-file` 时清理 marker 外残留公告;同一窗口内 Stdb / API 发布重复调用 `maintenance-on.sh` 时保留已安装公告;`maintenance-off.sh` 同时清理 marker 和公告页。Web Deploy 不再拥有临时公告事实源。
- 影响范围:默认维护页、维护开关脚本、Nginx snippet、Pingora 配置与 smoke、生产 Web 发布包门禁和生产运维文档。
- 验证方式:`npm run check:maintenance-page``npm run check:nginx-spa-routes``cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml``npm run check:pingora-gateway-smoke``npm run check:production-ops``npm run check:encoding``git diff --check`
+10 -2
View File
@@ -2964,8 +2964,8 @@
- 现象:`/not-exist` 已返回 404,但 `/creation/not-exist``/runtime/not-exist``/puzzle/not-exist` 仍返回 200 首页,搜索引擎继续判定为 soft 404。
- 原因:Nginx 或 Pingora 使用 `/creation/*``/runtime/*` 等宽前缀作为 SPA fallback,前端对未知路径又回到平台首页;只验收根级未知 URL 无法发现该问题。
- 处理:SPA fallback 必须精确匹配当前真实完整路径,同时允许前端已有的大小写归一和尾部斜杠;最终 catch-all 只提供真实静态文件失败返回 404。路由增删同步三套 Nginx、Pingora、route parity matrix 和路由门禁。
- 验证:除全部真实 SPA 路径外,至少检查 `/not-exist``/creation/not-exist``/runtime/not-exist``/puzzle/not-exist` 均返回 404;维护模式仍保持页面 503 优先语义。
- 处理:SPA fallback 必须精确匹配当前真实完整路径,同时允许前端已有的大小写归一和尾部斜杠;最终 catch-all 只提供真实静态文件。浏览器 HTML 导航失败返回品牌 `404.html`,但状态码仍为 404;API、探针和非 HTML 请求保持原有 404 响应。路由增删同步三套 Nginx、Pingora、route parity matrix 和路由门禁。
- 验证:除全部真实 SPA 路径外,至少检查 `/not-exist``/creation/not-exist``/runtime/not-exist``/puzzle/not-exist` 均返回 404`Accept: text/html` 的未知 Web 路径正文命中品牌页,不带 HTML Accept 的请求不得命中品牌页;维护模式仍保持页面 503 优先语义。
- 关联:`src/routing/appRoutes.tsx``src/routing/appPageRoutes.ts``deploy/nginx/``deploy/container/nginx.conf``server-rs/crates/pingora-gateway/src/main.rs`
## Jenkins Job UI 参数会被 SCM Jenkinsfile 覆盖
@@ -2991,6 +2991,14 @@
- 处理:Full 使用 `EXIT_MAINTENANCE_MODE_AFTER_COMPLETION` 表达产品选择,Stdb Publish 和 API Deploy 全程固定保持维护,Web Deploy 成功后才进入独立最终退出阶段;API Deploy 的独立 `KEEP_MAINTENANCE_MODE` 再转换为脚本 `--keep-maintenance-mode`。API deploy 还必须把 `production-api-deploy.sh``maintenance-on.sh``maintenance-off.sh` 从同一 build artifact 复制进 current release,否则 Full 最终阶段即使有选项也找不到随包退出脚本。默认值仍在 Full 结束时退出维护,避免定时 dev 发布行为变化。
- 验证:API deploy fixture 必须覆盖成功发布并保留 marker,还要断言 current release 中三个部署 / 维护脚本存在;生产运维静态门禁同时反查 Full 参数、下游透传、API Deploy 参数和脚本 flag。推送后用 fail-closed 首阶段运行刷新 live Job 参数,再核对 `config.xml`,不能只看仓库文件。
## 临时维护公告不能提交进版本化默认页
- 现象:现场已恢复通用维护页,但后续 Web Deploy 或下一次进入维护后,又显示昨天的“今天晚上 HH:MM~HH:MM”公告。
- 原因:`public/maintenance.html` 会被 Vite 复制进 `web.tar.gz`Web Deploy 解包后把 `/srv/genarrative/web` 指向新制品;临时公告一旦进入该源码,就会成为每次发布都恢复的长期内容。旧维护 on / off 只控制 marker,浏览器缓存不是根因。
- 处理:版本化默认页只保留无日期通用文案;临时公告用 `maintenance-on.sh --page-file <公告HTML>` 安装到 `/var/lib/genarrative/maintenance/page.html`。Nginx / Pingora 优先读取运行态公告,退出维护时同步清理;不要再原地编辑 `/srv/genarrative/web/maintenance.html` 或提交临时公告到 `public/`
- 验证:`npm run check:maintenance-page` 必须拒绝相对日期、具体日期和具体时间,并覆盖公告安装、同窗口保留、退出清理与新窗口清残留;Pingora smoke 必须证明运行态公告优先且删除后回退默认页。
- 关联:`public/maintenance.html``scripts/deploy/maintenance-on.sh``scripts/deploy/maintenance-off.sh``deploy/nginx/snippets/genarrative-maintenance.conf``server-rs/crates/pingora-gateway/src/main.rs`
## 遮罩点击关闭必须校验完整指针序列
- 现象:在弹窗内容内按下鼠标,拖到弹窗外的遮罩上松开时,弹窗被误关闭。
@@ -545,7 +545,7 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
| 主站 SPA allowlist | 只对当前前端完整路由及兼容恢复路径 `/creation/rpg/agent` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist``/runtime/not-exist``/puzzle/not-exist` 不进入 SPA fallback。 |
维护模式下,公网 API-like 路由返回 JSON `503`公网 Web 静态路由优先返回 `maintenance.html`,不存在时返回纯文本 `503`。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
维护模式下,公网 API-like 路由返回 JSON `503`公网 Web 静态路由先读取 `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_PAGE_FILE` 指向的 release 外运行态公告,缺失时回退 `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT/maintenance.html`两者都不存在时返回纯文本 `503`版本化默认页不得包含日期或具体时段,临时公告由 `maintenance-on.sh --page-file` 安装并在 `maintenance-off.sh` 时清理。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
代理失败时,API / SpacetimeDB 等代理路由返回统一 JSON 网关错误;本地静态路由仍保持对应 HTTP 错误状态。
静态 `Range` 只支持单段 bytes range;多段 range 暂按完整文件返回,避免在正式替换前引入 multipart 响应面。`If-None-Match` / `If-Modified-Since` 优先于 `Range` 判定,命中时仍返回 `304``If-Range` 日期匹配时继续返回 `206`,日期旧于文件或弱 ETag 校验器时回完整 `200``206` / `304` / `416` 不做 gzip 压缩,避免 `Content-Range` 语义被响应体改写破坏。Gateway smoke 会用固定 `X-Request-Id` 对账静态 `304``405``206``416` 的 Pingora access log 行,确认本地响应状态也进入正式切换证据链。
静态路由只允许 `GET` / `HEAD` 读取;其它方法在确认命中静态候选后返回 `405` 并写入 `Allow: GET, HEAD`,缺失文件仍返回 `404`,避免直连后错误客户端把静态入口当作可写接口。
@@ -411,7 +411,7 @@ Nginx 与 Pingora 在维护 marker 存在时对内网来源绕过整站维护闸
该规则只绕过网关维护响应,不会自动拉起 api-server、SpacetimeDB 或其它已停止的服务。人工执行 `maintenance-on.sh` 且后端仍运行时,可以从内网继续访问整站和修改后台数据;`pause-after-stdb` 会停止旧 API/controller/worker,在 API 被停期间静态页面可能仍可加载,但普通 API 与 `/admin/api/**` 仍不可用。验证使用 `npm run check:nginx-spa-routes``npm run check:pingora-route-parity``cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml``npm run check:pingora-gateway-smoke``npm run check:production-ops`,不要在 live 机器上为测试临时创建维护 marker。
维护页源码固定为 `public/maintenance.html`正常 Web 构建由 Vite 复制到发布包根目录的 `web/maintenance.html`。计划内停服需要临时更新公告时,先更新该源码,避免后续 Web Deploy 把现场公告覆盖回旧内容;现场紧急替换必须原子写入当前 `/srv/genarrative/web/maintenance.html`,并同时用 `genarrative.world``www.genarrative.world` 的真实 HTTPS 响应校验 `503` 和公告正文。
版本化默认维护页固定为 `public/maintenance.html`使用 `public/branding/taonier-maintenance-page.png` 作为品牌视觉,只允许保存无日期、无具体时段的通用文案;正常 Web 构建由 Vite 复制到发布包根目录的 `web/maintenance.html`,并由 `check-maintenance-page.mjs` 在打包前拒绝“今天 / 今晚”、具体日期或 `HH:MM` 等临时公告。计划内停服的临时公告必须放在 release 外文件中,通过 `/opt/genarrative/current/scripts/deploy/maintenance-on.sh --page-file <公告HTML> <维护原因>` 原子安装到 `/var/lib/genarrative/maintenance/page.html`。Nginx 与 Pingora 在该文件存在时优先返回它,缺失时回退当前 Web 制品的默认维护页;同一维护窗口内 Stdb / API 的后续 `maintenance-on.sh` 调用保留已安装公告,`maintenance-off.sh` 同时删除 marker 和运行态公告,避免下次维护复活旧内容。公告启用后同时用 `genarrative.world``www.genarrative.world` 的真实 HTTPS 响应校验 `503` 和公告正文。
生产 Jenkins 的 `Pipeline script from SCM` 由 Jenkins controller 读取 Jenkinsfile。`Genarrative-Server-Provision` 是服务器初始化流水线,Job 配置里的 SCM URL 必须使用 controller 本机可访问的仓库路径或内网 Gitea 地址,不能使用 `https://git.genarrative.world/...`;否则日志一开始的 `Checking out git ... to read jenkins/Jenkinsfile.production-server-provision` 就会先从公网拉 Jenkinsfile。构建类流水线和 `Genarrative-Server-Provision` 的 Jenkinsfile 内部源码准备阶段统一使用 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins SSH 凭据 `genarrative-local-gitea-ssh`;不再配置 `https://git.genarrative.world/...` 公网 fallback,也不再默认使用 `https://git.genarrative.world/git/GenarrativeAI/Genarrative.git`。所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true``depth=1``noTags=true``honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本,避免产物 commit 与部署脚本 commit 漂移;Server-Provision 也不在目标 dev / release agent checkout Git,而是由 Jenkins 构建节点先准备 provision 脚本与配置并上传给目标 agent。