2025-12-24 14:56:09 +08:00
|
|
|
|
pipeline {
|
|
|
|
|
|
agent any
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
tools {
|
2025-12-24 15:39:28 +08:00
|
|
|
|
maven 'maven 3.9.12' // 修正Maven工具名称
|
2025-12-24 15:33:40 +08:00
|
|
|
|
jdk 'openjdk21' // 修正JDK工具名称,使用Jenkins中实际配置的名称
|
2025-12-24 15:25:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
environment {
|
|
|
|
|
|
REGISTRY = 'timeline-registry:5000'
|
|
|
|
|
|
PROJECT_NAME = 'timeline-server'
|
|
|
|
|
|
DOCKER_REGISTRY = 'timeline-registry:5000'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parameters {
|
|
|
|
|
|
choice(
|
|
|
|
|
|
name: 'DEPLOY_ENV',
|
|
|
|
|
|
choices: ['dev', 'staging', 'prod'],
|
|
|
|
|
|
description: '选择部署环境'
|
|
|
|
|
|
)
|
|
|
|
|
|
string(
|
|
|
|
|
|
name: 'BRANCH_NAME',
|
|
|
|
|
|
defaultValue: 'main',
|
|
|
|
|
|
description: '构建分支'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 14:56:09 +08:00
|
|
|
|
stages {
|
2025-12-24 15:25:08 +08:00
|
|
|
|
stage('Checkout') {
|
|
|
|
|
|
steps {
|
|
|
|
|
|
checkout scm
|
|
|
|
|
|
script {
|
|
|
|
|
|
echo "当前分支: ${params.BRANCH_NAME}"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 14:56:09 +08:00
|
|
|
|
stage('Build') {
|
|
|
|
|
|
steps {
|
2025-12-24 15:25:08 +08:00
|
|
|
|
script {
|
|
|
|
|
|
echo '开始构建项目'
|
|
|
|
|
|
sh 'mvn clean compile -DskipTests'
|
|
|
|
|
|
}
|
2025-12-24 14:56:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
stage('Test') {
|
2025-12-24 14:56:09 +08:00
|
|
|
|
steps {
|
2025-12-24 15:25:08 +08:00
|
|
|
|
script {
|
|
|
|
|
|
echo '运行单元测试'
|
|
|
|
|
|
sh 'mvn test'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
post {
|
|
|
|
|
|
always {
|
2025-12-24 16:05:59 +08:00
|
|
|
|
script {
|
|
|
|
|
|
// 检查测试报告文件是否存在
|
2025-12-24 16:09:41 +08:00
|
|
|
|
def testReportDir = 'target/surefire-reports'
|
|
|
|
|
|
def hasReportDir = fileExists testReportDir
|
|
|
|
|
|
|
|
|
|
|
|
if (hasReportDir) {
|
|
|
|
|
|
def reportCount = sh(
|
|
|
|
|
|
script: "find ${testReportDir} -name 'TEST-*.xml' | wc -l",
|
|
|
|
|
|
returnStdout: true
|
|
|
|
|
|
).trim() as int
|
|
|
|
|
|
|
|
|
|
|
|
if (reportCount > 0) {
|
|
|
|
|
|
echo "找到 ${reportCount} 个测试报告文件"
|
|
|
|
|
|
junit testResults: 'target/surefire-reports/TEST-*.xml'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
echo "未找到测试报告文件,跳过JUnit发布"
|
|
|
|
|
|
}
|
2025-12-24 16:05:59 +08:00
|
|
|
|
} else {
|
2025-12-24 16:09:41 +08:00
|
|
|
|
echo "未找到测试报告目录,跳过JUnit发布"
|
2025-12-24 16:05:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-24 15:25:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stage('Package') {
|
|
|
|
|
|
steps {
|
|
|
|
|
|
script {
|
|
|
|
|
|
echo '打包项目'
|
|
|
|
|
|
sh 'mvn package -DskipTests'
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
// 保存构建产物
|
|
|
|
|
|
archiveArtifacts artifacts: 'timeline-gateway-service/target/*.jar, timeline-user-service/target/*.jar, timeline-story-service/target/*.jar, timeline-file-service/target/*.jar', fingerprint: true
|
|
|
|
|
|
}
|
2025-12-24 14:56:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
2025-12-24 17:28:47 +08:00
|
|
|
|
stage('Build and Push Docker Images') {
|
2025-12-24 15:25:08 +08:00
|
|
|
|
steps {
|
|
|
|
|
|
script {
|
2025-12-24 16:39:39 +08:00
|
|
|
|
def services = ['gateway', 'user', 'story', 'file']
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 16:39:39 +08:00
|
|
|
|
for (service in services) {
|
|
|
|
|
|
def serviceDir = "timeline-${service}-service"
|
|
|
|
|
|
def imageName = "${REGISTRY}/timeline-${service}-service:${BUILD_NUMBER}"
|
|
|
|
|
|
def latestImageName = "${REGISTRY}/timeline-${service}-service:latest"
|
|
|
|
|
|
|
|
|
|
|
|
// 检查Dockerfile是否存在,如果不存在则创建
|
|
|
|
|
|
if (!fileExists("${serviceDir}/Dockerfile")) {
|
|
|
|
|
|
writeFile file: "${serviceDir}/Dockerfile", text: getDockerfileContent(serviceDir)
|
2025-12-24 15:25:08 +08:00
|
|
|
|
}
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 16:39:39 +08:00
|
|
|
|
// 构建镜像
|
|
|
|
|
|
sh "docker build -t ${imageName} -t ${latestImageName} ${serviceDir}/."
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 17:28:47 +08:00
|
|
|
|
// 推送镜像
|
2025-12-24 15:25:08 +08:00
|
|
|
|
sh "docker push ${imageName}"
|
|
|
|
|
|
sh "docker push ${latestImageName}"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 14:56:09 +08:00
|
|
|
|
stage('Deploy') {
|
|
|
|
|
|
steps {
|
2025-12-24 15:25:08 +08:00
|
|
|
|
script {
|
|
|
|
|
|
// 创建或更新docker-compose文件
|
|
|
|
|
|
def composeContent = getComposeFileContent(BUILD_NUMBER)
|
|
|
|
|
|
writeFile file: 'docker-compose.yml', text: composeContent
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
// 拉取最新镜像
|
2025-12-24 18:01:49 +08:00
|
|
|
|
sh 'docker compose pull'
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
// 停止旧容器
|
2025-12-24 18:01:49 +08:00
|
|
|
|
sh 'docker compose down || true'
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
// 启动新容器
|
2025-12-24 18:01:49 +08:00
|
|
|
|
sh 'docker compose up -d'
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
echo "所有服务已部署完成"
|
|
|
|
|
|
}
|
2025-12-24 14:56:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
post {
|
|
|
|
|
|
success {
|
|
|
|
|
|
script {
|
|
|
|
|
|
sh 'echo "构建和部署成功完成"'
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
// 发送成功通知
|
2025-12-24 17:28:47 +08:00
|
|
|
|
//emailext (
|
|
|
|
|
|
// subject: "构建成功: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
|
|
|
|
|
|
// body: "构建成功: ${env.BUILD_URL}",
|
|
|
|
|
|
// to: "332515344@qq.com"
|
|
|
|
|
|
//)
|
2025-12-24 15:25:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
failure {
|
|
|
|
|
|
script {
|
|
|
|
|
|
sh 'echo "构建或部署失败"'
|
2025-12-24 15:39:28 +08:00
|
|
|
|
|
2025-12-24 15:25:08 +08:00
|
|
|
|
// 发送失败通知
|
2025-12-24 17:28:47 +08:00
|
|
|
|
//emailext (
|
|
|
|
|
|
// subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
|
|
|
|
|
|
// body: "构建失败: ${env.BUILD_URL}",
|
|
|
|
|
|
// to: "332515344@qq.com"
|
|
|
|
|
|
//)
|
2025-12-24 15:25:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
always {
|
|
|
|
|
|
cleanWs()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成Dockerfile内容的函数
|
|
|
|
|
|
def getDockerfileContent(serviceDir) {
|
2025-12-24 17:04:30 +08:00
|
|
|
|
return """FROM eclipse-temurin:21-jdk
|
2025-12-24 17:28:47 +08:00
|
|
|
|
VOLUME /tmp
|
|
|
|
|
|
COPY target/*.jar app.jar
|
|
|
|
|
|
EXPOSE 8080
|
|
|
|
|
|
ENTRYPOINT ["java","-jar","/app.jar"]
|
|
|
|
|
|
"""
|
2025-12-24 14:56:09 +08:00
|
|
|
|
}
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
// 生成docker-compose文件内容的函数
|
|
|
|
|
|
def getComposeFileContent(buildNumber) {
|
|
|
|
|
|
return """version: '3.8'
|
|
|
|
|
|
|
|
|
|
|
|
services:
|
|
|
|
|
|
|
|
|
|
|
|
timeline-story-service:
|
|
|
|
|
|
image: timeline-registry:5000/timeline-story-service:${buildNumber}
|
|
|
|
|
|
container_name: timeline-story-service
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "30001:30001"
|
|
|
|
|
|
environment:
|
|
|
|
|
|
- server.port=30001
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- spring.datasource.url=jdbc:mysql://host.docker.internal:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- spring.datasource.username=root
|
|
|
|
|
|
- spring.datasource.password=WoCloud@9ol7uj
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- spring.data.redis.host=host.docker.internal
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- spring.data.redis.port=6379
|
|
|
|
|
|
- file.service.url=http://timeline-file-service:30002/file/
|
|
|
|
|
|
- user.service.url=http://timeline-user-service:30003/user/
|
2025-12-24 17:17:37 +08:00
|
|
|
|
extra_hosts:
|
|
|
|
|
|
- "host.docker.internal:host-gateway"
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
timeline-file-service:
|
|
|
|
|
|
image: timeline-registry:5000/timeline-file-service:${buildNumber}
|
|
|
|
|
|
container_name: timeline-file-service
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "30002:30002"
|
|
|
|
|
|
environment:
|
|
|
|
|
|
- server.port=30002
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- spring.datasource.url=jdbc:mysql://host.docker.internal:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- spring.datasource.username=root
|
|
|
|
|
|
- spring.datasource.password=WoCloud@9ol7uj
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- minio.endpoint=http://host.docker.internal:9000
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- minio.accessKey=9ttSGjvQxek2uKKlhpqI
|
|
|
|
|
|
- minio.secretKey=12CaKew53tu94tgyDLoqAwAq32iDuz3SWW0O1hex
|
|
|
|
|
|
- minio.bucketName=timeline-test
|
2025-12-24 17:17:37 +08:00
|
|
|
|
extra_hosts:
|
|
|
|
|
|
- "host.docker.internal:host-gateway"
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
timeline-user-service:
|
|
|
|
|
|
image: timeline-registry:5000/timeline-user-service:${buildNumber}
|
|
|
|
|
|
container_name: timeline-user-service
|
|
|
|
|
|
ports:
|
|
|
|
|
|
- "30003:30003"
|
|
|
|
|
|
environment:
|
|
|
|
|
|
- server.port=30003
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- spring.datasource.url=jdbc:mysql://host.docker.internal:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- spring.datasource.username=root
|
|
|
|
|
|
- spring.datasource.password=WoCloud@9ol7uj
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- spring.data.redis.host=host.docker.internal
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- spring.data.redis.port=6379
|
2025-12-24 17:17:37 +08:00
|
|
|
|
extra_hosts:
|
|
|
|
|
|
- "host.docker.internal:host-gateway"
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
timeline-gateway-service:
|
|
|
|
|
|
image: timeline-registry:5000/timeline-gateway-service:${buildNumber}
|
|
|
|
|
|
container_name: timeline-gateway-service
|
|
|
|
|
|
ports:
|
2025-12-24 18:17:14 +08:00
|
|
|
|
- "33333:30000"
|
2025-12-24 15:25:08 +08:00
|
|
|
|
environment:
|
|
|
|
|
|
- server.port=30000
|
|
|
|
|
|
- spring.cloud.gateway.routes[0].id=story-service
|
|
|
|
|
|
- spring.cloud.gateway.routes[0].uri=http://timeline-story-service:30001
|
|
|
|
|
|
- spring.cloud.gateway.routes[0].predicates[0]=Path=/story/**
|
|
|
|
|
|
- spring.cloud.gateway.routes[0].filters[0]=StripPrefix=0
|
|
|
|
|
|
- spring.cloud.gateway.routes[1].id=file-service
|
|
|
|
|
|
- spring.cloud.gateway.routes[1].uri=http://timeline-file-service:30002
|
|
|
|
|
|
- spring.cloud.gateway.routes[1].predicates[0]=Path=/file/**
|
|
|
|
|
|
- spring.cloud.gateway.routes[1].filters[0]=StripPrefix=0
|
|
|
|
|
|
- spring.cloud.gateway.routes[2].id=user-service
|
|
|
|
|
|
- spring.cloud.gateway.routes[2].uri=http://timeline-user-service:30003
|
|
|
|
|
|
- spring.cloud.gateway.routes[2].predicates[0]=Path=/user/**
|
|
|
|
|
|
- spring.cloud.gateway.routes[2].filters[0]=StripPrefix=0
|
|
|
|
|
|
- spring.cloud.gateway.routes[3].id=user-service-ws
|
|
|
|
|
|
- spring.cloud.gateway.routes[3].uri=http://timeline-user-service:30003
|
|
|
|
|
|
- spring.cloud.gateway.routes[3].filters[0]=StripPrefix=0
|
2025-12-24 17:17:37 +08:00
|
|
|
|
- spring.datasource.url=jdbc:mysql://host.docker.internal:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true
|
2025-12-24 15:25:08 +08:00
|
|
|
|
- spring.datasource.username=root
|
|
|
|
|
|
- spring.datasource.password=WoCloud@9ol7uj
|
|
|
|
|
|
depends_on:
|
|
|
|
|
|
- timeline-story-service
|
|
|
|
|
|
- timeline-file-service
|
|
|
|
|
|
- timeline-user-service
|
2025-12-24 17:17:37 +08:00
|
|
|
|
extra_hosts:
|
|
|
|
|
|
- "host.docker.internal:host-gateway"
|
2025-12-24 15:25:08 +08:00
|
|
|
|
|
|
|
|
|
|
"""
|
2025-12-24 16:09:41 +08:00
|
|
|
|
}
|