pipeline { agent any environment { // 环境变量定义 PROJECT_NAME = 'timeline-frontend' DOCKER_REGISTRY = 'timeline-registry:5000' 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 { if (params.GIT_COMMIT) { checkout scm sh "git reset --hard ${params.GIT_COMMIT}" } else { checkout scm } } echo "当前构建的 Git Commit: ${env.GIT_COMMIT}" } } stage('Build timeline-frontend dist') { steps { script { def workspace = sh(script: "pwd", returnStdout: true).trim() echo "当前工作空间路径: ${workspace}" // 确保路径正确 sh "ls -la ${workspace}" // 修复权限问题 sh "chmod -R 755 ${workspace}" sh "chown -R jenkins:jenkins ${workspace}" // 使用 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') } // 安装 pnpm sh 'npm install -g pnpm' // 使用 pnpm 安装依赖,由于锁文件兼容性问题,不使用 --frozen-lockfile sh 'pnpm install --no-frozen-lockfile' // 构建项目 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' } } else { error('package.json not found') } } } } } } stage('Build Docker Image') { steps { script { def imageTag = "${BUILD_NUMBER}-${env.GIT_COMMIT.take(7)}" env.IMAGE_TAG = imageTag // 创建一个简单的 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 ''' sh """ docker build -t ${DOCKER_IMAGE}:latest """ } } } stage('Push Docker Image') { steps { script { sh "docker push ${DOCKER_IMAGE}:latest" } } } 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}:latest" // 停止现有容器 sh """ docker stop ${containerName} || true docker rm ${containerName} || true """ // 运行新容器 sh """ docker run -d \ --name ${containerName} \ --restart unless-stopped \ -p ${getPortForEnvironment(env)}:80 \ ${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': return '80' default: return '80' } }