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

若依前端 Jenkins 自动打包部署实战:从手工发版到 CI/CD 流水线

发布时间:2026/9/29 2:17:41

资讯中心
01
ARTICLE

若依前端 Jenkins 自动打包部署实战:从手工发版到 CI/CD 流水线

若依前端 Jenkins 自动打包部署实战:从手工发版到 CI/CD 流水线
1. 为什么若依项目的前端值得单独搭一套 Jenkins 自动打包部署前端的打包部署这件事说简单也简单说折磨人也真折磨人。我在带团队做若依项目的过程中前后经历过三种状态最早是本地npm run build:prod然后打开 Xftp 手动拖 dist 目录上去后来改成写一个 shell 脚本登服务器手动执行再后来才把 Jenkins 这套东西补齐做成前端自动打包部署的完整链路。每一次升级省下的都不是十分钟二十分钟而是改一行文案要发一次版这种恶心事带来的心理消耗。若依RuoYi作为一套在国内使用面很广的 Java 快速开发框架前端这块有几条并行的分支RuoYi-Vue 走的是 Vue2 加 Element UI 加 vue-cli 的老组合RuoYi-Vue3 走的是 Vue3 加 Element Plus 加 Vite 加 TypeScript 的新组合另外还有 RuoYi-Cloud 微服务版前端工程基本沿用 ruoyi-ui 的结构。也就是说同一个团队很可能同时维护两套前端一套 Node 14/16一套 Node 18/20打包命令、环境变量前缀、构建产物结构都不一样。这时候如果没有一套统一的自动打包部署流程光在哪台机器装哪个 Node 版本上就能吵起来。这套 Jenkins 自动打包部署方案要解决的核心问题其实就三个第一前端代码合并之后不再依赖某个人本地环境由 Jenkins 在固定的构建节点上完成依赖安装和打包第二打包产物能自动分发到目标服务器并配合 Nginx 完成发布中间不出现人肉复制粘贴第三出问题能快速回滚不用重新走一遍打包。适合谁来参考我觉得是三类人手里有若依项目、目前还在手工发版的前端或运维刚开始接触 Jenkins、想找一个真实项目练手的人以及正在做多环境、多分支发布规范化的团队。这里先给一个预期整套流程的搭建时间如果 Jenkins 服务器和构建节点已经就绪第一遍跑通大概两三个小时如果从零开始装 Jenkins、配 Node、通 Git 凭据那一两天也正常。真正花时间的不是写 Jenkinsfile而是把那些看起来无关紧要的环境细节对齐。1.1 若依前端工程的打包特性决定了流程怎么设计要把自动部署做扎实先得把若依前端工程的构建模型吃透。RuoYi-Vue 这一支用的是 vue-cli 体系package.json里通常能看到build:prod和build:stage两个脚本分别对应vue-cli-service build和vue-cli-service build --mode staging。这套体系依赖.env.production、.env.staging这类环境文件里面用VUE_APP_作为变量前缀比如后端接口地址VUE_APP_BASE_API。而 RuoYi-Vue3 这一支换成了 Vite脚本变成vite build和vite build --mode staging环境变量前缀改成了VITE_配置读取方式和注入时机都不一样。这个差异直接影响了 Jenkins 任务的设计。Vue2 版本里VUE_APP_BASE_API是编译期注入的也就是说不同环境必须打不同的包你没法用一份 dist 同时适配测试和生产。Vite 版本同理import.meta.env也是编译期静态替换。所以 Jenkins 任务必须把部署环境做成一个参数根据参数选择不同的构建命令而不是打完包再改配置文件——那样改出来的产物和实际打包产物之间有偏差出了问题很难复盘。还有一个容易被忽略的点若依前端的产物是纯静态资源但它不是放上去就能用。前端页面里的请求地址是相对的/prod-api或者某个绝对地址后者需要 Nginx 侧做反向代理到后端服务。这意味着前端部署和 Nginx 配置是一对绑定关系Jenkins 在发布阶段不仅要传文件往往还要保证目标机的 Nginx 配置是对的。我的习惯是把 Nginx 的 server 配置文件也纳入版本管理Jenkins 发布时用同一份模板渲染后下发避免某台机器的配置和别人不一样。再就是构建产物的大小。若依前端引入了 Element UI 或 Element Plus 全家桶加上若依自己封装的组件和工具库一个正常的 dist 目录两三兆到十几兆很常见。如果直接npm run build:prod而不做任何优化构建时间在普通 2 核 4G 的构建节点上可能要三到五分钟内存占用也容易碰到 Node 默认堆上限。所以后面我会专门讲构建参数和内存配置这不是可选项是必选项。1.2 手工发版的坑我按发生频率列一遍在动手搭 Jenkins 之前我把过去两年手工发版踩过的坑整理了一遍这些坑也正是自动化要解决的问题。按发生频率从高到低第一忘记清理上一次的构建产物dist目录里混进了旧 hash 的文件浏览器缓存策略一乱用户看到的是新旧混合的页面第二本地 Node 版本和服务器或者同事不一致node-sass、sass-loader这类原生模块直接编译失败报一堆看不懂的 gyp 错误第三打包时没注意.env.production里的接口地址把测试地址打进了生产包上线之后接口全 404第四拖文件拖了一半网络断了dist 目录不完整页面白屏第五发布完忘记 reload Nginx静态资源还是旧的第六出问题要回滚发现没有留存历史版本只能重新打包再发一遍。这六条里只有第二条属于环境问题其他五条都是流程问题。流程问题的解法就是把动作固化下来让机器按固定顺序执行不给人犯错的空间。Jenkins 的价值不在自动化这三个字本身而在于它把这一串动作变成了一段可以版本管理、可以审计、可以回放的代码。提示如果你的团队现在还处在谁手快谁发版的阶段不要一上来就追求全自动流水线。先把打包和部署两个动作拆开让 Jenkins 只负责打包和归档人还是手动取包部署。跑稳一两周之后再把部署也接进来。步子太大容易摔。1.3 方案选型为什么是 Jenkins而不是其他这个问题我被问过很多次。前端圈子里做 CI/CD 的选择其实不少比如 GitLab CI、GitHub Actions、Drone、或者干脆写个脚本配合 Webhook。选 Jenkins 的理由在若依这种项目场景下大概有三条。第一条是环境一致性。若依项目本身后端就是 Java很多团队的构建服务器上已经跑着 Jenkins 做后端服务的打包了前端的构建节点复用同一套 Jenkins 是最省事的。不用额外维护一套 CI 系统权限、凭据、通知这些基础设施都是现成的。第二条是内网适配。相当一部分若依项目部署在完全隔离的内网环境里GitHub Actions 这种云端方案根本用不了GitLab CI 又要求 GitLab Runner 的部署和维护Jenkins 作为老牌自托管方案在离线安装、插件本地化这些方面资料最全。第三条是可观测性。Jenkins 的构建日志、历史记录、产物归档、构建参数留痕在排查这次上线到底发了哪个 commit这类问题时非常好用尤其是配合 Git 插件显示的变更记录。当然 Jenkins 也有它的毛病界面老、插件生态虽然大但质量参差、Groovy 语法对前端同学不友好。我的建议是如果你只是一个人维护一两个前端项目脚本加 Webhook 也许更轻但只要是团队协作、多环境、要留痕Jenkins 的投入产出比仍然是划算的。2. 动手前的环境盘点与关键决策这一节讲的是搭之前要想清楚的事。我见过太多人上来就装 Jenkins装完发现 Node 版本不对、服务器连不上、Git 拉不下来然后开始到处打补丁。正确的顺序是先盘点再安装。盘点清单至少包括Jenkins 跑在哪台机器独立节点还是和构建节点合一、要支持哪几个 Node 版本、代码仓库是什么协议HTTP 还是 SSH、构建产物发到哪台服务器、用什么账号发、目标机的目录结构怎么规划。2.1 Jenkins 安装方式与插件源调整安装方式上我推荐两条路Docker 安装和 WAR 包安装。Docker 安装的好处是环境干净、升级方便docker run -d -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk17一条命令就能起来注意挂载卷一定要做否则重启后配置全丢。WAR 包的场景是内网离线下载jenkins.war之后java -jar jenkins.war --httpPort8080直接跑也可以用 systemd 托管。如果服务器上已经有一套 Jenkins 在跑后端那就别折腾了直接在里面加一个前端任务。装完第一件必须做的事是把插件更新源改掉。默认的更新中心在国内访问速度堪忧插件列表经常加载不出来。进入系统管理 - 插件管理 - 高级把升级站点改成清华大学或者华为云的 Jenkins 镜像地址保存之后点立即获取插件列表刷新速度会明显改善。这一步不做后面装插件会非常痛苦。需要装的插件清单我按重要性排列Git plugin拉代码、Pipeline流水线、Pipeline: Stage View阶段视图、Credentials Binding凭据绑定、NodeJS管理多个 Node 版本、Publish Over SSH远程发布可选、Workspace Cleanup清理工作空间、Timestamper给日志打时间戳、DingTalk钉钉通知可选。其中 NodeJS 插件是重点它允许你在 Jenkins 全局工具配置里预装多个 Node 版本构建时按需切换不用在服务器上手工管理 nvm。注意如果服务器完全离线插件只能用.hpi文件手工安装。下载好对应版本的 hpi 放进JENKINS_HOME/plugins/目录重启 Jenkins 生效注意插件之间的依赖关系装 Pipeline 之前先装它的依赖。这个过程很磨人建议离线环境一次把插件清单备齐。2.2 Node 版本管理为什么必须装两个如果你同时维护 RuoYi-Vue 和 RuoYi-Vue3Node 版本必须分开。Vue2 那套用 vue-cli 4/5 加 node-sass 的组合官方支持到 Node 16上到 Node 18 之后 node-sass 基本编译不过Vue3 那套 Vite 体系要求 Node 18 起步。硬要在同一台机器上装一个版本必然有一边挂掉。我的做法是在 Jenkins 的全局工具配置里装两个 NodeJS命名成node16和node20勾选自动安装。然后在流水线的tools块或者用withEnv切换 PATH。这么做的代价是每个版本都要下载一份到 Jenkins 工作目录磁盘占用大一点但换来的是任务之间互不干扰非常值。如果你不想用 Jenkins 的自动安装也可以在构建节点上用 nvm 管理然后在流水线里source $NVM_DIR/nvm.sh nvm use 16。这种方式更灵活但要求 Jenkins 执行 shell 的账号有 nvm 的环境变量容易踩坑我倾向于前者。2.3 Git 凭据与代码拉取方式凭据这块先决定用 HTTP 还是 SSH。HTTP 方式用账号密码或者访问令牌配置简单缺点是每次拉代码都要带凭据仓库大了速度也一般。SSH 方式需要在 Jenkins 服务器上生成密钥对把公钥配到代码平台的部署密钥里然后把私钥作为 SSH Username with private key 类型的凭据存进 Jenkins。若依项目通常放在内网 GitLab 上我一般用 SSH 方式稳定且不涉及密码轮换。凭据命名也要规范一点。我见过凭据列表里全是git-credential-1、git-credential-2这种名字过两个月自己都不知道哪个是哪个。建议按用途-仓库-环境的规则命名比如gitlab-ruoyi-ui-readonly一眼能看懂。凭据的 ID 在流水线里要用到改名字之前记得全局搜索一下。还有一个细节如果 GitLab 那边开启了 Webhook 触发构建需要在 GitLab 项目设置里填 Jenkins 的地址并且 Jenkins 侧要装 GitLab 插件、在系统配置里配好 GitLab 连接。Webhook 的好处是合并代码就自动触发比轮询 SCM 实时得多也不浪费构建资源。如果网络不通或者不可配置退而求其次用轮询H/5 * * * *表示每五分钟检查一次。2.4 发布目标机的目录规范目录规范这件事看起来小但它决定了你的回滚能不能做。我的建议是每个前端项目在目标机上有一个独立根目录下面按版本号建子目录再用一个软链接指向当前生效的版本。结构大概是这样/data/web/ruoyi-ui/ ├── releases/ │ ├── 20240518.12/ │ ├── 20240519.13/ │ └── 20240520.14/ ├── current - releases/20240520.14 └── shared/ └── nginx/ruoyi-ui.confNginx 的 root 指向/data/web/ruoyi-ui/current。发布时新建一个版本目录把 dist 内容推进去然后用ln -sfn原子地把 current 切过去。回滚就是把链接指回上一个版本一秒完成不需要重新打包。这套结构我用了三年绝大多数回滚场景都能覆盖。发布账号方面别用 root。建一个专门的 deploy 账号只给 releases 目录的写权限和 nginx reload 的 sudo 权限。Jenkins 通过 SSH 密钥登录这个账号权限最小化出问题影响面可控。3. 构建任务的核心配置拆解环境准备好了接下来是任务本身。我强烈建议用 Pipeline 而不是自由风格任务原因不是 Pipeline 更高级而是 Pipeline 的配置可以进版本库。Jenkinsfile 跟着代码走谁改了流程在 Git 记录里能看到新人接手打开文件就明白整个流程这个价值在团队协作里太大了。自由风格任务的所有配置都在 Jenkins 的 XML 里改了什么根本查不出来。3.1 参数化构建怎么设计才够用参数设计的原则是够用就好别把选择权交给执行人。很多人喜欢把分支、环境、后端地址全做成参数结果每次发版都要填一堆填错一次就是事故。我的做法是把稳定不变的东西写死在配置里把真正需要人决策的做成参数而且尽量用下拉选择而不是文本框。具体到若依前端我通常只开放两个参数DEPLOY_ENV部署环境选项为prod、staging和BRANCH构建分支选项为master、release。如果项目用了 Git 参数插件可以让BRANCH从仓库里动态拉分支列表避免手输拼错。剩下的比如 Node 版本、构建命令、目标服务器地址全部根据DEPLOY_ENV在流水线内部映射不给人改的机会。这么做还有一个好处参数化构建的每一次执行都会记下参数值出了事故可以直接翻构建历史看当时选的是哪个环境哪个分支。这比事后问你当时选的是啥靠谱一百倍。3.2 构建环境注入与依赖源设置依赖安装这一步最大的变数是网络。国内环境直接从 npm 官方源拉包速度慢且不稳定构建时间能差出好几倍。所以流水线里第一件事就是把 registry 指向国内镜像源比如npm config set registry https://registry.npmmirror.com。这里有个经验不要在 Jenkins 全局配 registry而是在流水线的构建阶段临时设置。因为不同项目对源的要求可能不同全局配置容易互相污染而且改全局配置需要管理员权限前端同学没这个权限就卡住了。临时设置的方式很简单在 shell 步骤里执行npm config set registry ...就行它是针对当前用户和当前环境的。另外如果构建节点和你本地一样会保留 npm 缓存那构建速度会快很多。Jenkins 的工作空间默认会被复用node_modules和 npm 缓存都还在npm install时能命中大量缓存。但这也带来了问题如果缓存脏了构建会出现诡异错误。我的处理方式是在流水线里加一个手动参数CLEAN_CACHE默认 false出问题时勾上重新构建执行npm cache clean --force并删除node_modules。3.3 打包命令与内存参数打包阶段Vue2 和 Vue3 的命令不同需要在流水线里做判断。Vue2 走npm run build:prod或npm run build:stageVue3 走npm run build:prod内部是vite build。命令本身不复杂真正需要注意的是内存。Node 默认的堆内存上限在 64 位系统上大约 2GB新版本可能更高若依前端打包时如果开了 source map 或者依赖特别多很容易触顶报错JavaScript heap out of memory。解决方案是在执行构建命令前设置NODE_OPTIONS--max-old-space-size4096。这条环境变量对 vue-cli 和 Vite 都有效。不过要注意--max-old-space-size的值不能超过构建节点的物理内存否则进程会被系统 OOM Killer 干掉表现是构建突然中断没有任何日志。我一般把 4G 作为默认值构建节点内存 8G 起步。如果构建节点只有 4G那就设 2048同时考虑把 source map 关掉能在vue.config.js或vite.config.ts里配置productionSourceMap: false产物体积和内存占用都会明显下降。还有一个细节是构建时的时间戳和版本号。我喜欢在打包前把当前 commit 短哈希写进环境变量通过VUE_APP_BUILD_VERSION或者VITE_BUILD_VERSION注入到前端页面上某个角落显示出来。这样用户反馈页面有问题时第一句话就能问清是哪个版本效率极高。3.4 环境变量文件与多环境区分前面提过Vue2 用.env.productionVue3 用.env.production文件格式类似但变量前缀不同。这些文件一般跟着代码走但有些值比如后端地址不同环境不一样如果全部提交到仓库就得靠 Jenkins 在构建前覆盖。我见过两种做法。一种是把所有环境的值都写进仓库用不同的 mode 区分.env.staging对应 staging.env.production对应 prod构建时选命令即可。另一种是仓库里只放模板Jenkins 在构建前用 Shell 脚本根据参数生成真实的.env.production。前者简单但是环境配置暴露在代码里后者灵活但需要维护模板和生成逻辑。我倾向于前者理由是简单可追溯。若依这类项目通常只有两三套环境多写两个文件成本很低。只有在环境数量多、且后端地址经常变动的情况下才值得用生成方式。如果确实要给不同环境注入不同的值可以在流水线里用sh命令往环境文件追加内容比如echo VUE_APP_BASE_API$API_BASE .env.production注意追加之后要重新执行构建不能在构建后改。4. Pipeline 落地从拉代码到发布上线前面铺垫了这么多这一节直接把完整流程摆出来。我不打算给一个最佳实践模板让你复制粘贴因为每个团队的目标机结构、Nginx 配置、通知方式都不一样。我会给一个我实际在用的版本然后逐段解释为什么这么写你可以按自己的情况裁剪。4.1 Jenkinsfile 完整示例与逐段说明下面这份 Jenkinsfile 是我给 RuoYi-Vue3 前端工程用的Vue2 版本只需要把构建命令换掉、Node 版本改掉即可。pipeline { agent any parameters { choice(name: DEPLOY_ENV, choices: [prod, staging], description: 部署环境) choice(name: BRANCH, choices: [master, release], description: 构建分支) booleanParam(name: CLEAN_CACHE, defaultValue: false, description: 是否清理缓存) } options { timestamps() buildDiscarder(logRotator(numToKeepStr: 30, artifactNumToKeepStr: 10)) disableConcurrentBuilds() timeout(time: 30, unit: MINUTES) } environment { NODE_BIN tool name: node20, type: jenkins.plugins.nodejs.tools.NodeJSInstallation TARGET_HOST 10.0.0.21 TARGET_DIR /data/web/ruoyi-ui } stages { stage(拉取代码) { steps { checkout([$class: GitSCM, branches: [[name: */${params.BRANCH}]], userRemoteConfigs: [[ url: gitgitlab.internal:web/ruoyi-ui.git, credentialsId: gitlab-ruoyi-ui-readonly ]] ]) script { env.GIT_SHORT sh(script: git rev-parse --short HEAD, returnStdout: true).trim() } } } stage(准备环境) { steps { withEnv([PATHTOOL${NODE_BIN}/bin]) { sh node -v npm -v } } } stage(安装依赖) { steps { withEnv([PATHTOOL${NODE_BIN}/bin]) { sh npm config set registry https://registry.npmmirror.com npm config set sass_binary_site https://registry.npmmirror.com/-/binary/node-sass if [ $CLEAN_CACHE true ]; then rm -rf node_modules npm cache clean --force fi npm ci --no-audit --no-fund } } } stage(打包构建) { steps { withEnv([PATHTOOL${NODE_BIN}/bin, NODE_OPTIONS--max-old-space-size4096]) { sh if [ ${params.DEPLOY_ENV} prod ]; then npm run build:prod else npm run build:stage fi } } post { success { sh du -sh dist find dist -maxdepth 1 -type f | head -20 } } } stage(归档产物) { steps { archiveArtifacts artifacts: dist/**, fingerprint: true, allowEmptyArchive: false } } stage(发布到目标机) { steps { sh RELEASE_DIR${TARGET_DIR}/releases/\$(date %Y%m%d).\${BUILD_NUMBER} ssh deploy${TARGET_HOST} mkdir -p \$RELEASE_DIR rsync -az --delete dist/ deploy${TARGET_HOST}:\$RELEASE_DIR/ ssh deploy${TARGET_HOST} ln -sfn \$RELEASE_DIR ${TARGET_DIR}/current sudo nginx -t sudo nginx -s reload } } } post { always { cleanWs(deleteDirs: true, patterns: [[pattern: node_modules/**, type: INCLUDE]]) } success { echo 构建成功版本 ${env.GIT_SHORT}环境 ${params.DEPLOY_ENV} } failure { echo 构建失败请查看日志 } } }逐段说几个关键点。parameters里我把BRANCH做成了 choice实际项目中建议用 Git Parameter 插件动态拉取options里的disableConcurrentBuilds很重要前端打包很吃资源两个构建同时跑很容易把内存打满buildDiscarder保留 30 次构建记录和 10 次产物磁盘不至于爆炸timeout防止卡死占用执行器。environment里的NODE_BIN用了tool语法这是 NodeJS 插件提供的会自动把配置好的 Node 版本路径填进来。下面每个阶段用withEnv把它加到 PATH 前面这样node、npm命令就走这个版本不影响系统其他部分。拉取代码阶段末尾用git rev-parse --short HEAD取短哈希存进环境变量后面发通知时带上非常实用。安装依赖阶段我用了npm ci而不是npm install。npm ci严格按照package-lock.json安装保证每次构建的依赖树完全一致而且速度更快。前提是仓库里必须有 lock 文件没有的话要先跑一次npm install生成并提交。发布到目标机阶段用的是 rsync over SSH注意目标机上 deploy 账号需要配好免密登录并且sudo nginx -s reload要在 sudoers 里放开。这里我先nginx -t校验配置通过才 reload避免坏配置上线。4.2 产物归档与远程分发的几种方式除了 rsync还有几种分发方式值得知道。一种是Publish Over SSH插件配置图形化适合不熟悉命令行的人但它在流水线里的写法比较别扭而且传输大文件时性能不如 rsync。另一种是先scp传到临时目录再在目标机解压适合产物被打成了 tar.gz 的情况网络传输效率更高。还有一种是把 dist 传到对象存储Nginx 直接从对象存储回源这种适合多节点部署的场景。我选 rsync 的原因是它支持增量传输和--delete。前者让第二次之后的发布快很多后者保证目标目录和源目录严格一致不会残留旧文件。注意--delete要慎用路径写错会删掉不该删的东西所以我在 rsync 前先确保目标目录是新创建的版本目录。产物归档这块archiveArtifacts会把 dist 存进 Jenkins 的构建记录里。好处是任何时候都能从 Jenkins 界面下载到某次构建的产物回滚时不用重新打包。缺点是占磁盘所以artifactNumToKeepStr要设一个合理的值。我的习惯是保留最近 10 次。4.3 Nginx 目录切换与缓存策略发布完成后Nginx 那边还有两个细节要处理。第一个是try_files。若依前端是单页应用路由用的是 history 模式直接访问非根路径会 404所以 Nginx 配置里必须有try_files $uri $uri/ /index.html;。这条不加用户刷新页面就白屏是新手最常踩的坑之一。第二个是缓存策略。前端产物经过构建后JS 和 CSS 文件名里带哈希内容变了文件名就变所以可以放心地给它们设长缓存比如expires 1y。但index.html绝对不能缓存否则用户拿到的是旧页面引用的是已经不存在的旧资源文件页面直接崩。所以我的配置一般是location / { root /data/web/ruoyi-ui/current; try_files $uri $uri/ /index.html; index index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ { root /data/web/ruoyi-ui/current; expires 365d; add_header Cache-Control public, immutable; } location /index.html { root /data/web/ruoyi-ui/current; add_header Cache-Control no-cache, no-store, must-revalidate; } 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_pass http://127.0.0.1:8080/; }这里index.html单独用location 精确匹配并加不缓存头其他静态资源走长缓存。/prod-api/是若依前端默认的接口前缀代理到后端服务注意proxy_pass末尾的斜杠有没有斜杠决定了路径拼接方式写错会导致接口 404这个坑我踩过不止一次。4.4 构建通知与结果回传构建完成之后团队需要知道结果。最轻量的方式是 Jenkins 自带的邮件通知但邮件在国内团队里的打开率不高。我一般会接钉钉群机器人配置很简单在钉钉群里添加自定义机器人拿到 Webhook 地址Jenkins 里装 DingTalk 插件在系统配置里填好然后在流水线的 post 块里发送消息。消息内容我会带上这几个字段任务名、构建号、环境、分支、commit 短哈希、构建耗时、结果、构建链接。这样群里一看就知道发了什么、成没成。如果构建失败最好把最后几十行日志也带上省得大家都要点进去看。除了通知我还会在构建成功后调用一个接口刷新 CDN 缓存。如果项目用了 CDN前端发布之后不刷缓存用户可能还在访问旧文件。刷缓存的操作一般是调用云厂商的 API在流水线的 post success 里执行一个 curl 即可。这一步容易被忘但忘记的后果是明明发了版用户说没变化。5. 高频问题排查速查不管流程多顺问题总会来。我按构建阶段和部署阶段分开整理都是真实遇到过的附带排查思路。5.1 构建阶段典型报错最常见的三个JavaScript heap out of memory、node-sass编译失败、Module not found。第一个前面讲过加NODE_OPTIONS--max-old-space-size4096如果还不行要检查构建节点内存和是否同时跑了多个构建。第二个通常出现在 Vue2 项目上原因是 Node 版本过高或者sass_binary_site没配解决方式是降 Node 版本到 16 并配置镜像源的二进制地址。第三个大多是依赖没装全或者 lock 文件冲突先删node_modules和 lock 重装试试还不行就看看是不是某个人在package.json里引了私有包但没配源。还有一类是本地能打包Jenkins 上不行。这种基本可以断定是环境差异排查顺序是Node 版本、npm 版本、环境变量、时区、文件大小写。大小写这个问题特别隐蔽因为 Linux 区分大小写而 Windows 不区分本地import Hello from ./hello能过服务器上找不到./Hello报错信息还不直观。5.2 部署阶段典型报错Permission denied (publickey)是 SSH 免密没配好检查 Jenkins 服务器的公钥是否在目标机 deploy 用户的authorized_keys里权限位是否是 600。rsync: mkdir failed一般是目标目录权限不足检查 deploy 用户对 releases 目录有没有写权限。nginx: [emerg] unknown directive是 Nginx 配置写错了reload 之前一定跑nginx -t。ln: failed to create symbolic link说明软链接已经存在且指向别处用ln -sfn强制覆盖注意-n不能少否则会把链接建到目标目录里面去。还有一类是发布成功但页面 404。先看 Nginx 的 error log大概率是 root 路径不对或者try_files没配。其次是看目录软链接指向是否正确ls -l current一眼就能看出来。5.3 问题速查表现象可能原因排查动作构建 OOM堆内存不足或并发构建加NODE_OPTIONS开disableConcurrentBuildsnode-sass 编译失败Node 版本过高、二进制源不通降到 Node 16配sass_binary_site依赖安装超时源不通、网络抖动换国内镜像源重试SSH 连接被拒免密未配、权限位不对检查 authorized_keys 和 600 权限发布后页面白屏资源 404、index 缓存查 Nginx 日志确认 index 不缓存刷新页面 404history 模式缺 try_files补try_files $uri $uri/ /index.html接口 404proxy_pass 斜杠问题检查末尾斜杠和前缀用户看到旧页面CDN 或浏览器缓存刷 CDN确认 index 不缓存构建日志时间对不上容器时区是 UTC挂载/etc/localtime或设 TZ这张表我建议直接贴到团队文档里出问题时先扫一遍多数情况能自己解决。5.4 独家避坑技巧说几个不太常见但很值钱的技巧。第一给 Jenkins 容器设时区。默认容器是 UTC构建日志的时间和你沟通时说的刚才那次构建对不上排查问题非常痛苦。挂载宿主机的/etc/localtime或者在启动参数里加-e TZAsia/Shanghai就能解决。第二把 Node 版本检测加进流水线。在准备环境阶段打印node -v npm -v一旦版本不是预期的日志里立刻能看出来比翻半天源码强。第三给 Jenkinsfile 加timestamps()。构建日志前面会带时间戳定位哪一步耗时长非常直观配合 Stage View 看每一阶段的耗时优化目标一目了然。第四发布前用nginx -t做校验。这个动作只要两秒但能拦住 90% 的配置事故。我就见过有人改了 Nginx 配置直接 reload结果因为一个分号写错导致整个站挂掉。第五保留历史版本目录并且限制数量。我用一个定时任务每周清理超过 30 天的 releases 子目录既保证了回滚能力又不会把磁盘塞满。6. 我踩过的坑与可复用的经验最后这一节不写总结只聊点具体的。关于缓存和 lock 文件我的态度是package-lock.json必须提交构建必须用npm ci。曾经有个项目为了避免冲突把 lock 文件加进了.gitignore结果同一份代码在三个人手里装出三个不同的依赖树线上偶发问题时根本没法复现。后来把 lock 补上问题消失。代价是每次升级依赖都要处理冲突但这个代价值得。关于并发构建我吃过一次大亏。当时图省事没开disableConcurrentBuilds两个同学同时触发构建两个npm install同时往同一个node_modules目录写结果装出来的依赖是混乱的打包产物直接报错。更惨的是那次构建成功了上线之后页面报奇怪的undefined is not a function排查了大半天。从那以后所有前端任务我都加上串行限制。关于回滚我的体会是回滚能力的上限取决于你在设计阶段留了多少余地。用软链接切换的方案回滚就是改一个链接指向一秒完成如果用覆盖式发布回滚就得重新打包再传一遍五分钟起步还要承担打包环境已经变了的风险。所以哪怕项目再小我也建议在第一天就把目录结构规划成带 releases 的形态前期多花十分钟后面省无数事。关于 Node 版本还有个小经验如果构建节点上同时装了两个 Node一定要在流水线里显式声明用哪个不要依赖系统默认。我见过一个任务因为系统默认 Node 被升级从 16 变成 18第二天全部 Vue2 项目打包失败。显式声明之后Node 升级再也不会影响存量项目。再分享一个后续可以扩展的方向如果团队规模上来了多分支、多环境、多项目会让 Jenkins 任务数量爆炸这时候可以考虑把 Jenkinsfile 抽成共享库用Library引入公共的构建逻辑各个项目只保留差异部分。再往前一步是把发布和 K8s 结合起来前端产物做成镜像用 Deployment 滚动更新回滚就变成了镜像版本回退。不过那是另一个话题了等这套 Jenkins 流程跑稳半年之后再考虑也不迟。我个人在实际维护这套流程的过程中最大的感受是自动化的价值不在于省下的那点操作时间而在于它把发布这件事从依赖某个人的记忆和手感变成了依赖一段可以被 review、被版本管理的代码。前者会随着人员流动而消失后者会一直留在仓库里。
02
RELATED NEWS

相关资讯

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

03
WHY YAOTU

想打造同款高转化官网?

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

◈

场景化定制

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

◐

营销型架构

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

▲

全周期服务

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

免费获取你的建站方案

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