348 lines
11 KiB
Markdown
348 lines
11 KiB
Markdown
|
|
# 前端 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` 与部署路径匹配。
|