多分支自动部署转测:用泛域名给每个分支生成独立测试环境
git: 离线包管理平台:https://gitlab.com/offline-package/offline-package-platform
h5页面:https://gitlab.com/offline-package/trade_h5_offline_frontend_template
安卓项目:https://gitlab.com/offline-package/OfflinePackageAndroid
正式环境: https://offline-package.keen-tech.top/
测试环境:https://offline-package-test.keen-tech.top/
H5 feature分支 https://feature-deploy-test.test.keen-tech.top/trade_h5/index.html
Test-xxxx 分支 https://test.test.keen-tech.top/trade_h5/index.html 对应有离线包
main/master分支 正式环境 https://h5.keen-tech.top/trade_h5/index.html 对应有离线包
安卓体验包二维码:http://cdn.keen-tech.top/android-apks/app-debug-offline-v2.apk

这篇单独聊 trade_h5_offline_frontend 里的多分支自动部署转测方案。
它解决的是一个很常见的协作问题:开发同学推了一个 feature/xxx 分支,测试同学不应该再问“你部署到哪了”“地址发我一下”“这个环境是不是最新”。这些事情应该由 CI 自动完成。
目标效果是:
git push feature/pay-flow
-> GitLab CI 自动构建
-> dist/ 同步到 ECS 对应目录
-> 生成 https://feature-pay-flow.test.keen-tech.top/trade_h5/index.html
-> 注册测试离线包
-> 飞书通知测试同学在h5 页面自动化构建和打包的逻辑
┌─────────────────┬─────────────────────────────────────────────┐
│ 分支 │ 效果 │
├─────────────────┼─────────────────────────────────────────────┤
│ test / test-xxx │ 构建 + 部署 + 注册离线包到测试管理平台 │
├─────────────────┼─────────────────────────────────────────────┤
│ feature-xxx │ 构建 + 部署,不注册离线包,不在管理平台 │
├─────────────────┼─────────────────────────────────────────── ─┤
│ main │ 构建 + 部署到正式环境,+ 注册离线包到正式管理平台 │
└─────────────────┴─────────────────────────────────────────────┘安卓加载离线包逻辑
┌──────┬───────────────────────────────────────────┬───────────────────────────────────────┐
│ 环境 │ H5 URL │ 拦截器行为 │
├──────┼───────────────────────────────────────────┼───────────────────────────────────────┤
│ 开发 │ http://10.0.2.2:5173/ │ 不匹配任何前缀 → 走网络 → Vite 热更新 │
├──────┼───────────────────────────────────────────┼───────────────────────────────────────┤
│ 测试 │ https://test.test.keen-tech.top/trade_h5/ │ 匹配 TEST_PREFIX → 拦截 → 本地文件 │
├──────┼───────────────────────────────────────────┼───────────────────────────────────────┤
│ 正式 │ https://h5.keen-tech.top/trade_h5/ │ 匹配 CDN_PREFIX → 拦截 → 本地文件 │
└──────┴───────────────────────────────────────────┴───────────────────────────────────────┘先用大白话理解
这套方案的核心不是“写一个复杂部署系统”,而是用几个简单规则把分支和测试环境对应起来。
大白话说:分支名就是环境名,域名只是把它包装成一个能访问的地址,服务器目录就是这个环境的文件夹。
offline-package 整体架构
/Users/keen/Desktop/code/projects/offline-package 下面其实是三套东西合在一起跑:
它们不是三个孤立 demo,而是一条完整的版本发布链路:
这条链路里,平台是唯一状态源。前端只负责把版本交上来,客户端只相信平台 /check 返回的版本。灰度、全量、拉黑都不散落在 CI 或客户端里。
版本自动部署的主线
“多分支转测”只是在线 H5 的入口地址自动化;“版本自动部署”则是让这份构建产物自动进入版本系统。
完整主线可以这样看:
大白话说:CI 不是只把网页部署出去,它还顺手把同一份网页打包成“客户端可下载的版本”,登记到版本管理平台。
测试和正式平台要隔离
版本自动部署最怕“测试包进正式环境”。这个项目把平台也分成两套:
这意味着:
feature/pay-flow
-> H5 地址:https://feature-pay-flow.test.keen-tech.top/trade_h5/index.html
-> 版本平台:https://offline-package-test.keen-tech.top
main
-> H5 地址:https://h5.keen-tech.top/trade_h5/index.html
-> 版本平台:https://offline-package.keen-tech.top测试包和正式包分库、分目录、分平台,客户端也按环境请求对应 /check,这样不会把测试版本误下发到正式用户。
为什么用泛域名
如果不用泛域名,每新增一个分支环境,就要做三件麻烦事:
- 新增 DNS 解析。
- 新增 Nginx server 配置。
- reload Nginx。
这显然不适合 feature 分支。分支可能一天建很多个,也可能一周后就删了。
泛域名方案把这件事简化成:
这样新分支上线时不需要改基础设施,只要把文件放到正确目录。
域名怎么落到目录
方案文档里的 Nginx 关键配置是:
server {
listen 443 ssl;
server_name ~^(?<branch>[a-z0-9-]+)\.test\.keen-tech\.top$;
root /data/test/$branch;
index index.html;
location /trade_h5/ {
try_files $uri $uri/ /trade_h5/index.html;
}
}请求路径会这样走:
这个设计最舒服的地方是:Nginx 只配置一次,以后新增分支只新增目录。
CI 先算清楚部署目标
项目里有个脚本 scripts/ci/resolve-deploy-target.mjs,它专门把“当前分支”翻译成部署上下文。
它的判断规则很直接:
脚本会生成 deploy.env:
DEPLOY_ENV=branch
DEPLOY_ENV_NAME=test/feature-pay-flow
DEPLOY_BRANCH_SLUG=feature-pay-flow
DEPLOY_ORIGIN=https://feature-pay-flow.test.keen-tech.top
DEPLOY_REMOTE_DIR=/data/test/feature-pay-flow/trade_h5
PLATFORM_API_BASE=https://offline-package-test.keen-tech.top
VITE_H5_CDN_BASE=https://feature-pay-flow.test.keen-tech.top/trade_h5/
APP_URL=https://feature-pay-flow.test.keen-tech.top/trade_h5/index.html后续 job 都读这份文件。这样 build、部署、离线包注册、通知使用的是同一套变量,不会出现“构建用一个域名,通知发另一个域名”的错位。

一份 dist 同时服务两件事
这个项目比较特殊,因为它既有在线 H5,也有 App 离线包。
所以同一份 dist/ 在 CI 里会走两条路:
scripts/ci/register.mjs 就是为 CI 场景准备的。它和本地 scripts/release.mjs 的区别是:
这就是它比 release.mjs 更适合流水线的原因。
版本平台接住了什么
前端注册版本时,请求的是平台的管理接口:
POST /api/admin/offline-package/register它会带上这些信息:
平台收到后会做三件事:
注意初始状态是 draft,不会立刻下发。这样 CI 自动注册不会等于自动放量,中间还留了人工确认、灰度和回滚空间。
从 draft 到客户端更新
版本自动部署之后,还要经过版本状态机。
Android 客户端请求:
GET /api/offline-package/v1/check?bizId=trade_h5&localVersion=0&deviceId=xxx&appVersion=1.0.0平台会从新到旧找候选版本:
这就是“版本自动部署”的闭环:CI 自动注册版本,平台人工控制放量,客户端按平台状态自动更新。
完整流水线怎么跑
把代码和方案合起来看,一条测试分支流水线可以拆成五步:
大白话说,流水线先问“我是谁,要去哪”,再构建,再一份给浏览器,一份给 App,最后把地址告诉测试。
缓存策略不能省
测试环境也要处理缓存,否则测试同学会遇到“你说部署了,但我刷新还是旧页面”。
Nginx 里建议这样分:
入口不缓存,静态资源长缓存,这是前端静态站最稳的组合。
总览页和自动清理
测试同学不一定记得每个分支的 slug,所以项目里放了 deploy/server/generate_index.sh。
它会扫描:
/data/test/*/trade_h5/index.html然后生成一个总览页:
https://test.keen-tech.top每个在线分支都会出现在列表里,点一下就能进入对应测试环境。
清理也有两层:
这里的双保险很重要。GitLab 管环境生命周期,服务器定时任务管磁盘空间。少一个都容易留下垃圾环境。
为什么这个方案适合转测
它的价值不只是“有一个链接”,而是把转测动作标准化了。
这套方案最值得保留的边界是:分支环境只是文件目录,不是基础设施配置。只要坚持这个边界,测试环境就能低成本地创建、访问和销毁。
最后更新:2026-03-05
