# 前端 Jenkins 部署指南 ## 一、项目概况 | 项目 | 说明 | |------|------| | 源码路径 | `nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/` | | 框架 | Vue 3 + Vite + Ant Design Vue 4(基于 vue-vben-admin 5.7.0) | | 包管理器 | pnpm 11.7.0(强制,不可用 npm/yarn) | | Node 版本 | >= 22.18.0(`.node-version` 指定 24.16.0) | | 构建工具 | Turbo(monorepo 编排)+ Vite(打包) | | 构建产物 | `apps/web-antdv-next/dist/` | | 路由模式 | hash 模式(`VITE_ROUTER_HISTORY=hash`) | --- ## 二、环境变量说明 构建时通过 `.env.production` 控制关键配置,可按环境覆盖。 **文件位置:** `apps/web-antdv-next/.env.production` | 变量 | 默认值 | 说明 | |------|--------|------| | `VITE_BASE` | `/` | 部署路径前缀,部署在子路径时改为 `/admin/` | | `VITE_GLOB_API_URL` | `http://127.0.0.1:48080/admin-api` | 后端 API 地址(完整 URL) | | `VITE_ROUTER_HISTORY` | `hash` | 路由模式,hash 模式无需 nginx 特殊配置 | | `VITE_COMPRESS` | `none` | 压缩方式(none / gzip / brotli) | | `VITE_ARCHIVER` | `true` | 是否生成 `dist.zip` | | `VITE_PWA` | `false` | 是否启用 PWA | **多环境覆盖方式**:在 Jenkins 构建步骤中创建 `.env.production.local` 文件覆盖 `VITE_GLOB_API_URL` 等变量,Vite 会优先读取 local 文件。 --- ## 三、Jenkins 流水线(Pipeline) ### 3.1 声明式流水线(推荐,使用 Docker 镜像自带 Node 环境) ```groovy pipeline { // 使用 Docker 镜像,自带 Node 24 + pnpm,不需要 Jenkins 宿主机装 Node agent { docker { image 'node:24-slim' args '-u root --memory=4g' } } // 构建参数,支持按环境选择 parameters { choice(name: 'DEPLOY_ENV', choices: ['dev', 'staging', 'prod'], description: '部署环境') string(name: 'API_BASE_URL', defaultValue: 'http://127.0.0.1:48080/admin-api', description: '后端 API 地址') } environment { // 源码子目录(仓库根目录下的相对路径) SOURCE_DIR = 'nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben' // pnpm 缓存目录,挂到宿主机避免重复下载 PNPM_HOME = '/root/.local/share/pnpm' } stages { // ==================== 第一步:拉取代码 ==================== stage('Checkout') { steps { checkout scm } } // ==================== 第二步:安装依赖 ==================== stage('Setup') { steps { dir(env.SOURCE_DIR) { sh 'corepack enable' sh 'corepack prepare pnpm@11.7.0 --activate' // 安装依赖(锁定版本) sh 'pnpm install --frozen-lockfile' } } } // ==================== 第三步:代码检查(可选) ==================== stage('Lint') { steps { dir(env.SOURCE_DIR) { sh 'pnpm run lint || true' } } } // ==================== 第四步:构建 ==================== stage('Build') { steps { dir(env.SOURCE_DIR) { script { // 根据部署环境覆盖 API 地址 def apiUrl = params.API_BASE_URL if (params.DEPLOY_ENV == 'prod') { apiUrl = 'https://api.your-domain.com/admin-api' } else if (params.DEPLOY_ENV == 'staging') { apiUrl = 'https://staging-api.your-domain.com/admin-api' } // 写入环境变量(Vite 构建时读取) writeFile file: 'apps/web-antdv-next/.env.production.local', text: """ VITE_GLOB_API_URL=${apiUrl} """.stripIndent().trim() // 执行构建 sh '''#!/bin/bash export NODE_OPTIONS="--max-old-space-size=8192" pnpm run build --filter=@vben/web-antdv-next ''' } } } } // ==================== 第五步:打包产物 ==================== stage('Archive') { steps { dir(env.SOURCE_DIR) { script { def distPath = 'apps/web-antdv-next/dist' sh "tar -czf dist-${params.DEPLOY_ENV}.tar.gz -C ${distPath} ." archiveArtifacts artifacts: "dist-${params.DEPLOY_ENV}.tar.gz", fingerprint: true } } } } // ==================== 第六步:部署 ==================== stage('Deploy') { when { expression { params.DEPLOY_ENV == 'dev' || params.DEPLOY_ENV == 'staging' || params.DEPLOY_ENV == 'prod' } } steps { dir(env.SOURCE_DIR) { script { def serverIp = '' def deployPath = '/usr/share/nginx/html/admin' if (params.DEPLOY_ENV == 'dev') { serverIp = '192.168.1.10' } else if (params.DEPLOY_ENV == 'staging') { serverIp = '192.168.1.20' } else if (params.DEPLOY_ENV == 'prod') { serverIp = '192.168.1.30' } sshagent(['deploy-ssh-key']) { sh """ ssh root@${serverIp} 'mkdir -p ${deployPath}' scp dist-${params.DEPLOY_ENV}.tar.gz root@${serverIp}:/tmp/ ssh root@${serverIp} ' rm -rf ${deployPath}/* tar -xzf /tmp/dist-${params.DEPLOY_ENV}.tar.gz -C ${deployPath} rm -f /tmp/dist-${params.DEPLOY_ENV}.tar.gz nginx -t && nginx -s reload ' """ } } } } } } post { success { echo "构建成功!环境:${params.DEPLOY_ENV}" } failure { echo "构建失败!请检查日志。" } always { cleanWs() } } } ``` ### 3.2 简化版流水线(仅构建 + 归档,手动部署) ```groovy pipeline { agent { docker { image 'node:24-slim' args '-u root --memory=4g' } } parameters { string(name: 'API_BASE_URL', defaultValue: 'http://127.0.0.1:48080/admin-api', description: '后端 API 地址') string(name: 'VITE_BASE', defaultValue: '/', description: '部署子路径') } environment { SOURCE_DIR = 'nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben' } stages { stage('Checkout') { steps { checkout scm } } stage('Install & Build') { steps { dir(env.SOURCE_DIR) { sh 'corepack enable && corepack prepare pnpm@11.7.0 --activate' sh 'pnpm install --frozen-lockfile' writeFile file: 'apps/web-antdv-next/.env.production.local', text: """ VITE_GLOB_API_URL=${params.API_BASE_URL} VITE_BASE=${params.VITE_BASE} """.stripIndent().trim() sh ''' export NODE_OPTIONS="--max-old-space-size=8192" pnpm run build --filter=@vben/web-antdv-next ''' } } } stage('Package') { steps { dir("${env.SOURCE_DIR}/apps/web-antdv-next") { sh 'tar -czf dist.tar.gz -C dist .' archiveArtifacts artifacts: 'dist.tar.gz', fingerprint: true } } } } post { always { cleanWs() } } } ``` --- ## 四、Nginx 部署配置 项目使用 hash 路由,nginx 配置很简单: ```nginx server { listen 80; server_name admin.your-domain.com; # 前端静态文件 root /usr/share/nginx/html/admin; index index.html; # hash 路由模式,单页应用标准配置 location / { try_files $uri $uri/ /index.html; } # API 反向代理到后端网关 location /admin-api/ { proxy_pass http://backend-gateway:48080/admin-api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` --- ## 五、Docker 部署(可选) 项目自带 Dockerfile 但默认构建的是 playground 应用,需要调整。推荐新建一个: ```dockerfile # Dockerfile 放在 yudao-ui-admin-vben/ 目录下 # ============ 构建阶段 ============ FROM node:24-slim AS builder RUN npm install -g pnpm@11.7.0 WORKDIR /app COPY pnpm-lock.yaml pnpm-workspace.yaml package.json turbo.json .npmrc ./ COPY internal/ internal/ COPY packages/ packages/ COPY apps/web-antdv-next/ apps/web-antdv-next/ RUN pnpm install --frozen-lockfile ARG VITE_GLOB_API_URL=http://127.0.0.1:48080/admin-api ENV VITE_GLOB_API_URL=${VITE_GLOB_API_URL} RUN NODE_OPTIONS="--max-old-space-size=8192" pnpm run build --filter=@vben/web-antdv-next # ============ 运行阶段 ============ FROM nginx:stable-alpine COPY --from=builder /app/apps/web-antdv-next/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` 构建镜像: ```bash docker build \ --build-arg VITE_GLOB_API_URL=https://api.your-domain.com/admin-api \ -t nl-admin-ui:latest \ . ``` --- ## 六、Jenkins 配置清单 在 Jenkins 上配置前需要准备: | 配置项 | 说明 | |--------|------| | **Node.js 插件** | 安装 Jenkins NodeJS Plugin,添加 Node.js 24.x 安装 | | **SSH 凭据** | 添加目标服务器的 SSH 私钥(ID 为 `deploy-ssh-key`) | | **Git 仓库** | 确保 Jenkins 能访问代码仓库 | | **构建机资源** | 内存 >= 4GB(`NODE_OPTIONS=--max-old-space-size=8192`) | | **网络** | 构建机能访问 `registry.npmmirror.com`(或换内网 npm 镜像) | --- ## 七、常见问题 ### Q1:构建报 `pnpm: command not found` Jenkins 环境中 `corepack enable` 可能不生效,改用 `npm install -g pnpm@11.7.0`。 ### Q2:构建 OOM(内存溢出) 调大 Node 内存限制:`NODE_OPTIONS="--max-old-space-size=8192"`,流水线中已默认设置。 ### Q3:API 地址不对 检查 `.env.production.local` 是否在构建前正确写入,且 `VITE_GLOB_API_URL` 为完整 URL(如 `https://api.your-domain.com/admin-api`)。 ### Q4:页面 404 白屏 确认 nginx 配置了 `try_files $uri $uri/ /index.html`,以及 `VITE_BASE` 与部署路径匹配。