一、背景与目标
H5 项目频繁发版,手动部署流程繁琐:
arduino
开发 → 本地 npm run build → 手动登录 OSS 控制台 → 清空旧文件 → 上传新文件 → 通知测试
存在以下痛点:
- 步骤多:每次发布需要 5-7 个手动步骤
- 易出错:忘记清空旧文件导致缓存问题、上传错目录、漏传文件
- 不可追溯:无法确定当前线上是哪个 commit 构建的
- 环境不统一:开发者本地 Node.js 版本各不相同
目标:Push 代码即自动部署,全程无需人工干预,构建可追溯、可回滚。
二、整体架构
scss
┌──────────┐ Webhook ┌──────────┐ ossutil sync ┌──────────┐ CDN ┌──────────┐
│ GitLab │ ───────────────▶│ Jenkins │ ────────────────▶│ 阿里云 │ ──────▶│ 用户浏览器 │
│ (代码仓库) │ │ (CI 流水线)│ │ OSS │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
- GitLab:存放业务代码和 Jenkins Pipeline 脚本
- Jenkins:接收 Webhook,执行 Pipeline Job,完成拉代码 → 装依赖 → 构建 → 上传
- 阿里云 OSS:静态资源存储,通过 CDN 分发
三、仓库与职责
| 仓库 | 职责 | 内容 |
|---|---|---|
| 部署脚本仓库 | 集中管理所有项目的 Jenkins Pipeline 脚本 | Groovy 脚本文件 |
| 业务仓库 | H5 应用源码 | Vue 业务代码、构建配置 |
四、Jenkins 环境
- 节点:Linux (CentOS 7),系统自带 Node.js 22.0.0
- 凭据管理:所有敏感信息(Git SSH Key、OSS AK/SK)通过 Jenkins 凭据管理,不在脚本中明文暴露
| 凭据类型 | 用途 |
|---|---|
| SSH Key | 拉取 GitLab 代码 |
| Username / Password | 阿里云 OSS AccessKey(testing 环境) |
五、Pipeline 脚本设计
为了方便统一管理,所有项目的 Jenkins Pipeline 脚本统一存放在部署脚本仓库中。Jenkins 任务创建时配置 SCM 指向部署脚本仓库,Script Path 指向对应的 .groovy 文件。这种脚本与应用分离的设计,使得脚本可以独立迭代、多个项目共享相同的基础设施配置,不需要在每个业务仓库里创建 Jenkinsfile。
5.1 目录结构
css
deployment-scripts/
└── scripts/
└── h5-app.groovy # H5 应用部署
5.2 通用阶段
每个项目的 Pipeline 都包含以下 6 个标准阶段:
arduino
┌──────────────────┐
│ 1. Validate │ 校验触发分支,非目标分支直接终止
│ Branch │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 2. Checkout │ 浅克隆目标业务仓库(depth=1,加速拉取)
│ App │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 3. Setup │ 下载 Node.js / pnpm / ossutil(缓存复用)
│ Tools │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 4. Install │ pnpm install(下载依赖)
│ │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 5. Build │ pnpm run build:xxx(构建产物)
│ │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 6. Deploy │ ossutil sync 上传至 OSS(--delete 同步)
│ OSS │
└──────────────────┘
六、H5 应用部署
6.1 触发方式
GitLab testing 分支 Push → Webhook → Jenkins。
6.2 构建配置
| 项 | 值 |
|---|---|
| 触发分支 | testing |
| 包管理器 | pnpm 9.15.0 |
| 安装命令 | pnpm install --no-frozen-lockfile |
| 构建命令 | pnpm run build:test |
| 产物目录 | dist/ |
| OSS 路径 | 应用目录 |
6.3 执行流程
bash
┌──────────────────────────────────────────────────────────────────┐
│ Stage 1: Validate Branch │
│ ───────────────────── │
│ 仅放行 testing 分支,其余分支 push 直接 ABORTED │
└────────────────────────┬─────────────────────────────────────────┘
│ testing ✓
▼
┌──────────────────────────────────────────────────────────────────┐
│ Stage 2: Checkout App │
│ ──────────────────── │
│ 浅克隆 H5 应用仓库 testing 分支 → WORKSPACE/app/ │
└────────────────────────┬─────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Stage 3: Setup Tools │
│ ──────────────────── │
│ 在工作目录 .tools/ 下准备构建工具(首次下载后缓存复用): │
│ │
│ ├─ Node.js 22.21.0 │
│ │ curl https://nodejs.org/dist/v22.21.0/node-v22.21.0-... │
│ │ tar -xJf → .tools/node-v22.21.0-linux-x64/ │
│ │ │
│ ├─ pnpm 9.15.0 │
│ └─ ossutil 1.7.19 │
└────────────────────────┬─────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Stage 4: Install │
│ ───────────────── │
│ ★ 关键:export PATH=.tools/node-xxx/bin:$PATH │
│ 确保 pnpm 及 vite 子进程使用 22.21.0,而非系统的 22.0.0 │
│ │
│ pnpm install --no-frozen-lockfile │
│ · 不使用 frozen-lockfile,允许 pnpm 根据 package.json 更新 lockfile │
└────────────────────────┬─────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Stage 5: Build │
│ ─────────────── │
│ export PATH=.tools/node-xxx/bin:$PATH (同上) │
│ pnpm run build:test │
│ └─ vite build --mode test │
│ └─ 产物写入 dist/ 目录 │
│ │
│ test -d dist ← 校验产物存在 │
└────────────────────────┬─────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Stage 6: Deploy OSS │
│ ────────────────── │
│ ossutil sync -f dist/ oss://bucket/应用目录/ --delete │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────┐
│ 部署成功! │
└──────────┘
6.4 为什么需要自建 Node.js
go
系统 Node.js 版本:22.0.0
H5 应用 package.json 的 engines:
"node": "^20.19.0 || >=22.12.0"
^^^^^^^^
22.0.0 不满足 >=22.12.0
Vite 7 依赖 oxc-parser 需要原生 binding:
· Node.js 22.0.0 → 不兼容,构建报错 "Cannot find native binding"
· Node.js 22.21.0 → 兼容 ✓
解决方案 不是在 Jenkins 服务器上升级 Node.js(影响其他任务),而是在流水线内自行下载兼容版本到工作目录,通过 export PATH 确保所有子进程使用正确版本。Job 之间互不影响。
七、Jenkins 任务配置指南
以下流程图描述了新建一个项目的完整配置步骤。
7.1 新建 Jenkins Pipeline 任务的完整流程
vbnet
┌─────────────────────────────────────────────────────────────────────────────┐
│ Jenkins 新建任务 全流程 │
└─────────────────────────────────────────────────────────────────────────────┘
STEP 1:Jenkins 首页 → 新建 Item
┌──────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 输入任务名称 │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ component-library-deploy │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ │ ○ Freestyle project │ │
│ │ ● Pipeline(★ 选这个) │ │
│ │ ○ Multi-branch Pipeline │ │
│ │ ○ Folder │ │
│ │ ○ GitHub Organization │ │
│ │ │ │
│ │ [ 确定 ] │ │
│ └─────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────┘
STEP 2:General 配置
┌──────────────────────────────────────────┐
│ │
│ ☑ GitHub project │
│ ☑ 丢弃旧的构建 │
│ 策略:Log Rotation │
│ 保持构建的天数:30 │
│ 保持构建的最大个数:50 │
│ │
└──────────────────────────────────────────┘
STEP 3:构建触发器
┌──────────────────────────────────────────┐
│ │
│ ● 触发远程构建 (例如, 使用脚本) │
│ 身份验证令牌:<自动生成的 Token> │
│ │
│ → Webhook URL 将生成为: │
│ http://<jenkins>/job/<任务名>/ │
│ build?token=<Token> │
│ │
│ 注:如使用 GitLab Plugin 则选 │
│ ☑ Build when a change is pushed │
│ to GitLab. GitLab webhook URL: │
│ http://<jenkins>/project/<任务名> │
│ │
└──────────────────────────────────────────┘
STEP 4:流水线定义(★ 关键步骤)
┌──────────────────────────────────────────┐
│ │
│ 定义:● Pipeline script from SCM │
│ │
│ SCM:Git │
│ ┌─────────────────────────────────────┐ │
│ │ Repository URL: │ │
│ │ git@<gitlab>:xxx/deployment-scripts │ │
│ │ │ │
│ │ Credentials:GitLab (SSH Key) │ │
│ │ │ │
│ │ Branches to build:*/master │ │
│ │ │ │
│ │ Script Path:scripts/app.groovy │ │
│ │ │ │
│ │ Lightweight checkout:☑ │ │
│ └─────────────────────────────────────┘ │
│ │
│ [ 保存 ] │
└──────────────────────────────────────────┘
STEP 5:GitLab Webhook 配置
┌──────────────────────────────────────────┐
│ │
│ 进入 GitLab 目标项目 → Settings → │
│ Webhooks │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ URL:http://<jenkins>/project/<任务名>│ │
│ │ Secret Token:<留空或与 Jenkins 一致> │ │
│ │ │ │
│ │ Trigger: │ │
│ │ ☑ Push events │ │
│ │ ☐ Tag push events │ │
│ │ ☐ Merge request events │ │
│ │ │ │
│ │ 如有分支过滤:输入 testing │ │
│ │ │ │
│ │ ☑ Enable SSL verification │ │
│ │ │ │
│ │ [ Add webhook ] │ │
│ └─────────────────────────────────────┘ │
│ │
│ 添加后点击 Test → Push events 验证 │
│ 返回 HTTP 200 即表示成功 │
│ │
└──────────────────────────────────────────┘
STEP 6:手动触发验证
┌──────────────────────────────────────────┐
│ │
│ Jenkins → 任务名 → Build with │
│ Parameters / Build Now │
│ │
│ 观察 Console Output 确认每个 Stage │
│ 执行成功 │
│ │
│ 检查 OSS 控制台确认文件已更新 │
│ │
└──────────────────────────────────────────┘
7.2 凭据配置
vbnet
Jenkins → Manage Jenkins → Manage Credentials → 全局凭据 → Add Credentials
┌─────────────────────────────────────────────────────┐
│ Kind:SSH Username with private key │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ID: GitLab │ │
│ │ Username: git │ │
│ │ Private Key:<粘贴 SSH 私钥> │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Kind:Username with password │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ID: oss-test │ │
│ │ Username: <OSS AccessKey ID> │ │
│ │ Password: <OSS AccessKey Secret> │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
八、分支保护策略
流水线在第一个 Stage 就进行分支校验:
objectivec
┌──────────────┐
│ GitLab Push │
└──────┬───────┘
│
▼
┌──────────────────┐
│ Validate Branch │
│ │
│ 分支 == testing ? │
└────┬──────┬──────┘
│ YES │ NO
▼ ▼
┌─────────┐ ┌──────────┐
│ 继续构建 │ │ ABORTED │
└─────────┘ │ 流水线终止 │
└──────────┘
groovy
script {
def pushedBranch = env.gitlabBranch?.trim()
if (pushedBranch && pushedBranch != APP_BRANCH) {
currentBuild.result = 'ABORTED'
error("仅允许 ${APP_BRANCH} 分支触发构建,当前分支: ${pushedBranch}")
}
}
即使 GitLab Webhook 配了分支过滤也不完全可靠(可被人为修改),Pipeline 内部的二次校验是最后一道防线。
九、如何新增一个项目的自动化部署
9.1 步骤总览
vbnet
Step 1 ────▶ Step 2 ────▶ Step 3 ────▶ Step 4 ────▶ Step 5
编写 commit & Jenkins GitLab Push 测试
Groovy push 到 新建 配置 业务代码
脚本 部署仓库 Pipeline Webhook 验证
9.2 详细步骤
Step 1:编写 Pipeline 脚本
在部署脚本仓库 scripts/ 目录下新建 项目名.groovy,复制已有模板并修改以下变量:
| 变量 | 说明 | 示例 |
|---|---|---|
APP_GIT_URL |
目标项目的 GitLab 地址 | git@<gitlab>:xxx/project.git |
APP_BRANCH |
触发分支 | testing |
OSS_PREFIX |
OSS 上的目录名 | myApp/ |
| 安装命令 | pnpm install 及参数 |
pnpm install --no-frozen-lockfile |
| 构建命令 | pnpm run build:xxx |
pnpm run build:test |
| 产物目录 | build 后文件所在目录 | dist/ |
| 是否需要自建 Node.js | Jenkins 系统版本不兼容时 | 添加 NODE_VERSION 等变量 |
Step 2:提交推送
bash
cd deployment-scripts/
git add scripts/项目名.groovy
git commit -m "feat: 新增 项目名 的自动化部署脚本"
git push origin master
Step 3:Jenkins 新建任务
参考第七章流程图完成配置。
Step 4:GitLab 配置 Webhook
目标项目 → Settings → Webhooks → 填入 URL(http://<jenkins>/project/<任务名>)。
Step 5:验证
往业务代码的 testing 分支 push 一个 commit,观察 Jenkins Console Output 中各 Stage 是否全部通过。
十、常见问题
Q1:构建时 frozen-lockfile 报错
vbnet
ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile"
because pnpm-lock.yaml is not up to date with package.json
原因 :CI 环境下 pnpm 默认启用 --frozen-lockfile,但 lockfile 与 package.json 不同步。
解决 :改用 pnpm install --no-frozen-lockfile。
Q2:Node.js 版本不兼容
arduino
You are using Node.js 22.0.0. Vite requires Node.js version 20.19+ or 22.12+.
Error: Cannot find native binding.
原因:Jenkins 服务器的 Node.js 版本不满足项目的 engines 要求。
解决 :在 Setup Tools 阶段自行下载兼容版本,并将 bin/ 加入 PATH。详见第七章。
Q3:Groovy 脚本注释解析错误(重中之重!!出现了 N 次!!)
arduino
unexpected token: * @ line 10, column 2
原因 :Jenkins Pipeline 的 Groovy 解析器对 /** */ 块注释有兼容性问题。特别是注释内容中出现 */ 字符(如 Branch:origin/*/master)时,*/ 会被错误识别为注释结束,后续的 * 变成裸乘法运算符。
解决 :统一使用 // 单行注释。
Q4:ossutil sync 后文件未更新
原因:浏览器或 CDN 缓存了旧文件。OSS 上的文件已更新,但 CDN 节点或浏览器本地缓存未刷新。
解决:
- CDN 控制台手动刷新缓存
- 或等待缓存过期(取决于 CDN 缓存策略配置)
- HTML 入口文件设置较短的缓存时间(index.html 建议 no-cache)
Q5:部署后页面白屏
排查流程:
ini
白屏
│
├─ 1. 打开浏览器 F12 → Console,看是否有 JS 报错
│ └─ 404 错误 → OSS 文件不存在,检查产物目录是否正确
│ └─ CORS 错误 → OSS 跨域配置问题
│
├─ 2. 打开 F12 → Network,看 chunk-vendor.js 是否加载成功
│ └─ 加载失败 → 公共路径(base)配置错误
│
├─ 3. 检查 index.html 中的 <script src="./static/...">
│ 相对路径是否正确
│
└─ 4. 检查 OSS 控制台,确认文件是否已上传
└─ 对比本地 dist/ 目录和 OSS 文件列表
附带几张 Jenkins 的配置图:


