2025-12-26 21:02:15 +08:00
|
|
|
pipeline {
|
2025-12-26 21:18:32 +08:00
|
|
|
agent any
|
|
|
|
|
|
2025-12-26 21:02:15 +08:00
|
|
|
environment {
|
|
|
|
|
// 环境变量定义
|
|
|
|
|
PROJECT_NAME = 'timeline-frontend'
|
2025-12-26 21:18:32 +08:00
|
|
|
DOCKER_REGISTRY = 'timeline-registry:5000'
|
2025-12-26 21:02:15 +08:00
|
|
|
DOCKER_IMAGE = "${DOCKER_REGISTRY}/${PROJECT_NAME}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parameters {
|
|
|
|
|
// 构建参数
|
|
|
|
|
choice(
|
|
|
|
|
name: 'DEPLOY_TARGET',
|
|
|
|
|
choices: ['dev', 'staging', 'prod'],
|
|
|
|
|
description: '选择部署环境'
|
|
|
|
|
)
|
|
|
|
|
string(
|
|
|
|
|
name: 'GIT_COMMIT',
|
|
|
|
|
defaultValue: '',
|
|
|
|
|
description: '指定 Git Commit SHA 进行构建(留空则使用最新提交)'
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stages {
|
|
|
|
|
stage('Checkout') {
|
|
|
|
|
steps {
|
|
|
|
|
script {
|
2025-12-26 21:14:40 +08:00
|
|
|
if (params.GIT_COMMIT) {
|
|
|
|
|
checkout scm
|
|
|
|
|
sh "git reset --hard ${params.GIT_COMMIT}"
|
|
|
|
|
} else {
|
|
|
|
|
checkout scm
|
|
|
|
|
}
|
2025-12-26 21:02:15 +08:00
|
|
|
}
|
2025-12-26 21:14:40 +08:00
|
|
|
echo "当前构建的 Git Commit: ${env.GIT_COMMIT}"
|
2025-12-26 21:02:15 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-26 21:18:32 +08:00
|
|
|
|
2025-12-26 21:08:00 +08:00
|
|
|
stage('Build timeline-frontend dist') {
|
2025-12-26 21:02:15 +08:00
|
|
|
steps {
|
|
|
|
|
script {
|
2025-12-29 15:13:23 +08:00
|
|
|
def workspace = sh(script: "pwd", returnStdout: true).trim()
|
2025-12-29 14:39:16 +08:00
|
|
|
echo "当前工作空间路径: ${workspace}"
|
2025-12-29 14:36:47 +08:00
|
|
|
|
|
|
|
|
// 确保路径正确
|
2025-12-29 14:39:16 +08:00
|
|
|
sh "ls -la ${workspace}"
|
2025-12-29 14:36:47 +08:00
|
|
|
|
2025-12-29 15:47:36 +08:00
|
|
|
// 修复权限问题
|
|
|
|
|
sh "chmod -R 755 ${workspace}"
|
|
|
|
|
sh "chown -R jenkins:jenkins ${workspace}"
|
2025-12-29 15:26:05 +08:00
|
|
|
|
2025-12-29 15:47:36 +08:00
|
|
|
// 使用 Jenkins Node.js 插件,并确保在工作空间目录中执行
|
|
|
|
|
dir(workspace) {
|
|
|
|
|
nodejs('NodeJS-18') {
|
|
|
|
|
// 检查是否存在 package.json 和 pnpm-lock.yaml
|
|
|
|
|
if (fileExists('package.json')) {
|
|
|
|
|
echo "package.json found"
|
|
|
|
|
if (fileExists('pnpm-lock.yaml')) {
|
|
|
|
|
echo "pnpm-lock.yaml found"
|
|
|
|
|
} else {
|
|
|
|
|
error('pnpm-lock.yaml not found')
|
|
|
|
|
}
|
2025-12-29 15:41:20 +08:00
|
|
|
|
2025-12-29 15:47:36 +08:00
|
|
|
// 安装 pnpm
|
|
|
|
|
sh 'npm install -g pnpm'
|
2025-12-29 15:41:20 +08:00
|
|
|
|
2025-12-29 15:55:25 +08:00
|
|
|
// 使用 pnpm 安装依赖,由于锁文件兼容性问题,不使用 --frozen-lockfile
|
|
|
|
|
sh 'pnpm install --no-frozen-lockfile'
|
2025-12-29 15:41:20 +08:00
|
|
|
|
2025-12-29 15:47:36 +08:00
|
|
|
// 构建项目
|
|
|
|
|
sh 'pnpm run build'
|
|
|
|
|
|
|
|
|
|
// 检查 dist 目录是否存在
|
|
|
|
|
if (!fileExists('dist')) {
|
|
|
|
|
error('ERROR: dist directory does not exist after build')
|
|
|
|
|
} else {
|
|
|
|
|
echo 'Build completed successfully, dist directory exists'
|
|
|
|
|
}
|
2025-12-29 15:41:20 +08:00
|
|
|
} else {
|
2025-12-29 15:47:36 +08:00
|
|
|
error('package.json not found')
|
2025-12-29 15:41:20 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-29 15:24:13 +08:00
|
|
|
}
|
2025-12-29 14:55:43 +08:00
|
|
|
}
|
2025-12-26 21:08:00 +08:00
|
|
|
}
|
2025-12-26 21:02:15 +08:00
|
|
|
|
2025-12-26 21:08:00 +08:00
|
|
|
stage('Build Docker Image') {
|
|
|
|
|
steps {
|
|
|
|
|
script {
|
2025-12-26 21:02:15 +08:00
|
|
|
def imageTag = "${BUILD_NUMBER}-${env.GIT_COMMIT.take(7)}"
|
|
|
|
|
env.IMAGE_TAG = imageTag
|
2025-12-26 21:18:32 +08:00
|
|
|
|
|
|
|
|
// 创建一个简单的 nginx 配置(可选)
|
|
|
|
|
sh '''
|
|
|
|
|
cat > nginx.conf << 'EOF'
|
|
|
|
|
events {
|
|
|
|
|
worker_connections 1024;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
http {
|
|
|
|
|
include /etc/nginx/mime.types;
|
|
|
|
|
default_type application/octet-stream;
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
listen 80;
|
|
|
|
|
server_name localhost;
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
root /usr/share/nginx/html;
|
|
|
|
|
index index.html index.htm;
|
|
|
|
|
try_files $uri $uri/ /index.html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error_page 500 502 503 504 /50x.html;
|
|
|
|
|
location = /50x.html {
|
|
|
|
|
root /usr/share/nginx/html;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
EOF
|
|
|
|
|
'''
|
|
|
|
|
|
2025-12-26 21:02:15 +08:00
|
|
|
sh """
|
|
|
|
|
docker build -t ${DOCKER_IMAGE}:${imageTag} .
|
|
|
|
|
docker tag ${DOCKER_IMAGE}:${imageTag} ${DOCKER_IMAGE}:latest
|
|
|
|
|
"""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stage('Push Docker Image') {
|
|
|
|
|
steps {
|
|
|
|
|
script {
|
2025-12-29 16:08:27 +08:00
|
|
|
sh "docker push ${DOCKER_IMAGE}:${env.IMAGE_TAG}"
|
|
|
|
|
sh "docker push ${DOCKER_IMAGE}:latest"
|
2025-12-26 21:02:15 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stage('Deploy to Environment') {
|
|
|
|
|
parallel {
|
|
|
|
|
stage('Deploy to Dev') {
|
|
|
|
|
when {
|
|
|
|
|
expression { params.DEPLOY_TARGET == 'dev' }
|
|
|
|
|
}
|
|
|
|
|
steps {
|
|
|
|
|
deployToEnvironment('dev')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stage('Deploy to Staging') {
|
|
|
|
|
when {
|
|
|
|
|
anyOf {
|
|
|
|
|
expression { params.DEPLOY_TARGET == 'staging' }
|
|
|
|
|
expression { params.DEPLOY_TARGET == 'prod' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
steps {
|
|
|
|
|
deployToEnvironment('staging')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stage('Deploy to Production') {
|
|
|
|
|
when {
|
|
|
|
|
expression { params.DEPLOY_TARGET == 'prod' }
|
|
|
|
|
}
|
|
|
|
|
steps {
|
|
|
|
|
script {
|
|
|
|
|
input message: "确定要部署到生产环境吗?", ok: "是", parameters: [
|
|
|
|
|
choice(name: 'CONFIRM_DEPLOY', choices: ['yes', 'no'], description: '确认部署')
|
|
|
|
|
]
|
|
|
|
|
if (params.CONFIRM_DEPLOY == 'yes') {
|
|
|
|
|
deployToEnvironment('prod')
|
|
|
|
|
} else {
|
|
|
|
|
error "部署被取消"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
post {
|
|
|
|
|
always {
|
|
|
|
|
// 清理构建产物
|
|
|
|
|
cleanWs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
success {
|
|
|
|
|
script {
|
|
|
|
|
def slackMessage = """
|
|
|
|
|
*✅ 构建成功*
|
|
|
|
|
项目: ${PROJECT_NAME}
|
|
|
|
|
构建: ${BUILD_NUMBER}
|
|
|
|
|
分支: ${env.BRANCH_NAME}
|
|
|
|
|
提交: ${env.GIT_COMMIT}
|
|
|
|
|
镜像: ${DOCKER_IMAGE}:${env.IMAGE_TAG}
|
|
|
|
|
"""
|
|
|
|
|
// 发送成功通知(如果配置了 Slack 通知)
|
|
|
|
|
// slackSend(channel: '#builds', color: 'good', message: slackMessage)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
failure {
|
|
|
|
|
script {
|
|
|
|
|
def slackMessage = """
|
|
|
|
|
*❌ 构建失败*
|
|
|
|
|
项目: ${PROJECT_NAME}
|
|
|
|
|
构建: ${BUILD_NUMBER}
|
|
|
|
|
分支: ${env.BRANCH_NAME}
|
|
|
|
|
提交: ${env.GIT_COMMIT}
|
|
|
|
|
错误: ${currentBuild.description ?: '构建失败'}
|
|
|
|
|
"""
|
|
|
|
|
// 发送失败通知(如果配置了 Slack 通知)
|
|
|
|
|
// slackSend(channel: '#builds', color: 'danger', message: slackMessage)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup {
|
|
|
|
|
// 清理 Docker 镜像
|
|
|
|
|
script {
|
|
|
|
|
sh """
|
|
|
|
|
docker rmi -f ${DOCKER_IMAGE}:${env.IMAGE_TAG} || true
|
|
|
|
|
docker rmi -f ${DOCKER_IMAGE}:latest || true
|
|
|
|
|
"""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 公共函数定义
|
|
|
|
|
def deployToEnvironment(String env) {
|
|
|
|
|
script {
|
|
|
|
|
// 根据环境设置部署参数
|
|
|
|
|
def containerName = "${PROJECT_NAME}-${env}"
|
|
|
|
|
def imageToDeploy = "${DOCKER_IMAGE}:${env.IMAGE_TAG}"
|
|
|
|
|
|
|
|
|
|
// 停止现有容器
|
|
|
|
|
sh """
|
|
|
|
|
docker stop ${containerName} || true
|
|
|
|
|
docker rm ${containerName} || true
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
// 运行新容器
|
|
|
|
|
sh """
|
|
|
|
|
docker run -d \
|
|
|
|
|
--name ${containerName} \
|
|
|
|
|
--restart unless-stopped \
|
2025-12-26 21:18:32 +08:00
|
|
|
-p ${getPortForEnvironment(env)}:80 \
|
2025-12-26 21:02:15 +08:00
|
|
|
${imageToDeploy}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
// 验证部署
|
|
|
|
|
sh """
|
|
|
|
|
echo "等待容器启动..."
|
|
|
|
|
sleep 10
|
|
|
|
|
docker ps | grep ${containerName}
|
|
|
|
|
docker logs ${containerName}
|
|
|
|
|
"""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取环境对应端口的辅助函数
|
|
|
|
|
def getPortForEnvironment(String env) {
|
|
|
|
|
switch(env) {
|
|
|
|
|
case 'dev':
|
|
|
|
|
return '3001'
|
|
|
|
|
case 'staging':
|
|
|
|
|
return '3002'
|
|
|
|
|
case 'prod':
|
2025-12-26 21:18:32 +08:00
|
|
|
return '80'
|
2025-12-26 21:02:15 +08:00
|
|
|
default:
|
2025-12-26 21:18:32 +08:00
|
|
|
return '80'
|
2025-12-26 21:02:15 +08:00
|
|
|
}
|
|
|
|
|
}
|