diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a2b04f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# 使用 nginx 作为基础镜像 +FROM nginx:alpine + +# 将构建好的前端文件复制到 nginx 的默认目录 +COPY dist/ /usr/share/nginx/html/ + +# 复制 nginx 配置文件(如果存在) +COPY nginx.conf /etc/nginx/nginx.conf + +# 暴露端口 +EXPOSE 80 + +# 启动 nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..2f16085 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,207 @@ +pipeline { + agent any + + environment { + // 环境变量定义 + PROJECT_NAME = 'timeline-frontend' + 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 Docker Image') { + steps { + script { + // 确保先执行前端构建命令 + sh 'npm install' + sh 'npm run build' // 这会生成 dist 目录 + + def imageTag = "${BUILD_NUMBER}-${env.GIT_COMMIT.take(7)}" + env.IMAGE_TAG = imageTag + sh """ + docker build -t ${DOCKER_IMAGE}:${imageTag} . + docker tag ${DOCKER_IMAGE}:${imageTag} ${DOCKER_IMAGE}:latest + """ + } + } + } + + stage('Push Docker Image') { + steps { + script { + withCredentials([usernamePassword(credentialsId: 'docker-registry-credentials', + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS')]) { + sh 'echo $DOCKER_PASS | docker login $DOCKER_REGISTRY -u $DOCKER_USER --password-stdin' + sh "docker push ${DOCKER_IMAGE}:${env.IMAGE_TAG}" + 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}:${env.IMAGE_TAG}" + + // 停止现有容器 + sh """ + docker stop ${containerName} || true + docker rm ${containerName} || true + """ + + // 运行新容器 + sh """ + docker run -d \ + --name ${containerName} \ + --restart unless-stopped \ + -p ${getPortForEnvironment(env)}:3000 \ + ${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 '3000' + default: + return '3000' + } +} diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..bd352a2 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,24 @@ +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; # 用于处理 SPA 路由 + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +}