💥手把手教你如何从0到1搭建jekins自动化打包发版流水线

0 阅读15分钟

从 0 到 1 搭建 Jenkins 全自动交付流水线:一套脚本打遍多客户、多平台

你是否也经历过这样的发版夜:

  • 前端 dist 手动 pnpm build,后端手动 dotnet publish,再手工拼 zip;
  • 客户 A 要 Windows、客户 B 要麒麟 ARM、客户 C 要统信 x64,每次都得重来一遍;
  • 打包到一半报个错,连锁炸出十几条「系统找不到指定的路径」;
  • 包打好了,还得远程桌面登服务器去拷文件。

这篇教程带你用 Jenkins + 一套参数化构建脚本,把上面这一切压缩成「填 5 个参数 → 点构建 → 浏览器下载 zip」,20~30 分钟自动出包。所有步骤都来自真实落地经验,只讲流程、不谈业务,照着抄就能用。


jekins自动化流水线.png

目录

  1. 我们要解决什么问题
  2. 整体架构与最终产物
  3. 第一章:环境与前置准备(一次性)
  4. 第二章:从 0 安装并配置 Jenkins 任务
  5. 第三章:核心——参数化构建脚本全公开
  6. 第四章:踩坑实录(避坑必看)
  7. 第五章:多客户 / 多平台(异构)怎么打
  8. 第六章:交付包发版前核验清单
  9. 结语

1. 我们要解决什么问题

一个前端(Vue3 + Vite + pnpm)+ 后端(.NET 8)的移动端 H5 项目,要交付给多家客户,且每家客户的操作系统与 CPU 架构都不同(普通 Windows Server、麒麟/统信的 x64 与 ARM64 等)。

核心矛盾有三点:

  • 多客户差异化:不同客户前端标题/底座不同,但不能每来一个客户就改业务代码;
  • 多平台异构:.NET 要按目标平台发布成自包含(self-contained)包,错一个 RID 客户机就跑不起来;
  • 可重复 & 可下载:运维/实施同学不在服务器旁边,也要能随时拉到一个结构正确、内容完整的发版包。

解法就是:用 Jenkins 把「克隆 → 构建 → 推送 → 打包 → 归档下载」串成一条参数化流水线


2. 整体架构与最终产物

Jenkins 任务 delivery-package 一键完成以下阶段:

  1. 克隆交付仓库(内含固定文件:启停脚本、nginx 部署包、部署文档);
  2. 前端构建:克隆前端仓库 → pnpm installpnpm run build:<客户>
  3. 后端构建:克隆后端仓库 → dotnet publish(self-contained,按目标平台);
  4. dist + H5api 推送到交付仓库(固定分支);
  5. 拉最新仓库内容 → 打 zip → 复制到 workspace 供归档下载。

最终产物

H5_XC_<版本>_<平台>_<日期>.zip

最终产物.png

包内结构(发版约定,务必固定):

H5_XC_1.0.0_linux-arm64_20260721.zip
├── dist/                      # 前端构建产物
├── H5api/
│   ├── api/                  # 后端 self-contained 发布产物
│   │   ├── <YourApiProject>  # Linux 下无 .exe 后缀的可执行文件
│   │   ├── appsettings.json
│   │   └── *.so              # 自带 .NET 运行时
│   ├── start-api.sh / .bat   # 启停脚本(按平台复制)
│   ├── stop-api.sh / .bat
│   └── restart-api.sh / .bat
├── nginx部署包/
└── 部署文档.docx

命名规则:H5_XC_<版本>_<平台>_<日期>.zip,平台如 linux-arm64 / win-x64。结构一旦定下,客户机的解压即部署脚本就稳定了。


3. 第一章:环境与前置准备(一次性)

3.1 服务器环境

组件说明
操作系统Windows Server(Jenkins / 后端 / Nginx 同机)
JenkinsWAR 包启动,路径如 <JENKINS_DIR>\jenkins.war
JavaJDK 17(Eclipse Adoptium 等发行版均可)
.NETSDK 8.0.x(后端用)
Nginx反向代理 + 静态资源,子配置如 conf/conf.d/h5.conf
Node / pnpm前端构建,pnpm 建议用 npx pnpm@<版本> 锁定

启动 Jenkins:

java -jar <JENKINS_DIR>\jenkins.war --httpPort=8080

重启 Nginx:

<NGINX_DIR>\nginx.exe -s reload -p <NGINX_DIR>

端口规划建议:本地自研环境用一组端口(前端 8081 / 后端 8001、8082 / 8002…),发版/交付用另一组(如 8083 / 8003 起,客户端口依次顺延 8084/8004…)。建表记下来,别凭记忆

3.2 交付仓库结构(固定文件单独管理)

把"不常变"的东西放进一个 Git 仓库(下文称交付仓库),只放固定文件,构建产物由 Jenkins 运行时推送进去,最后统一拉最新打 zip:

delivery-repo/
├── nginx部署包/                    # 固定,更新时手动 push
├── 部署文档.docx                   # 固定,更新时手动 push
├── scripts/                       # 固定:启停脚本(start/stop/restart × .sh/.bat 共 6 个)
└── .gitignore                     # 只写 *.zip

⚠️ .gitignore 只能忽略 *.zipdist/H5api/scripts/ 必须被追踪。否则 git add -A 不会提交它们,最后拉下来打包是空的。

3.3 前端"配置化打包"(多客户差异化,不碰业务代码)

每个客户只需改源码一次,之后全靠参数驱动:

  1. 新建 .env.<客户名>(与 .env.dev 同级),变量以 VITE_ 开头: VITE_BASE_API=/api VITE_BASE_ROUTE_URL=/ VITE_CUSTOM_TITLE=客户A VITE_CUSTOM_CODE=customerA
  2. package.jsonscripts 加一行:
    "build:customerA": "vite build --mode customerA"
    
  3. .env.customerApackage.json 的改动 commit 并 push 到对应分支。

之后 Jenkins 构建脚本用 pnpm run build:customerA,部署到 html/h5-customerA,Nginx 加一个 server 块即可。纯配置驱动,未来加客户不用改动流水线。

3.4 启停脚本统一管理

6 个脚本(start/stop/restart-api × .sh/.bat提交到交付仓库的 scripts/。构建时脚本按目标平台自动选 .sh(Linux) 或 .bat(Windows) 复制进 H5api/ 根目录,不再依赖服务器本地文件

⚠️ .sh 脚本不要靠构建机的 echo 生成——Windows 的 cmd 是 GBK,写中文/制表符会乱码。放进 Git 仓库用 xcopy 拷进包最稳。


4. 第二章:从 0 安装并配置 Jenkins 任务

4.1 新建任务

  1. 新建 Freestyle project
  2. 勾选 This project is parameterized(参数化构建);
  3. 勾选 Delete workspace before build starts(每次清空工作区,避免脏产物)。

4.2 参数设计

参数类型说明示例
CUSTOMERChoice客户名(决定 env 文件 + build 脚本)customerA / customerB
TARGET_RIDChoice目标平台 RIDwin-x64 / linux-arm64
VERSIONString版本号1.0.0
FRONTEND_BRANCHString前端分支(自动去 origin/ 前缀)dev_1.0.0
BACKEND_BRANCHString后端分支(自动去前缀)release_1.0.0

前后端分支分开传,是因为实际中两端发版分支经常不一致(比如前端带下划线、后端不带)。脚本开头统一做"去 origin/ 前缀"处理,填 origin/dev_1.0.0 也兼容。

4.3 构建步骤

增加一个 Execute Windows batch command,把第三章的完整脚本整段贴进去。

4.4 构建后归档(浏览器直接下载,不用登服务器)

  1. 任务 → 配置 → 滚到最底部「构建后操作」;
  2. 点「增加构建后操作步骤」→ 选 Archive the artifacts
  3. Files to archive 填:H5_XC_*.zip
  4. (可选)勾选 Fingerprint all archived artifacts(便于比对包是否一致)。

构建完成后:进入该次构建页 → 左侧「工件 / Artifacts」→ 点 zip 文件名直接下载。只要浏览器能打开 Jenkins 地址就能取包。


5. 第三章:核心——参数化构建脚本全公开

下面是可直接粘贴的脱敏模板。把 <...> 占位符换成你自己的值即可。<GIT_USER>:<GIT_TOKEN> 是访问令牌,建议用只读部署账号,不要放个人密码。

@echo off
setlocal enabledelayedexpansion

:: 基础环境
set PATH=C:\Program Files\nodejs;C:\Program Files\Git\cmd;%PATH%
git config --global http.sslVerify false
git config --global user.name "jenkins"
git config --global user.email "jenkins@local"

:: ---------- 去掉 Jenkins 传入分支名中的 origin/ 前缀 ----------
set FB=%FRONTEND_BRANCH%
if "%FB:~0,7%"=="origin/" set FB=%FB:~7%

set BB=%BACKEND_BRANCH%
if "%BB:~0,8%"=="origin1/" set BB=%BB:~8%
if "%BB:~0,7%"=="origin/" set BB=%BB:~7%

echo [INFO] 前端分支: %FB%
echo [INFO] 后端分支: %BB%
echo [INFO] 客户: %CUSTOMER%  ^|  平台: %TARGET_RID%  ^|  版本: %VERSION%

cd /d %WORKSPACE%

:: ==================== 步骤0: 克隆交付仓库 ====================
echo.
echo [步骤0] 克隆交付仓库 delivery-repo ...
if exist delivery-repo rmdir /s /q delivery-repo
git clone http://<GIT_USER>:<GIT_TOKEN>@<GIT_HOST>:<GIT_PORT>/<GROUP>/delivery-repo.git delivery-repo
if errorlevel 1 (
    echo [ERROR] delivery-repo 克隆失败,检查仓库地址和权限
    exit /b 1
)

:: ==================== 步骤①: 前端构建 ====================
echo.
echo [步骤1] 克隆前端 (分支: %FB%) ...
if exist h5-vue rmdir /s /q h5-vue
git clone -b %FB% http://<GIT_USER>:<GIT_TOKEN>@<GIT_HOST>:<GIT_PORT>/<GROUP>/h5-vue.git h5-vue
if errorlevel 1 (
    echo [ERROR] 前端克隆失败,检查分支 %FB% 是否存在
    exit /b 1
)

:: ---------- 定制客户白名单:仅白名单客户走 build:客户名,其余走标准版 build:stander ----------
:: 白名单值必须与 Jenkins CUSTOMER 参数的值完全一致(/i 忽略大小写)
set BUILD_SCRIPT=build:stander
if /i "%CUSTOMER%"=="customerA" set BUILD_SCRIPT=build:%CUSTOMER%
if /i "%CUSTOMER%"=="customerB" set BUILD_SCRIPT=build:%CUSTOMER%
echo [INFO] 前端构建命令: pnpm run %BUILD_SCRIPT%

cd h5-vue
call npx pnpm@<PNPM_VERSION> install --no-frozen-lockfile
if errorlevel 1 (
    echo [ERROR] pnpm install 失败
    exit /b 1
)

call npx pnpm@<PNPM_VERSION> run %BUILD_SCRIPT%
if errorlevel 1 (
    echo [ERROR] %BUILD_SCRIPT% 失败!若是定制客户,请确认 package.json 中存在
    echo        build:%CUSTOMER% 脚本,且 .env.%CUSTOMER% 文件已提交。
    exit /b 1
)
echo [OK] 前端构建完成 (%BUILD_SCRIPT%)

:: ==================== 步骤②: 后端构建 ====================
echo.
echo [步骤2] 克隆后端 (分支: %BB%) ...
cd /d %WORKSPACE%
if exist h5-api rmdir /s /q h5-api
git clone -b %BB% http://<GIT_USER>:<GIT_TOKEN>@<GIT_HOST>:<GIT_PORT>/<GROUP>/h5-api.git h5-api
if errorlevel 1 (
    echo [ERROR] 后端克隆失败,检查分支 %BB% 是否存在
    exit /b 1
)

:: 目标输出目录(首次构建该客户时自动创建,避免"路径不存在")
if not exist <DELIVERY_DIR>\%CUSTOMER% mkdir <DELIVERY_DIR>\%CUSTOMER%

cd h5-api
dotnet publish <API_PROJECT>\<API_PROJECT>.csproj -c Release -r %TARGET_RID% --self-contained true -o <DELIVERY_DIR>\%CUSTOMER%\H5api-tmp
if errorlevel 1 (
    echo [ERROR] dotnet publish 失败
    exit /b 1
)
echo [OK] 后端发布完成

:: ---- 整理 H5api/api/ 结构 + 复制启停脚本 ----
cd /d <DELIVERY_DIR>\%CUSTOMER%
if exist H5api rmdir /s /q H5api
mkdir H5api\api
xcopy /E /I /Y H5api-tmp\* H5api\api\
if errorlevel 1 (
    echo [ERROR] 整理 H5api 目录失败
    exit /b 1
)
rmdir /s /q H5api-tmp

:: 按平台选择 .sh(Linux) 或 .bat(Windows) 启停脚本
set PLATEXT=sh
echo %TARGET_RID% | findstr /i "win" >nul && set PLATEXT=bat
echo [INFO] 启停脚本扩展名: %PLATEXT%

xcopy /Y %WORKSPACE%\delivery-repo\scripts\start-api.%PLATEXT% H5api\
xcopy /Y %WORKSPACE%\delivery-repo\scripts\stop-api.%PLATEXT% H5api\
xcopy /Y %WORKSPACE%\delivery-repo\scripts\restart-api.%PLATEXT% H5api\
echo [OK] 启停脚本已复制到 H5api/

:: ==================== 步骤③: 推送到交付仓库 ====================
echo.
echo [步骤3] 合并 dist + H5api 到交付仓库 ...
cd /d %WORKSPACE%

if exist delivery-repo\dist rmdir /s /q delivery-repo\dist
if exist delivery-repo\H5api rmdir /s /q delivery-repo\H5api

xcopy /E /I /Y %WORKSPACE%\h5-vue\dist delivery-repo\dist
if errorlevel 1 (
    echo [ERROR] 复制 dist 失败,前端可能未成功构建
    exit /b 1
)
xcopy /E /I /Y <DELIVERY_DIR>\%CUSTOMER%\H5api delivery-repo\H5api
if errorlevel 1 (
    echo [ERROR] 复制 H5api 失败
    exit /b 1
)

cd /d %WORKSPACE%\delivery-repo
git add -A
git commit -m "auto: %CUSTOMER% %TARGET_RID% v%VERSION% %date:~0,4%%date:~5,2%%date:~8,2%"
if errorlevel 1 (
    echo [WARN] 没有变更需要提交(或 commit 为空),继续打包...
) else (
    git push origin <DELIVERY_BRANCH>
    if errorlevel 1 (
        echo [ERROR] 推送失败,检查分支权限或网络
        exit /b 1
    )
)
echo [OK] 已推送到 delivery-repo (<DELIVERY_BRANCH>)

:: ==================== 步骤④: 拉最新打 zip ====================
echo.
echo [步骤4] 拉取最新内容并打包 ...
cd /d %WORKSPACE%
if exist package-temp rmdir /s /q package-temp
mkdir package-temp

git clone --depth 1 http://<GIT_USER>:<GIT_TOKEN>@<GIT_HOST>:<GIT_PORT>/<GROUP>/delivery-repo.git package-temp\src
if errorlevel 1 (
    echo [ERROR] 打包克隆失败
    exit /b 1
)

:: 去掉 .git 元数据和 scripts/ 源码目录(脚本已在 H5api/ 内)
rmdir /s /q package-temp\src\.git 2>nul
if exist package-temp\src\scripts rmdir /s /q package-temp\src\scripts

if not exist <DELIVERY_DIR>\%CUSTOMER% mkdir <DELIVERY_DIR>\%CUSTOMER%

cd /d %WORKSPACE%\package-temp\src
powershell -Command "Compress-Archive -Path '*' -DestinationPath '<DELIVERY_DIR>\%CUSTOMER%\H5_XC_%VERSION%_%TARGET_RID%_%date:~0,4%%date:~5,2%%date:~8,2%.zip' -Force"
if errorlevel 1 (
    echo [ERROR] 打包失败
    exit /b 1
)
echo [OK] ZIP 已生成

:: ==================== 步骤⑤: 复制到 workspace 供归档下载 ====================
copy "<DELIVERY_DIR>\%CUSTOMER%\H5_XC_%VERSION%_%TARGET_RID%_%date:~0,4%%date:~5,2%%date:~8,2%.zip" "%WORKSPACE%\"
if errorlevel 1 (
    echo [WARN] 复制 zip 到 workspace 失败,可手动从 <DELIVERY_DIR>\%CUSTOMER% 取
)

echo.
echo ============================================
echo   完成: %WORKSPACE%\H5_XC_%VERSION%_%TARGET_RID%_%date:~0,4%%date:~5,2%%date:~8,2%.zip
echo ============================================
exit /b 0

注意:批处理里 Git 地址的 @ 若出现在用户名中需转义为 %%40;用 <GIT_TOKEN> 令牌方式通常不含 @,可忽略。另外 Windows bat 调用 npx 必须加 call,否则后续命令会被截断。


6. 第四章:踩坑实录(避坑必看)

这些都是真金白银踩出来的坑,照做能省你一晚上:

6.1 后端源码硬编码端口 + Windows 端口排除范围

appsettings.json 里 Kestrel 写死了一个端口(如 :8000),而该端口被 Windows 端口排除范围占用,直接启动会报 SocketException (10013)。并且 Kestrel Endpoints 配置优先级高于命令行 --urls,传 --urls 无效。

解法:环境变量注入(不修改源码)。.NET 配置系统会以环境变量覆盖 json 值,层级用 __ 双下划线:

set CommonCfg__UserCode=<YourUserCode>
set CommonCfg__IRHost=<YourIrHost>
set CommonCfg__IRPort=<YourIrPort>
set Kestrel__Endpoints__Http__Url=http://0.0.0.0:<YourPort>

IR 是身份/认证服务,配错会"未找到对应的用户"。这类客户相关配置不要写死在脚本里——放包内 appsettings.json 让客户按环境改,或按客户预填配置文件随包交付。

6.2 pnpm lockfile 报错(团队规范:不删 lock)

CI 环境 pnpm 默认 frozen-lockfile=true,若 pnpm-lock.yamlpackage.json 对不上(有人加依赖但没更新 lock),报 ERR_PNPM_OUTDATED_LOCKFILE

  • ❌ 旧做法:del pnpm-lock.yaml 让 pnpm 重新生成(团队不让删);
  • ✅ 推荐做法:install 加 --no-frozen-lockfile,允许自动更新 lock、不删文件;
  • ✅ 最规范:本地 pnpm install 更新后把新 pnpm-lock.yaml 提交到 git。
call npx pnpm@<PNPM_VERSION> install --no-frozen-lockfile

6.3 echo 里的竖线 | 必须转义

批处理里想 echo 出带 | 的内容(如 客户 | 平台),必须写成 ^|,否则被当成管道符报错 '平台' 不是内部或外部命令

echo 客户 ^| 平台

6.4 分支前缀 origin/

Jenkins Git 插件传入的分支常带 origin/ 前缀,git clone -b origin/dev 会报 Remote branch origin/xxx not found。脚本开头已统一去前缀(见第三章步骤0),新写脚本务必带上。

6.5 客户目录不存在

首次构建某客户时 <DELIVERY_DIR>\<CUSTOMER> 还没创建,publish/打包会报"路径不存在"。在 publish 前加:

if not exist <DELIVERY_DIR>\%CUSTOMER% mkdir <DELIVERY_DIR>\%CUSTOMER%

6.6 .gitignore 只忽略 *.zip

交付仓库的 .gitignore 若误把 dist/H5api/scripts/ 也忽略了,git add -A 不提交它们,最后拉下来打包是空的。只写 *.zip

6.7 空 commit 仍 push 报错

没有变更时 git commit 为空,若仍执行 git push 会报 src refspec <branch> does not match any。脚本里已做判断:commit 失败(空)就跳过 push。同时确认交付仓库默认分支名(常见 master / main),推送分支名须与实际一致。

6.8 交付仓库地址命名空间写错

不同仓库的 Git 命名空间可能不同(比如后端在 <GROUP>/h5-api.git,而交付仓库在另一个 <GROUP>/delivery-repo.git)。地址报 not found 时,先核对命名空间。

6.9 失败即停,别连锁报错

前面某步失败但没 exit,后续命令继续执行会爆出一堆"系统找不到指定的路径"。每个关键操作后加 if errorlevel 1 exit /b 1,失败即停,定位更快。

6.10 仓库地址里的 @ 转义

批处理环境里 Git URL 的 @ 需写成 %%40(仅当用户名含 @ 时)。用令牌方式(<user>:<token>)通常不含 @,可忽略。


7. 第五章:多客户 / 多平台(异构)怎么打

不同客户服务器操作系统 + CPU 架构不同,发布方式分两类:

模式场景发布方式
A. 自研环境dev/test/发版(单机 Windows)环境变量注入,dotnet xxx.dll 直接跑(framework-dependent)
B. 交付客户各客户自有服务器-r <RID> --self-contained true,打 zip 交付,客户自行部署

目标平台 RID 对照(重点)

客户服务器RID说明
Windows 64位win-x64普通 Windows Server
Windows 32位win-x86老旧系统
Linux x64(海光/兆芯)linux-x64麒麟/统信 x86 版
Linux ARM64(飞腾/鲲鹏)linux-arm64麒麟/统信 ARM 版(最常见)
龙芯linux-loongarch64需对应 .NET 支持版本

⚠️ 部署前必须问客户:操作系统 + CPU 架构。同是"麒麟",飞腾芯要 arm64,海光芯要 x64,选错跑不起来。--self-contained true 自带 .NET 运行时,客户机免装 .NET。

客户部署方式:

  • Windows:双击 exe;
  • Linux:chmod +x <YourApiProject> && ./<YourApiProject>

8. 第六章:交付包发版前核验清单

出包后、发给客户前,必查 5 条

  1. 顶层结构 = dist/ + H5api/(外加 nginx部署包/ 与部署文档);
  2. H5api/api/<YourApiProject>Linux 下无 .exe 后缀)+ appsettings.json
  3. H5api/api/ 内有 .so 文件(libcoreclr.so / libhostfxr.so / libclrjit.so 等)→ 确认 self-contained 完整,客户机免装 .NET;
  4. H5api/ 根有 start/stop/restart-api.sh.bat内容无乱码
  5. dist/index.html 存在。

想自动化核验?用 Python 一行 zipfile.ZipFile(path).namelist() 检查上述路径是否都在即可。


9. 结语

配好一次之后,换客户 / 换平台只要改 5 个参数点构建即可,约 20~30 分钟自动出包,包结构永远一致、客户机解压即部署。

这套流水线的精髓就三句话:

  • 配置驱动差异化:前端靠 .env + build:<客户> 脚本,后端靠环境变量注入,绝不在脚本里写死客户配置;
  • 产物可追溯:构建产物推 Git、打 zip、Jenkins 归档,浏览器直接下载,无需登服务器;
  • 失败即停 + 清单核验:把不确定性挡在发版之前。

照着这篇从环境、任务配置、完整脚本到踩坑清单一步步走,你也能拥有一套"填参数就出包"的交付流水线。


附:一键自查表

检查项是否就绪
Jenkins 能启动、能访问
交付仓库 scripts/ 含 6 个启停脚本
.gitignore 只忽略 *.zip
前端已提交 .env.<客户> + build:<客户>
参数化任务 5 个参数已建
构建后操作已配 Archive the artifacts
已知客户操作系统 + CPU 架构

出问题先对照第四章踩坑表自查,80% 的报错都在里面。