diff --git a/Jenkinsfile b/Jenkinsfile index b1d2e56..69f2dfb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,20 +1,305 @@ pipeline { agent any + + tools { + maven 'Maven-3.8.6' + jdk 'JDK-21' + } + + 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: '构建分支' + ) + } + stages { + stage('Checkout') { + steps { + checkout scm + script { + echo "当前分支: ${params.BRANCH_NAME}" + } + } + } + stage('Build') { steps { - sh 'echo build' + script { + echo '开始构建项目' + sh 'mvn clean compile -DskipTests' + } } } - stage('Test'){ + + stage('Test') { steps { - sh 'echo test' + script { + echo '运行单元测试' + sh 'mvn test' + } + } + post { + always { + publishTestResults testResultsPattern: 'target/surefire-reports/*.xml' + } } } + + stage('Package') { + steps { + script { + echo '打包项目' + sh 'mvn package -DskipTests' + + // 保存构建产物 + archiveArtifacts artifacts: 'timeline-gateway-service/target/*.jar, timeline-user-service/target/*.jar, timeline-story-service/target/*.jar, timeline-file-service/target/*.jar', fingerprint: true + } + } + } + + stage('Build Docker Images') { + steps { + script { + def services = ['gateway', 'user', 'story', 'file'] + def imageTags = [:] + + 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) + } + + // 构建镜像 + sh "docker build -t ${imageName} -t ${latestImageName} ${serviceDir}/." + imageTags[service] = imageName + } + + env.IMAGE_TAGS = writeJSON returnText: imageTags + } + } + } + + stage('Push Images') { + steps { + script { + def imageTags = readJSON text: env.IMAGE_TAGS + def services = ['gateway', 'user', 'story', 'file'] + + for (service in services) { + def imageName = imageTags[service] + sh "docker push ${imageName}" + + // 也推送latest标签 + def latestImageName = imageName.replace(BUILD_NUMBER, "latest") + sh "docker push ${latestImageName}" + } + } + } + } + stage('Deploy') { steps { - sh 'echo publish' + script { + // 创建或更新docker-compose文件 + def composeContent = getComposeFileContent(BUILD_NUMBER) + writeFile file: 'docker-compose.yml', text: composeContent + + // 拉取最新镜像 + sh 'docker-compose pull' + + // 停止旧容器 + sh 'docker-compose down || true' + + // 启动新容器 + sh 'docker-compose up -d' + + echo "所有服务已部署完成" + } } } } + + post { + success { + script { + sh 'echo "构建和部署成功完成"' + + // 发送成功通知 + emailext ( + subject: "构建成功: ${env.JOB_NAME} - ${env.BUILD_NUMBER}", + body: "构建成功: ${env.BUILD_URL}", + to: "dev-team@example.com" + ) + } + } + failure { + script { + sh 'echo "构建或部署失败"' + + // 发送失败通知 + emailext ( + subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}", + body: "构建失败: ${env.BUILD_URL}", + to: "dev-team@example.com" + ) + } + } + always { + cleanWs() + } + } } + +// 生成Dockerfile内容的函数 +def getDockerfileContent(serviceDir) { + return """FROM openjdk:21-jdk-slim +VOLUME /tmp +COPY target/*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/app.jar"] +""" +} + +// 生成docker-compose文件内容的函数 +def getComposeFileContent(buildNumber) { + return """version: '3.8' + +services: + mysql: + image: mysql:8.0 + container_name: timeline-mysql + ports: + - "33306:33306" + environment: + MYSQL_ROOT_PASSWORD: WoCloud@9ol7uj + MYSQL_DATABASE: timeline + volumes: + - mysql_data:/var/lib/mysql + + redis: + image: redis:7-alpine + container_name: timeline-redis + ports: + - "36379:6379" + command: redis-server --requirepass 123456 + + minio: + image: minio/minio:latest + container_name: timeline-minio + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: 9ttSGjvQxek2uKKlhpqI + MINIO_ROOT_PASSWORD: 12CaKew53tu94tgyDLoqAwAq32iDuz3SWW0O1hex + command: server /data --console-address ":9001" + volumes: + - minio_data:/data + + timeline-story-service: + image: timeline-registry:5000/timeline-story-service:${buildNumber} + container_name: timeline-story-service + ports: + - "30001:30001" + environment: + - server.port=30001 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + - spring.data.redis.host=redis + - spring.data.redis.port=6379 + - spring.data.redis.password=123456 + - file.service.url=http://timeline-file-service:30002/file/ + - user.service.url=http://timeline-user-service:30003/user/ + depends_on: + - mysql + - redis + + timeline-file-service: + image: timeline-registry:5000/timeline-file-service:${buildNumber} + container_name: timeline-file-service + ports: + - "30002:30002" + environment: + - server.port=30002 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + - minio.endpoint=http://minio:9000 + - minio.accessKey=9ttSGjvQxek2uKKlhpqI + - minio.secretKey=12CaKew53tu94tgyDLoqAwAq32iDuz3SWW0O1hex + - minio.bucketName=timeline-test + depends_on: + - mysql + - minio + + timeline-user-service: + image: timeline-registry:5000/timeline-user-service:${buildNumber} + container_name: timeline-user-service + ports: + - "30003:30003" + environment: + - server.port=30003 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + - spring.data.redis.host=redis + - spring.data.redis.port=6379 + - spring.data.redis.password=123456 + depends_on: + - mysql + - redis + + timeline-gateway-service: + image: timeline-registry:5000/timeline-gateway-service:${buildNumber} + container_name: timeline-gateway-service + ports: + - "30000:30000" + 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].predicates[0]=Path=/user/ws/** + - spring.cloud.gateway.routes[3].filters[0]=StripPrefix=0 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + depends_on: + - timeline-story-service + - timeline-file-service + - timeline-user-service + +volumes: + mysql_data: + minio_data: +""" +} \ No newline at end of file diff --git a/deploy.bat b/deploy.bat new file mode 100644 index 0000000..fb7f1ba --- /dev/null +++ b/deploy.bat @@ -0,0 +1,42 @@ +@echo off +setlocal + +echo 开始部署 Timeline Server... + +REM 检查 Docker 是否运行 +docker version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: 未找到 Docker 或 Docker 未运行,请先启动 Docker + exit /b 1 +) + +REM 检查 Docker Compose 是否可用 +docker-compose version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: 未找到 Docker Compose,请先安装 Docker Compose + exit /b 1 +) + +REM 构建项目 +echo 正在构建项目... +call mvnw.cmd clean package -DskipTests + +REM 构建并启动服务 +echo 正在构建并启动服务... +docker-compose up --build -d + +REM 等待服务启动 +echo 等待服务启动... +timeout /t 30 /nobreak >nul + +REM 检查服务状态 +echo 检查服务状态... +docker-compose ps + +echo 部署完成! +echo 网关服务: http://localhost:30000 +echo 故事服务: http://localhost:30001 +echo 文件服务: http://localhost:30002 +echo 用户服务: http://localhost:30003 + +endlocal \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..a4f5e45 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Timeline Server 部署脚本 +set -e + +echo "开始部署 Timeline Server..." + +# 检查是否安装了必要的工具 +if ! command -v docker &> /dev/null; then + echo "错误: 未找到 docker 命令,请先安装 Docker" + exit 1 +fi + +if ! command -v docker-compose &> /dev/null; then + echo "错误: 未找到 docker-compose 命令,请先安装 Docker Compose" + exit 1 +fi + +# 构建项目 +echo "正在构建项目..." +./mvnw clean package -DskipTests + +# 构建并启动服务 +echo "正在构建并启动服务..." +docker-compose up --build -d + +# 等待服务启动 +echo "等待服务启动..." +sleep 30 + +# 检查服务状态 +echo "检查服务状态..." +docker-compose ps + +echo "部署完成!" +echo "网关服务: http://localhost:30000" +echo "故事服务: http://localhost:30001" +echo "文件服务: http://localhost:30002" +echo "用户服务: http://localhost:30003" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ad6c5e3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,128 @@ +version: '3.8' + +services: +# mysql: +# image: mysql:8.0 +# container_name: timeline-mysql +# ports: +# - "33306:33306" +# environment: +# MYSQL_ROOT_PASSWORD: WoCloud@9ol7uj +# MYSQL_DATABASE: timeline +# volumes: +# - ./mysql-init:/docker-entrypoint-initdb.d +# - mysql_data:/var/lib/mysql + + redis: + image: redis:7-alpine + container_name: timeline-redis + ports: + - "36379:6379" + command: redis-server --requirepass 123456 + +# minio: +# image: minio/minio:latest +# container_name: timeline-minio +# ports: +# - "9000:9000" +# - "9001:9001" +# environment: +# MINIO_ROOT_USER: 9ttSGjvQxek2uKKlhpqI +# MINIO_ROOT_PASSWORD: 12CaKew53tu94tgyDLoqAwAq32iDuz3SWW0O1hex +# command: server /data --console-address ":9001" +# volumes: +# - minio_data:/data + + timeline-story-service: + build: + context: ./timeline-story-service + dockerfile: Dockerfile + container_name: timeline-story-service + ports: + - "30001:30001" + environment: + - server.port=30001 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + - spring.data.redis.host=redis + - spring.data.redis.port=6379 + - spring.data.redis.password=123456 + - file.service.url=http://timeline-file-service:30002/file/ + - user.service.url=http://timeline-user-service:30003/user/ + depends_on: + - redis + restart: unless-stopped + + timeline-file-service: + build: + context: ./timeline-file-service + dockerfile: Dockerfile + container_name: timeline-file-service + ports: + - "30002:30002" + environment: + - server.port=30002 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + - minio.endpoint=http://minio:9000 + - minio.accessKey=9ttSGjvQxek2uKKlhpqI + - minio.secretKey=12CaKew53tu94tgyDLoqAwAq32iDuz3SWW0O1hex + - minio.bucketName=timeline-test + restart: unless-stopped + + timeline-user-service: + build: + context: ./timeline-user-service + dockerfile: Dockerfile + container_name: timeline-user-service + ports: + - "30003:30003" + environment: + - server.port=30003 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + - spring.data.redis.host=redis + - spring.data.redis.port=6379 + - spring.data.redis.password=123456 + restart: unless-stopped + + timeline-gateway-service: + build: + context: ./timeline-gateway-service + dockerfile: Dockerfile + container_name: timeline-gateway-service + ports: + - "30000:30000" + 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].predicates[0]=Path=/user/ws/** + - spring.cloud.gateway.routes[3].filters[0]=StripPrefix=0 + - spring.datasource.url=jdbc:mysql://mysql:3306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true + - spring.datasource.username=root + - spring.datasource.password=WoCloud@9ol7uj + depends_on: + - timeline-story-service + - timeline-file-service + - timeline-user-service + restart: unless-stopped + +volumes: + mysql_data: + minio_data: diff --git a/timeline-file-service/Dockerfile b/timeline-file-service/Dockerfile new file mode 100644 index 0000000..8d9bb3d --- /dev/null +++ b/timeline-file-service/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:21-jdk-slim +VOLUME /tmp +COPY target/*.jar app.jar +EXPOSE 30002 +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/timeline-gateway-service/Dockerfile b/timeline-gateway-service/Dockerfile new file mode 100644 index 0000000..7f65755 --- /dev/null +++ b/timeline-gateway-service/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:21-jdk-slim +VOLUME /tmp +COPY target/*.jar app.jar +EXPOSE 30000 +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/timeline-story-service/Dockerfile b/timeline-story-service/Dockerfile new file mode 100644 index 0000000..b32f727 --- /dev/null +++ b/timeline-story-service/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:21-jdk-slim +VOLUME /tmp +COPY target/*.jar app.jar +EXPOSE 30001 +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/timeline-user-service/Dockerfile b/timeline-user-service/Dockerfile new file mode 100644 index 0000000..6861de0 --- /dev/null +++ b/timeline-user-service/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:21-jdk-slim +VOLUME /tmp +COPY target/*.jar app.jar +EXPOSE 30003 +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file