
1. 为什么前端也要接Jenkins自动部署前端项目做自动打包部署这件事很多团队一开始都是靠人肉操作本地npm run build然后打开FTP或者宝塔面板把dist目录拖上去再刷新一下CDN。项目小的时候还行一旦上了若依这类前后端分离的框架配合多人协作和频繁发版手动部署几乎必然出问题——有人漏传了文件有人把测试环境的配置发到了生产还有人打包时忘了改VUE_APP_BASE_API导致接口全 404。我见过最离谱的一次是同事把本地没提交的代码打包发到线上排查了两个小时才发现问题。Jenkins的价值就在这里它把拉代码、装依赖、打包、上传、刷新这一整套动作固化成流水线谁触发、什么分支、发到哪个环境全部有据可查。前端的自动打包部署和后端的逻辑不太一样后端一般是打包成 jar 或镜像直接跑前端产出的是静态资源最终要落到 Nginx 或者对象存储上所以流水线的最后一步通常是文件同步而不是服务重启。这个差异决定了前端 Jenkins 配置的写法后面会详细展开。若依RuoYi是一套在国内相当流行的快速开发框架它的 Vue 前端版本ruoyi-ui结构规整package.json里脚本命名统一构建产物固定输出到dist这些特点其实非常适合作自动化。你不需要改造项目结构只要在 Jenkins 里写清楚 Node 版本、构建命令和发布路径就行。这篇文章我会从零开始把一套能直接落地的方案讲透包括 Jenkins 环境准备、Node 构建环境、Git 凭据配置、流水线脚本、Nginx 衔接、以及我自己踩过的十来个坑。不管你之前有没有碰过 Jenkins照着做都能跑通。适合阅读的人群负责若依前端部署的前端同学、需要给团队搭 CI/CD 的全栈或运维、以及正在从手动拖文件往自动化迁移的开发者。文中用的是 Jenkins 的声明式流水线Declarative Pipeline比传统自由风格任务更清晰也更容易做版本管理。2. 整体方案设计与关键选型考量2.1 前端部署和后端部署的本质差异先说清楚一个认知问题否则后面很多配置你会觉得为什么和网上后端教程不一样。后端部署的终点是进程重启Jenkins 干完活通常是执行一段 shell 去kill老进程、起新 jar或者滚动更新 K8s 的 Deployment。前端部署的终点是静态文件替换dist里的 HTML、JS、CSS 换了用户的浏览器下次请求就能拿到新版本中间不涉及任何进程生命周期管理。这个差异带来三个直接影响。第一前端部署天然是覆盖式的老文件不删会越堆越多所以流水线里要有清理旧目录的动作。第二前端有浏览器缓存问题文件名带 hash 的资源可以放心用长缓存但index.html必须禁用缓存否则用户拿到的还是旧入口。第三前端不需要考虑服务启停窗口理论上可以做到用户无感知前提是文件替换的过程中不能让用户访问到半新半旧的状态——这也是后面要用临时目录再切换的原因。2.2 为什么选 Jenkins 而不是其他方案市面上的选择不少GitLab CI、GitHub Actions、Drone、以及各种云厂商的流水线。选 Jenkins 的理由很实在——它太常见了。若依本身的社区教程里 Jenkins 的案例最多遇到问题搜得到答案它是自托管的内网环境、私有 Git 仓库都能适配不依赖外部服务的网络策略插件生态成熟Publish Over SSH、GitLab 插件、钉钉通知插件都是一行配置的事。当然它也有短板Jenkins 的界面确实老派配置项散落在各种全局工具配置里新手第一次上手容易迷路。但只要你理解了它的核心模型——节点Node执行任务Job任务里跑流水线Pipeline——剩下的就是查插件文档的事。对若依这种规模的项目Jenkins 完全够用没必要为了追新去折腾更复杂的方案。2.3 部署架构单机 Nginx 还是别的如果部署架构还没定我建议前端静态资源直接用Nginx托管Jenkins 和 Nginx 可以在同一台机器也可以分开。同一台机器最省事流水线把dist传到本机某个目录Nginx 直接指向它配置简单到不能再简单。分开的好处是构建和运行隔离构建时的高 CPU 占用不会影响线上服务适合访问量稍大的场景。如果你的若依前端要发到多个节点别在 Jenkins 里写一堆 scp 命令逐个推送直接用 rsync 同步到一台中转机再由中转机分发或者干脆上对象存储 CDN把分发交给云服务。手工逐个推的方式节点一多必然漏发。选定架构之后整条流水线的动作就确定了拉取 Git 代码 → 切到指定分支 → 安装 npm 依赖 → 执行构建 → 产物上传到目标目录 → 备份旧版本 → 切换目录 → 重载 Nginx。下面这张表把每一步的目的和常见坑列出来方便对照。流水线阶段核心动作目的常见问题Checkout拉取指定分支代码保证构建源正确凭据失效、分支写错Installnpm/yarn/pnpm 安装依赖准备构建环境依赖源慢、lock 文件冲突Build执行 build 脚本产出 dist内存不足、环境变量错误Deploy上传产物替换线上文件权限不足、路径写错Reload重载 Nginx生效新文件缓存未清、软链未切3. Jenkins 环境准备与关键插件配置3.1 安装方式的选择与国内镜像加速Jenkins 的安装方式主要有三种直接跑 war 包、用系统包管理器yum/apt、以及 Docker 部署。如果是新环境我更推荐Docker 部署理由是版本隔离干净、升级回滚方便、环境依赖少。命令大概是这样docker run -d --name jenkins \ -p 8080:8080 -p 50000:50000 \ -v /data/jenkins_home:/var/jenkins_home \ -v /usr/bin/docker:/usr/bin/docker \ -v /var/run/docker.sock:/var/run/docker.sock \ --restartalways \ jenkins/jenkins:lts注意-v /data/jenkins_home:/var/jenkins_home这个挂载容器删了数据还在这是保命配置。另外要注意容器内 uid 的权限问题Jenkins 容器默认用户 uid 是 1000挂载出来的宿主机目录权限要给它写权限否则首次启动就报错。系统包安装的话官方源在国内访问很慢装插件时更是慢到怀疑人生。换国内镜像源是必须做的一步。进系统管理 → 插件管理 → 高级把升级站点 URL 换成国内镜像比如清华的 Jenkins 镜像地址。这一步不做你装一个 Git 插件可能要等十分钟。Docker 部署的情况下还记得在容器里加上时区参数-e TZAsia/Shanghai否则构建日志时间和你的认知对不上。3.2 必装插件清单Jenkins 裸装是没法干活的下面这几个插件基本是刚需我按重要性排一下Git plugin / Git Client plugin拉代码用必装。GitLab plugin或 Gitea/Bitbucket 对应插件如果你想让 GitLab 提交后自动触发构建这个是关键。Pipeline声明式流水线的基础LTS 版本一般自带。NodeJS Plugin让 Jenkins 帮你管理 Node 版本比在宿主机装 Node 干净得多强烈建议装。Publish Over SSH如果目标机和 Jenkins 不在一台用它传文件。DingTalk钉钉通知插件构建成功失败推送到群里团队协作体验提升明显。Workspace Cleanup构建前清空工作空间避免上一次的残留文件污染这次构建。安装插件同样建议走国内镜像装完之后记得重启 Jenkins 让插件完全生效。装完 NodeJS 插件要去系统管理 → 全局工具配置里新增一个 NodeJS 安装项给它起个名字比如node18选定版本勾选自动安装。这个名字后面在流水线里要用到别随便改。3.3 凭据管理与安全边界拉私有仓库要凭据这一步千万别用明文密码写在脚本里构建日志里一旦打印出来就是安全事故。正确做法是在系统管理 → 凭据里新建一个用户名密码类型的凭据填 Git 账号和访问令牌给它起个 ID比如gitlab-ruoyi-cred。流水线里通过这个 ID 引用密码永远不会出现在日志里。用访问令牌Personal Access Token比用登录密码更好一是可以单独撤销二是权限可以限定到只读仓库。令牌泄漏了随时换一个就行密码泄漏的代价大得多。Nginx 目标目录的写权限也要提前规划。Jenkins 执行部署的用户Docker 里默认是jenkins用户必须对发布目录有写权限否则流水线跑到最后一步报Permission denied前面白干。常规做法是把发布目录的属主改成 jenkins 用户或者把 jenkins 用户加进一个专门的分组并给目录分组写权限。这一步在第一次部署时就要配好别等到出问题再回头查。4. 若依前端项目的构建要点4.1 项目结构与构建命令确认若依的 Vue 前端ruoyi-ui目录结构比较固定package.json里的 scripts 通常是长这样的{ scripts: { dev: vue-cli-service serve, build:prod: vue-cli-service build, build:stage: vue-cli-service build --mode staging } }这里有个关键点很多人会忽略构建环境是靠--mode参数区分的它决定了加载哪个.env文件。.env.production里的VUE_APP_BASE_API就是你打包后前端请求后端的地址。流水线里如果直接跑npm run build默认走 production 模式如果你们有独立的测试环境就要用对应的--mode。我先给你一个判断口诀构建命令要和目标环境严格对应不能靠改代码来切环境。见过有人为了发测试环境手动去改.env.production里的接口地址然后提交第二次发生产忘了改回来直接酿成事故。正确姿势是在 Jenkins 里根据不同分支或不同参数选择不同的构建命令。另外若依新版本有些是基于 Vite 的ruoyi-vue3或相关衍生版本构建命令是vite build产物目录一般还是dist但构建速度比 Webpack 版本快很多。不管哪个版本你要做的是打开package.json确认脚本名和产物目录这两个信息写错后面全错。4.2 Node 版本与依赖安装的坑Node 版本是个高频雷区。若依的老版本Vue2 vue-cli在 Node 16 上跑得好好的换到 Node 18 可能要开NODE_OPTIONS--openssl-legacy-provider才能构建否则报ERR_OSSL_EVP_UNSUPPORTED。而若依的 Vue3 版本又要求 Node 16。所以第一步是确认项目要求的 Node 版本一般看package.json里的engines字段或者看 README。在 Jenkins 里指定 Node 版本有两种方式。一是用 NodeJS 插件在流水线的tools段里声明tools { nodejs node18 }二是不用插件在宿主机装好 Node直接用系统环境里的。第一种更可控建议用第一种。依赖安装这一步的坑集中在包管理器选择和依赖源上。若依老版本一般用 npm新版本有些仓库会带pnpm-lock.yaml。必须跟着 lock 文件走有package-lock.json就用npm ci有pnpm-lock.yaml就用pnpm install --frozen-lockfile有yarn.lock就用yarn install --frozen-lockfile。所谓 frozen 就是严格按 lock 文件装不自动升级版本保证每次构建产物一致。npm install和npm ci的区别一定要搞清楚前者会尝试更新 lock 文件里的依赖版本后者严格照 lock 来。CI 环境必须用npm ci或者带 frozen 参数的其他包管理器否则每次构建可能装到不一样的依赖出现我本地能跑线上不行的玄学问题。还有个国内开发者的老问题——npm 官方源慢。在流水线里可以临时指定镜像源npm config set registry https://registry.npmmirror.com npm ci --no-audit --no-fund加--no-audit --no-fund是为了跳过安全审计和捐赠提示构建能快好几秒日志也干净。4.3 构建内存与超时控制前端构建尤其是 Webpack 打包是吃内存的。若依的项目规模不算大一般 2GB 内存够用但如果服务器上同时跑着 Jenkins 容器和其他服务构建时 OOM内存溢出导致 Node 进程被 kill 是常见故障报错信息通常是JavaScript heap out of memory或者进程莫名其妙退出日志断在打包中途。解决办法是给 Node 加大堆内存上限在构建命令里加环境变量export NODE_OPTIONS--max-old-space-size4096 npm run build:prod4096 是 4GB按你服务器的实际内存调整别设得超过物理内存否则拖垮整机。另外构建超时也要设流水线里默认没有任何超时保护一个卡死的构建能把执行器一直占着。声明式流水线可以这样加options { timeout(time: 30, unit: MINUTES) buildDiscarder(logRotator(numToKeepStr: 30)) }timeout是构建超时上限buildDiscarder是只保留最近 30 次构建记录防止磁盘被日志和产物撑爆。这两条几乎是我每个前端流水线的标配。5. 完整流水线脚本拆解5.1 拉取代码与分支策略先明确一个原则构建哪个分支要能一眼看出。参数化构建是最直观的做法在任务的General里勾选参数化构建过程加一个字符串参数BRANCH默认值写master或者你们的主干分支名。这样每次构建时界面上有一个输入框可以填分支历史记录里也能看到这次构建用的是哪个分支。拉代码的流水线片段是stage(Checkout) { steps { checkout([ $class: GitSCM, branches: [[name: ${params.BRANCH}]], userRemoteConfigs: [[ url: gityour-gitlab.com:group/ruoyi-ui.git, credentialsId: gitlab-ruoyi-cred ]] ]) } }如果你在拉取时遇到 Permission denied (publickey)说明用的是 SSH 地址但没配密钥。两条路一是把凭据换成 SSH 私钥类型把 Jenkins 的公钥加到 GitLab 的部署密钥里二是直接用 HTTPS 地址配用户名密码凭据。HTTPS 更简单SSH 更省去密码管理团队内网推荐 SSH。用 SSH 方式时Jenkins 首次连接会提示 host key 验证非交互环境下会直接失败。稳妥做法是提前在 Jenkins 的 known_hosts 里加上目标 Git 服务器的公钥指纹或者用插件提供的跳过验证配置。别在第一次构建时才去处理那样你会对着一个莫名其妙的报错发呆很久。5.2 依赖安装与构建阶段的写法把 Node 环境、依赖源、内存参数、构建命令都整合进去构建阶段大概是这样的stage(Build) { tools { nodejs node18 } steps { sh export NODE_OPTIONS--max-old-space-size4096 npm config set registry https://registry.npmmirror.com npm ci --no-audit --no-fund npm run build:prod ls -lh dist/ } }最后那句ls -lh dist/是刻意加的用来在日志里确认产物到底生成了没有、大小是否正常。前端构建成功但 dist 是空的这种情况真的存在——比如.env文件里配了错误的输出路径或者构建命令其实是 lint 而不是 build。加上这一句日志里能直观看到产物。如果需要多环境把构建命令改成参数化sh npm run ${params.BUILD_CMD}然后在参数里给BUILD_CMD一个可选列表build:prod、build:stage下拉选择。这样测试和生产各用各的构建脚本互不干扰。5.3 产物上传与目录切换这是前端部署最有讲究的一步。最粗糙的做法是直接rm -rf /var/www/ruoyi cp -r dist /var/www/ruoyi问题在于删除和复制的间隙里目录是不存在的用户正好访问就会 404。更好的做法是先传新目录再原子切换TS$(date %Y%m%d%H%M%S) TARGET/var/www NEW_DIR$TARGET/ruoyi-$TS # 上传新版本到独立目录 mkdir -p $NEW_DIR cp -r dist/* $NEW_DIR/ # 原子切换软链接 ln -sfn $NEW_DIR $TARGET/ruoyi这里用软链接切换是关键ln -sfn是原子操作切换的瞬间新的软链就指向新目录任何时刻 Nginx 都能读到完整版本不存在半新半旧的窗口。Nginx 的root配置指向/var/www/ruoyi实际是根据软链找到最新的目录。如果 Jenkins 和目标机不在同一台就用rsync传输它自带增量同步和断点续传能力比 scp 稳rsync -avz --delete dist/ deploytarget:/var/www/ruoyi-$TS/注意dist/后面的斜杠不能省略有无斜杠行为完全不同有斜杠是同步目录内容没斜杠是把目录本身放进去。这个细节坑过无数人。保留历史版本目录是故意的出问题时可以一键切回来。但别无限保留磁盘会满。加个清理逻辑只留最近 5 个版本ls -dt /var/www/ruoyi-* | tail -n 6 | xargs rm -rf。5.4 重载 Nginx 与缓存处理静态文件替换完成后一般不需要重启 Nginx但要让浏览器拿到新的index.html。如果你的index.html被 Nginx 也设了长缓存用户不会主动去拿新版本。正确的缓存策略是这样的文件类型缓存策略原因index.html不缓存或极短缓存入口文件必须让用户拿到最新带 hash 的 JS/CSS长缓存一年文件名随内容变无需担心旧版图片、字体长缓存内容变化频率低manifest.json等短缓存可能随版本变对应的 Nginx 配置片段location / { root /var/www/ruoyi; try_files $uri $uri/ /index.html; add_header Cache-Control no-cache, no-store, must-revalidate; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ { root /var/www/ruoyi; expires 1y; add_header Cache-Control public, immutable; }try_files $uri $uri/ /index.html这一行是 Vue Router 的 history 模式必需的否则用户直接访问/system/user这类深链会 404。很多人部署完发现首页能进刷新子页面 404就是漏了这行。重载命令就一句nginx -t nginx -s reloadnginx -t先做配置检查通过了再重载避免配置写错直接把服务搞挂。这个顺序别颠倒。6. 常见问题排查与避坑实录6.1 构建失败类问题速查构建阶段的报错花样最多我把高频的整理成一张表遇到直接对号入座。报错信息根本原因解决方式ERR_OSSL_EVP_UNSUPPORTEDNode 17 与老版 Webpack 的加密兼容问题加NODE_OPTIONS--openssl-legacy-provider或降 Node 版本JavaScript heap out of memory构建内存不足加大--max-old-space-sizeCannot find module xxx依赖没装全或 lock 文件冲突用npm ci重装检查包管理器是否统一npm ERR! network/ timeout依赖源访问不畅切国内镜像源command not found: npmNode 环境没配到流水线里检查 tools 段或全局工具配置Failed to fetchNode 自动安装下载失败配置国内 Node 下载镜像或改用宿主机 Node关于 Node 自动安装失败补充一个细节NodeJS 插件的自动安装是从外部下载的国内网络经常失败。可以在系统管理 → 全局工具配置的 NodeJS 安装项里把自动安装的下载地址改成国内镜像。如果嫌麻烦直接在宿主机装好 Node流水线不声明 tools用系统的也行只是版本管理没那么灵活。6.2 部署环节的权限与路径问题部署阶段最常见的两个报错Permission denied和No such file or directory。Permission denied几乎都是目录属主问题。查清楚 Jenkins 用哪个用户执行容器里是jenkins然后确认目标目录对这个用户的写权限。快速排查命令ls -ld /var/www id jenkins如果 jenkins 用户不在目录的属主组里加组或者chown。No such file or directory一般是路径写错或者目录还没创建。用 rsync 时如果目标目录不存在rsync 会尝试创建但多级父目录不存在还是会失败所以保险起见先mkdir -p。还有一种情况比较隐蔽打包成功但页面白屏。这种通常不是部署问题而是资源路径问题。若依的vue.config.js里如果publicPath设成了相对路径./再配合前端路由的 history 模式某些部署结构下会找不到资源。检查构建日志里的资源引用路径和 Nginx 的实际访问路径对不对得上。默认若依的publicPath是/部署在根路径下没问题如果部署在子路径比如/ruoyi/必须同步改publicPath和路由的base两处要一致。6.3 触发方式与通知机制自动化的最后一块拼图是怎么触发。最省心的当然是代码提交后自动构建配 GitLab 的 Webhook 即可GitLab 项目里配好 Jenkins 的回调地址Jenkins 任务里勾选GitLab 项目构建触发器生成 Token 填回 GitLab。这样每次 push 就自动跑。但生产环境的部署我建议不要全自动用参数化构建手动触发避免有人误提交就发到线上。测试环境可以全自动生产环境手动确认这是很实用的折中。构建完成后的通知也值得配。装个钉钉插件构建成功失败都推到群里附上构建日志链接。团队里谁能不知道发布了什么出了问题也能第一时间看到。配置在任务的后置步骤里加一段具体格式插件文档有示例主要是填 Webhook 地址和消息模板。通知里建议带上分支名、构建人、构建持续时间、以及是成功还是失败。光说构建失败了别人还得点进去查是哪个分支效率太低。信息给全接收的人一眼就知道要不要关注。6.4 版本升级与回滚策略因为前面用了软链接 历史目录的保留策略回滚就变得非常简单把软链指回上一个版本目录就行。ln -sfn /var/www/ruoyi-20240101120000 /var/www/ruoyi即时生效不用重新构建。这是保留历史版本目录这个设计的最大回报。我建议在部署脚本里顺手生成一个回滚脚本放到目标机上出问题时运维同志直接执行不用等开发来操作。关于 Jenkins 本身的升级Docker 部署的情况下就是换镜像重启数据都在挂载卷里风险可控。但升级前一定记得备份JENKINS_HOME插件兼容性问题偶有发生备份是最后的保险。7. 我个人在实际操作中的几点体会把这套流水线在几个若依项目上跑了之后有些经验是不写成文档、光看教程学不到的。第一条先手动跑通一遍再上自动化。很多人一上来就写流水线脚本结果报错一堆分不清是脚本问题还是环境问题。正确顺序是手动 SSH 到 Jenkins 所在的机器手动执行一遍git clone、npm ci、npm run build、上传、切软链确保每个命令都能跑通再把这些命令翻译成流水线。流水线的本质就是自动化执行你已经验证过的命令命令本身没验证过自动化只会放大问题。第二条把环境变量集中管理。构建命令、目标路径、服务地址这些别散落在脚本各处用 Jenkins 的全局环境变量或者参数统一管。每次要发布新环境时改一处就行改多处必然漏。我用的是参数化 环境变量结合主路径固定环境相关的走参数。第三条日志要能看懂。流水线里适当加echo输出当前阶段和关键变量值比如打印当前分支、目标目录、构建命令出问题时看日志一眼就知道卡在哪。构建成功但线上没更新这种情况八成是分支错了或者目标路径写错了日志里有这些信息就能立刻定位。第四条别追求一步到位。第一版流水线能把代码拉下来、打包上传、切软链就已经解决了 80% 的问题。通知、回滚自动化、多环境并发这些可以后续慢慢加。我就见过有团队为了做一套完美的流水线拖了两个月最后还是手动部署。先跑起来再优化。