尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

若依前端Jenkins自动化部署:从手工发版到流水线

发布时间:2026/9/29 11:19:18

资讯中心
01
ARTICLE

若依前端Jenkins自动化部署:从手工发版到流水线

若依前端Jenkins自动化部署:从手工发版到流水线
1. 先把场景说透手工打包在若依项目上到底卡在哪若依这套框架在国内中小团队里的渗透率高得离谱后台管理、内部系统、行业 SaaS 的骨架十有八九是从若依改出来的。改动量一旦上去前端的发版就变成了一件很烦的事本地npm run build:prod跑一遍等两三分钟把dist压缩包通过某种方式丢到服务器解压覆盖然后刷新页面看有没有白屏。这套动作做一次两次还行做到第十次的时候人就开始犯错了。我自己经历过最典型的一次翻车是周五下午紧急修一个表单校验的问题。本地打包、上传、覆盖一气呵成结果第二天业务反馈菜单全没了。排查了半天才发现覆盖的时候把服务器上前一天手工加的静态资源目录一起删掉了。这种错误跟技术能力没关系纯粹是流程缺陷。1.1 一次典型的人肉发版到底包含哪些动作把手工流程拆开看其实远比想象的复杂确认当前本地分支是不是最新git pull一下有时候还要切分支确认node_modules是不是干净的偶尔要删掉重装跑构建命令盯着控制台有没有 warning 变成 error找到dist目录打成 zip用 FTP 工具或者 scp 上传到服务器某个临时目录SSH 上去备份旧目录解压新包改权限刷新页面清浏览器缓存验证七个步骤每一步都有出错空间。第 1 步容易忘第 2 步最耗时第 5 步受网络影响第 6 步最容易出事故。而 Jenkins 能吃掉的是 1 到 6 全部人只需要负责第 7 步的验收。这就是它存在的全部意义。1.2 该不该上 Jenkins先看三个判断标准不是所有项目都值得配一套 Jenkins。我的判断标准很简单判断维度倾向手工倾向 Jenkins发版频率一个月不到一次每周都有甚至每天环境数量只有生产一套开发/测试/预发/生产多套参与人数只有你一个人改前端两三个人以上并行开发回滚要求出问题慢慢修必须能五分钟内切回上一版如果中了后面两列任意两条那配置成本大概半天到一天很快就能赚回来。尤其是多环境这一条手工切环境靠改.env.production里的接口地址再重新打包改错一次就是生产环境打到测试接口这种事故的代价远高于配 Jenkins 的时间。还有一点常被忽略Jenkins 顺带解决了构建产物不可追溯的问题。构建号、Git commit hash、构建时间、是谁触发的全都留痕。线上出问题的时候能精确知道跑的是哪个版本的代码这一点在排查疑难杂症时价值极高。2. 流水线动工前的地基Jenkins 与 Node 环境怎么搭很多人配 Jenkins 失败不是败在 Jenkinsfile 写得不对而是败在环境没搭明白。Node 版本不对、插件缺一个、凭据权限不够这些问题会以各种奇怪的报错形式出现让人误以为是脚本的问题。2.1 Jenkins 的安装方式与首次初始化三种主流装法我按推荐度排Docker 方式最干净jenkins/jenkins:lts-jdk17拉下来跑就行。但要注意挂载卷否则重启一次配置全丢另外容器里要跑 Node得把 Node 也装进容器或者挂载宿主机的 Node 目录。WAR 包 系统服务java -jar jenkins.war直接跑适合临时验证。要长期用就写个 systemd service配置Restartalways。发行版包管理器安装Debian/Ubuntu 系加官方源后apt install jenkins会自动创建jenkins用户和 service最省心。我个人更偏好第二种变体用 systemd 托管 WAR 包。原因是升级只换一个文件而且 Jenkins 的JENKINS_HOME路径清晰可控备份直接打包目录就行。首次访问会要求输入初始密码位置在JENKINS_HOME/secrets/initialAdminPassword。安装向导里选安装推荐的插件就够用后面缺什么再补。这一步如果官方更新站点下载慢可以在Manage Jenkins→Plugins→Advanced里把更新站点换成国内镜像地址速度会有明显改善不需要折腾别的。注意Jenkins 默认监听 8080跟很多 Java 应用的端口冲突。装之前先netstat -tlnp | grep 8080看一眼冲突就改JENKINS_PORT或者启动参数--httpPort9090。这个小细节能省掉半小时的为什么访问不了。2.2 插件清单只装真正用得上的Jenkins 的插件生态庞大但装多了会拖慢启动、制造版本冲突。跑若依前端自动部署这套组合基本够插件名用途是否必需Git plugin拉取代码必需NodeJS Plugin管理多版本 Node推荐Pipeline编写 Jenkinsfile必需Credentials Binding流水线里安全注入凭据必需SSH Agent / Publish Over SSH远程执行部署脚本二选一Build Timestamp生成构建时间变量用于产物命名推荐DingTalk或同类通知插件构建结果推送可选Publish Over SSH和SSH Agent这两个经常让人纠结。前者配置在全局用起来方便但把服务器信息散落在 Jenkins 配置里后者在流水线里显式声明凭据 ID更透明。我现在统一用SSH Agent配合rsync因为rsync的增量同步比scp全量传输快得多尤其是前端 dist 里百分之七八十的文件其实没变。2.3 Node 环境NodeJS 插件还是 nvm这个问题在若依项目上格外重要因为若依 Vue2 版和 Vue3 版对 Node 版本的要求完全不同。若依 Vue2 版ruoyi-ui底层是 Vue CLI 4 webpack 4这套工具链在 Node 17 及以上会直接报ERR_OSSL_EVP_UNSUPPORTED因为 Node 17 之后 OpenSSL 升级到了 3.x而 webpack 4 用的哈希算法在 3.x 里被标记为 legacy。若依 Vue3 版ruoyi-ui基于 Vite反过来Node 16 以下的版本跑不动需要 Node 18 甚至 20。两种做法NodeJS Plugin在Manage Jenkins→Tools里配置多个 Node 版本命名如node16、node20。流水线里用tools { nodejs node16 }或者nodejs(node16) { ... }调用。好处是版本隔离干净切换一行配置就行。服务器装 nvmJenkins 用户需要有权限加载 nvm得在~/.bashrc里配好并且 Jenkins 执行 shell 时要用bash -lc才能加载到环境。稍微绕一点但版本管理更灵活。我选第一种。配置路径是Manage Jenkins→Tools→NodeJS installations勾选自动安装填上版本号。Jenkins 首次使用时会自动下载解压到JENKINS_HOME/tools下之后复用。提醒一个坑如果 Jenkins 跑在 Docker 容器里NodeJS Plugin 自动下载的 Node 是在容器内的容器重建就没了。这种情况要么把JENKINS_HOME完整挂载出来要么干脆用宿主机 Node 并通过挂载卷暴露进去。2.4 凭据Git 与目标服务器怎么授权两条凭据链缺一不可。拉代码的凭据如果 Git 服务支持用 SSH 密钥比账号密码稳。Jenkins 里选SSH Username with private key把私钥粘进去生成一个 ID 比如git-ssh-key。然后在任务里填仓库地址时用 SSH 格式gityour-git-host:group/ruoyi-ui.git。用 HTTPS 格式的话记得把用户名也带上否则部分 Git 服务会要求交互式输入流水线会卡死。部署到服务器的凭据同样用SSH Username with private keyID 叫deploy-server-key。私钥对应的公钥要提前用ssh-copy-id或者手工追加到目标服务器的~/.ssh/authorized_keys并且验证一下 Jenkins 用户能不能免密登录sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_rsa deploy192.168.1.20 echo ok这一步必须手工验证通过否则流水线跑到部署阶段才报权限错误排查起来很烦。3. 若依前端工程的脾气决定了脚本必须这么写通用教程里讲的 Jenkins 前端流水线拿到若依项目上直接用大概率会翻车。因为若依的前端工程有几个自己的特征不了解就容易踩坑。3.1 Vue2 版与 Vue3 版若依的差异对照先明确你手上是哪个版本。目录名都叫ruoyi-ui但内部差别很大对比项RuoYi-VueVue2RuoYi-Vue3Vue3构建工具Vue CLI 4 / webpack 4Vite 4构建命令npm run build:prodnpm run build:prod产物目录distdist静态资源目录dist/staticdist/assets环境变量前缀VUE_APP_VITE_APP_配置文件vue.config.jsvite.config.js打包耗时中等项目2-5 分钟30 秒-2 分钟Node 版本建议14 / 1618 / 20耗时差异很明显。Vue2 版走 webpack 全量打包项目一大就容易跑到内存告急Vue3 版走 Vite依赖预构建加上 esbuild 转译速度快一个数量级。这也意味着两者的内存参数配置策略不同Vue2 版大概率需要调大 Node 堆内存Vue3 版通常不需要。确认版本的方法很简单看package.json的dependencies里是vue: ^2.x还是vue: ^3.x或者看有没有vite.config.js。这一步别偷懒选错了脚本等于白写。3.2 .env 文件与 publicPath 的联动关系若依的接口地址是通过环境变量注入的。Vue2 版在.env.production里VUE_APP_TITLE 业务管理系统 ENV production VUE_APP_BASE_API /prod-apiVue3 版在.env.production里VITE_APP_TITLE 业务管理系统 VITE_APP_BASE_API /prod-api这个VUE_APP_BASE_API/VITE_APP_BASE_API是前端请求的基路径最终由 Nginx 反向代理转发到后端。它和 Nginx 的location配置必须严格对应否则会出现页面能打开但所有接口 404 的情况。另一个关键点是publicPathVue3 里叫base。如果部署在域名根目录保持/就行如果部署在子路径比如https://example.com/admin/必须改成/admin/否则 JS 和 CSS 会去根目录找页面直接白屏。我见过最隐蔽的一个案例测试环境在根目录生产环境在子路径手工发版时有人直接在服务器上改了构建产物里的路径勉强能跑。换成 Jenkins 之后就炸了因为每次构建都会把这个手工修改覆盖掉。这个问题的正确解法是在.env.production里加一个变量来控制VITE_APP_PUBLIC_PATH /然后在vite.config.js里读这个变量import { defineConfig, loadEnv } from vite export default defineConfig(({ mode }) { const env loadEnv(mode, process.cwd()) return { base: env.VITE_APP_PUBLIC_PATH || /, build: { outDir: dist, sourcemap: false } } })这样不同环境只要改环境文件代码零改动。Vue2 版同理在vue.config.js里用publicPath: process.env.VUE_APP_PUBLIC_PATH || /。这类改服务器不改代码的操作是自动部署落地的最大阻力。手工发版的便利性正来自这种随意性一旦上了流水线所有环境差异都必须代码化、配置化。前期梳理清楚后期才稳。3.3 依赖安装阶段最容易埋雷的三个点构建阶段报错八成问题出在依赖安装上。第一npm install和npm ci的选择。若依仓库里带了package-lock.json那就应该用npm ci。它严格按照 lock 文件安装不会自动升级小版本构建结果可复现。npm install在某些情况下会更新 lock 文件导致本地能跑、流水线报错。对应命令npm ci --registryhttps://registry.npmmirror.com把镜像源写进命令而不是依赖全局配置能避免换了台机器忘了配的问题。第二node-sass 与 sass 的恩怨。老版本若依 Vue2 用的是node-sass这个包需要编译原生模块跟 Node 版本强绑定。Node 14 能装的版本Node 16 上可能直接编译失败。解决办法是换成纯 JS 实现的sassDart Sass改package.json并同步调整vue.config.js里的sass-loader配置。新版若依已经切换过来了老项目升 Jenkins 时建议顺手把这一步做掉。第三node_modules缓存。每次构建都重装依赖一次两分钟一天十次就是二十分钟。Jenkins 可以用stash/unstash但更实用的是把node_modules放在工作区之外做缓存stage(Install) { steps { script { def cacheDir ${env.HOME}/.npm-cache/ruoyi-ui sh mkdir -p ${cacheDir} if [ -d ${cacheDir}/node_modules ]; then cp -r ${cacheDir}/node_modules ./ fi npm ci --registryhttps://registry.npmmirror.com || npm install --registryhttps://registry.npmmirror.com rm -rf ${cacheDir}/node_modules cp -r node_modules ${cacheDir}/ } } }这段逻辑略显粗暴但成本低、效果立竿见影。注意npm ci失败时 fallback 到npm install因为锁文件在依赖冲突时可能过时。4. 流水线主体从拉代码到拿到 dist环境理顺了接下来就是流水线本身。4.1 自由风格任务和 Pipeline 的取舍Jenkins 提供两种任务类型很多人第一反应是选自由风格因为界面上点点勾勾就行。我的建议是只要项目会长期存在就选 Pipeline。理由有三条。第一Jenkinsfile 进仓库跟代码一起版本管理改动能 review。第二复制一套新环境只需要复制一个文件改几个参数而自由风格的配置得在界面上重新点一遍。第三调试和排错时Pipeline 的日志按 stage 分段一眼能看出卡在哪个环节。代价是学习曲线。但 Groovy 的声明式 Pipeline 语法其实就那几个关键字pipeline、agent、stages、stage、steps、environment、post。掌握了就能覆盖九成场景。4.2 Jenkinsfile 逐段拆解下面这份是我在若依 Vue3 项目上实际跑通的版本稍微调整过变量名可以直接参考pipeline { agent any tools { nodejs node20 } parameters { choice(name: DEPLOY_ENV, choices: [test, prod], description: 部署环境) string(name: TARGET_HOST, defaultValue: 192.168.1.20, description: 目标服务器) booleanParam(name: SKIP_DEPLOY, defaultValue: false, description: 只构建不部署) } environment { APP_NAME ruoyi-ui REMOTE_DIR /data/web/ruoyi-ui GIT_CRED git-ssh-key DEPLOY_CRED deploy-server-key BUILD_TIME sh(script: date %Y%m%d%H%M, returnStdout: true).trim() } options { timestamps() buildDiscarder(logRotator(numToKeepStr: 30)) timeout(time: 30, unit: MINUTES) disableConcurrentBuilds() } stages { stage(拉取代码) { steps { checkout([ $class: GitSCM, branches: [[name: */${params.DEPLOY_ENV prod ? master : dev}]], userRemoteConfigs: [[ url: gityour-git-host:group/ruoyi-ui.git, credentialsId: env.GIT_CRED ]] ]) } } stage(安装依赖) { steps { sh npm ci --registryhttps://registry.npmmirror.com } } stage(构建) { steps { sh export NODE_OPTIONS--max-old-space-size4096 npm run build:${params.DEPLOY_ENV} } } stage(归档产物) { steps { sh cd dist tar -czf ../${APP_NAME}-${BUILD_TIME}-${BUILD_NUMBER}.tar.gz . cd .. ls -lh *.tar.gz archiveArtifacts artifacts: *.tar.gz, fingerprint: true } } stage(发布) { when { expression { return !params.SKIP_DEPLOY } } steps { sshagent(credentials: [env.DEPLOY_CRED]) { sh scp -o StrictHostKeyCheckingno \ ${APP_NAME}-${BUILD_TIME}-${BUILD_NUMBER}.tar.gz \ deploy${params.TARGET_HOST}:/tmp/ ssh -o StrictHostKeyCheckingno deploy${params.TARGET_HOST} set -e ROOT${REMOTE_DIR} RELEASE\$ROOT/releases/${BUILD_TIME}-${BUILD_NUMBER} mkdir -p \$RELEASE tar -xzf /tmp/${APP_NAME}-${BUILD_TIME}-${BUILD_NUMBER}.tar.gz -C \$RELEASE ln -sfn \$RELEASE \$ROOT/current ls -1dt \$ROOT/releases/* | tail -n 6 | xargs -r rm -rf rm -f /tmp/${APP_NAME}-${BUILD_TIME}-${BUILD_NUMBER}.tar.gz } } } } post { success { echo 构建成功产物版本 ${BUILD_TIME}-${BUILD_NUMBER} } failure { echo 构建失败请查看控制台日志 } } }几个设计细节值得展开说。为什么先打包成 tar.gz 再传输直接 rsyncdist目录文件数量多几万个小文件的元数据操作会拖慢速度。压缩后单文件传输网络利用率高服务器端解压也快。代价是压缩解压的 CPU 开销但前端 dist 一般是几 MB 到几十 MB可以忽略。为什么用disableConcurrentBuilds()若依前端部署涉及软链接切换这一步两个构建同时跑会导致切换顺序错乱。宁可排队也不要并发。为什么有SKIP_DEPLOY参数有时候只是想验证代码能不能构建通过或者需要产物但暂时不发布。有这个开关能省很多事。4.3 产物命名、归档与版本可追溯产物文件名里带上BUILD_TIME和BUILD_NUMBER是最低成本的可追溯手段。BUILD_NUMBER是 Jenkins 自增的BUILD_TIME精确到分钟两者组合基本不会重复。配合archiveArtifacts每次构建的产物都留在 Jenkins 上默认策略保留最近 30 次由buildDiscarder控制。线上出问题时可以在 Jenkins 上找到出问题之前的那个构建号下载对应的 tar.gz手工解压到服务器或者直接改软链接指过去比起从 Git 上 checkout 旧版本重新构建这条路快得多。还有个小技巧在构建产物里写一个版本信息文件内容是 commit hash 和构建时间。git rev-parse --short HEAD dist/version.txt echo build: ${BUILD_TIME}-${BUILD_NUMBER} dist/version.txt页面上加个隐藏入口比如按CtrlShiftV弹窗显示这个文件内容线上排查问题时能立刻确认跑的是哪版代码。这个做法我在好几个项目里都用过效率提升很明显。5. 把 dist 送到服务器并安全切换部署这一步的设计直接决定了出事故时能不能快速止损。5.1 rsync 直推目录这套方案的落地细节除了上面演示的 scp tar 方案另一个常见做法是 rsync 直推。两者各有适用场景方案优点缺点适用场景scp tar.gz传输快产物归档天然每次全量服务器需解压空间产物几十 MB 以内rsync 增量只传变化文件首次全量慢文件多时元数据开销大静态资源多、频繁小改S3/OSS 中转与 CDN 天然集成需要额外配置回源有 CDN 的正式环境rsync 的典型命令长这样rsync -avz --delete \ -e ssh -o StrictHostKeyCheckingno \ dist/ deploy${TARGET_HOST}:/data/web/ruoyi-ui/current/这里的--delete是把双刃剑它能清理掉源码里已删除的旧文件避免残留但如果路径末尾少了斜杠语义会完全不同——dist/表示把 dist 里的内容同步到目标dist表示把 dist 目录本身同步到目标下。这个区别搞错了会把目录结构弄乱。我的习惯是永远不在生产目录上直接 rsync而是同步到一个新的releases/时间戳目录再切换软链接。--delete再怎么出问题最多影响一个新目录切换前验证一下就安全。5.2 用软链接做原子切换与一键回滚这是整个部署方案里最有价值的设计。目录结构/data/web/ruoyi-ui/ ├── releases/ │ ├── 202501151030-128/ │ ├── 202501151645-129/ │ └── 202501160915-130/ └── current - releases/202501160915-130Nginx 的root指向/data/web/ruoyi-ui/current。每次发布新版本解压到新的releases目录然后用ln -sfn把current指过去。关键点在于ln -sfn的原子性。ln命令替换符号链接时Linux 下是原子操作意味着 Nginx 在处理请求的过程中要么看到旧链接要么看到新链接不会出现悬空状态。这比先删目录再解压要安全得多后者在解压的几十秒里用户访问会直接 404。回滚变得极其简单ln -sfn /data/web/ruoyi-ui/releases/202501151645-129 /data/web/ruoyi-ui/current一条命令毫秒级生效。配合前面提到的自动清理保留最近 5 个版本服务器磁盘也不会无限增长。需要注意 Nginx 的一个行为如果root指令指向的路径包含符号链接Nginx 默认会解析。大多数情况下没问题但某些配置下需要确认disable_symlinks没有被误开。默认值通常是off不用改。5.3 Nginx 端必须配合的两处配置前端部署跑通了页面还是白屏或者刷新 404问题往往在 Nginx。第一处history 模式的 fallback。若依前端默认用的是 history 路由模式URL 里没有#。用户直接在浏览器地址栏输入/system/user然后刷新Nginx 会去磁盘找system/user这个文件找不到就 404。必须加location / { root /data/web/ruoyi-ui/current; index index.html; try_files $uri $uri/ /index.html; }try_files的意思是先找对应文件再找对应目录都没有就返回index.html交给前端路由处理。第二处接口反向代理。前面.env.production里配的VITE_APP_BASE_API /prod-api需要在 Nginx 里对应location /prod-api/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:8080/; proxy_read_timeout 300s; }注意proxy_pass末尾的斜杠。http://127.0.0.1:8080/会把/prod-api/user/list改写成/user/list如果末尾没有斜杠则会原样传递/prod-api/user/list后端大概率找不到这个路径。这个斜杠问题困扰过太多人。proxy_read_timeout也值得留意。若依后台有些导出功能会跑很久默认 60 秒超时会导致前端报网络错误。调到 300 秒是个比较稳妥的值。另外建议给静态资源加长缓存location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ { root /data/web/ruoyi-ui/current; expires 30d; add_header Cache-Control public, immutable; }Vite 和 webpack 生成的产物文件名都带内容哈希内容变了文件名就变所以可以放心用长缓存。这一条能让老用户第二次访问的加载速度提升非常明显。6. 踩坑实录那些构建成功却上线失败的场景前面四章是应该怎么做这一章是实际会怎么坏。有些坑不踩一次真的想不到。6.1 JavaScript heap out of memory 与 ERR_OSSL_EVP_UNSUPPORTED内存溢出的报错长这样FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory原因很直白Node 默认堆内存上限在 64 位系统上大约 2GBNode 12 会根据可用内存动态调整但默认不算高webpack 打包大型项目时加上 source map 生成很容易突破。解决办法是在执行构建前设置环境变量export NODE_OPTIONS--max-old-space-size40964096 是 MB即 4GB。设置多少合适看服务器内存。如果服务器只有 4GB 内存设 4096 会因为物理内存不足被 OOM Killer 干掉。经验值是不超过物理内存的 60%8GB 机器设 409616GB 机器可以设 8192。还有一个容易忽略的优化方向关掉 source map。生产环境本来就不需要而且它是内存消耗大户。Vue CLI 里在vue.config.js中设置module.exports { productionSourceMap: false }Vue3 Vite 里export default defineConfig({ build: { sourcemap: false } })关掉之后构建内存占用和耗时都能降两三成。ERR_OSSL_EVP_UNSUPPORTED是另一类经典错误完整报错信息里会出现digital envelope routines::unsupported。前面提过这是 Node 17 搭配 webpack 4 的问题。两个方案把 Jenkins 的 Node 版本降到 16推荐最省事或者在构建命令前加export NODE_OPTIONS--openssl-legacy-provider第二种是临时方案长期还是应该升级工具链或者固定 Node 版本。6.2 构建绿灯但页面白屏从 index.html 里的路径查起Jenkins 显示全部 stage 成功产物也传上去了打开页面一片空白。这种时候别急着怀疑 Jenkins按顺序查第一步看浏览器控制台的网络请求。如果是 JS 文件 404说明资源路径不对。打开dist/index.html看script src...的路径前缀是不是/assets/xxx.js或者带子路径。对照 Nginx 的root配置判断能不能对上。第二步看 index.html 本身能不能访问。如果连 HTML 都是 404那是 Nginx 的路径或者权限问题跟构建无关。第三步看报错内容。如果 JS 加载成功但报Unexpected token 说明服务器返回的是 HTML 而不是 JS——这通常是try_files配置错误把所有请求都 fallback 到了index.html连静态资源的请求也一起 fallback 了。解决方式是在try_files之前单独为静态资源目录加一个location块。第四步检查权限。用ls -l看releases目录下文件的属主和权限。Jenkins 通过 SSH 登录的deploy用户创建的文件属主是deployNginx 的 worker 进程通常以nginx或www-data运行。如果releases目录权限是700Nginx 根本读不到。正确的做法是目录755文件644或者把 Nginx 用户加到deploy组里。我在一次迁移到新服务器时就踩过这个坑排查了两个小时最后发现是新服务器上deploy用户的默认umask是077创建出来的目录是700。在部署脚本里加一句umask 022就解决了。6.3 我明明改了线上没变的两层缓存这个问题出现的频率高到值得单独讲。第一层是 Nginx 的缓存头。如果之前给index.html也加了长缓存expires 30d那用户浏览器会缓存旧版本 HTML 长达一个月即使服务器上文件换了也看不到。正确做法是给 HTML 设置不缓存location /index.html { root /data/web/ruoyi-ui/current; add_header Cache-Control no-cache, no-store, must-revalidate; add_header Pragma no-cache; expires 0; }带内容哈希的 JS/CSS 长缓存不带哈希的 HTML 不缓存这是标准组合。第二层是服务端的 CDN 或者前置代理。如果架构里有 CDN发布后需要刷新缓存。这一步最好也纳入流水线在部署 stage 之后加一个调用 CDN 刷新接口的步骤。不同服务商的接口不同这里不展开思路是把它自动化别依赖人工记得点。第三层容易被忽略是浏览器里已经加载的旧 JS。用户开着页面没关新版本发布后继续点击菜单加载到的是新的 chunk 文件可能报Loading chunk xxx failed。这是动态导入的固有问题。若依前端可以在router/index.js里加路由错误处理捕获 chunk 加载失败后自动刷新页面。这个小改动能显著降低用户侧的报错投诉。6.4 权限、时区与文件名大小写时区问题Jenkins 容器默认 UTC 时区产物文件名里的时间会比北京时间少 8 小时。看起来不影响功能但排查问题时对时间会很别扭。解决方式是在容器启动时挂载时区文件或者设置JAVA_OPTS-Duser.timezoneAsia/Shanghai。文件名大小写Linux 文件系统大小写敏感Windows 和 macOS默认不敏感。开发同学在 Mac 上写了import Header from ./components/header本地跑得好好的Jenkins 在 Linux 上构建就报模块找不到。这类问题只能在构建阶段暴露最好的预防方式是开发机上配置 ESLint 规则import/no-unresolved加caseSensitive: true。set -e的重要性远程执行的 shell 脚本如果没加set -e前面某条命令失败了脚本会继续往下走最后返回 0Jenkins 以为部署成功。我就遇到过解压失败但软链接照样切换了的情况结果线上直接指向一个空目录。所有部署脚本开头加set -e这是硬性要求。7. 多环境参数化、通知与后续可扩展的方向流水线跑通只是起点接下来是把它变得好用。7.1 用参数化构建覆盖 dev / test / prod前面 Jenkinsfile 里的DEPLOY_ENV参数配合choice类型在 Jenkins 页面上会渲染成下拉框。选择不同值直接映射到不同的分支和构建命令stage(构建) { steps { script { def buildCmd params.DEPLOY_ENV prod ? build:prod : build:${params.DEPLOY_ENV} sh export NODE_OPTIONS--max-old-space-size4096 npm run ${buildCmd} } } }这样做的前提是package.json里有对应的 scripts 和环境文件。若依自带build:prod其他环境需要自己加{ scripts: { build:dev: vite build --mode development, build:test: vite build --mode test, build:prod: vite build --mode production } }配套的.env.test文件放测试环境的接口地址。这套结构一旦搭起来加一个新环境只需要三件事加一个.env.xxx文件、加一条 script、在 Jenkins 参数里加一个选项。对于生产环境我强烈建议加上发布前确认。Jenkins 的input指令能做这件事stage(生产发布确认) { when { expression { params.DEPLOY_ENV prod } } steps { input message: 确认发布到生产环境, ok: 确认发布, submitter: ops-team } }注意input会让构建挂起等待人工点击要配合timeout使用否则流水线可能永远挂着。另外submitter限制只有特定用户组能点避免误操作。7.2 构建结果推送到群机器人在post段里加通知是投入产出比最高的一步post { success { script { def msg 【${APP_NAME}】构建成功\n环境: ${params.DEPLOY_ENV}\n版本: ${BUILD_TIME}-${BUILD_NUMBER}\n触发人: ${currentBuild.getBuildCauses()[0]?.userName ?: 自动触发} dingTalkSend(msg, #00FF00) } } failure { script { def msg 【${APP_NAME}】构建失败\n环境: ${params.DEPLOY_ENV}\n构建号: ${BUILD_NUMBER}\n请及时处理 dingTalkSend(msg, #FF0000) } } }如果不想引入额外的插件用curl直接调 Webhook 也可以curl -s -H Content-Type: application/json \ -d {\msgtype\:\text\,\text\:{\content\:\$MSG\}} \ $WEBHOOK_URL小声提醒一句Webhook 地址是敏感信息别硬编码在 Jenkinsfile 里放到 Jenkins 凭据里用withCredentials注入。仓库权限稍微松一点地址就泄露出去了虽然危害有限但被刷消息也很烦。7.3 再往前一步镜像化与流水线复用前端流水线跑顺之后通常会遇到两个新需求。一是把 dist 打进容器镜像。用 Nginx 基础镜像加一层 COPY产出物交给 K8s 或者容器平台部署。这样版本管理从目录软链接升级到镜像 tag回滚就是换 tag更适合微服务架构下的若依 Cloud 场景。对应的 Dockerfile 很短FROM nginx:1.25-alpine COPY dist/ /usr/share/nginx/html/ COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80但要注意这种方式下前端配置必须在构建时就固定不能靠部署时挂载配置文件。所以.env.production里的接口地址要么用相对路径推荐要么在构建阶段通过 build args 注入。二是流水线复用。项目多了之后同一个 Jenkinsfile 复制七八份改一个公共逻辑要改七八个地方。这时候可以把公共部分抽成共享库Shared Library项目里的 Jenkinsfile 只保留差异部分Library(ruoyi-pipeline) _ ruoyiFrontendDeploy( appName: ruoyi-ui, nodeVersion: node20, buildCmd: build:prod, targetHost: 192.168.1.20 )这是 Jenkins 比较成熟的用法但前提是先把单项目的流水线打磨稳定。共享库的抽象一旦做早了后面每个项目都要绕开它的限制反而更痛苦。我个人在实际操作中的体会是前端自动部署的价值不在省下那几分钟手工操作而在于把发版这件事从靠人记得变成靠流程保证。手工发版最危险的从来不是慢而是漏掉某个步骤却没人发现。Jenkins 的流水线把这些步骤固化成代码人只负责最后的验收和决策这才是它真正解决的问题。最后再分享一个小技巧在部署 stage 的最后加一条健康检查构建脚本主动请求一下线上的index.html判断返回码是不是 200、内容里有没有预期的 title。这一步能把部署脚本执行成功但实际没生效这类问题提前拦住。脚本很简单CODE$(curl -s -o /dev/null -w %{http_code} https://your-domain.com/) if [ $CODE ! 200 ]; then echo 健康检查失败返回码 $CODE exit 1 fi加了这一条之后我经手的项目里再也没有出现过流水线绿灯但用户打不开页面的情况。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。