From 780e85bde4d03de08c659ead6832e73017fe1e7e Mon Sep 17 00:00:00 2001 From: Lxy Date: Mon, 29 Jun 2026 00:15:11 +0800 Subject: [PATCH] ci: add GitHub Actions CI/CD workflows and deploy scripts --- .github/workflows/backend-ci.yml | 93 ++++++++++++++++++++++ .github/workflows/cicd.yml | 125 ++++++++++++++++++++++++++++++ .github/workflows/frontend-ci.yml | 56 +++++++++++++ Dockerfile | 13 ++++ docker-compose.yml | 48 ++++++++++++ scripts/deploy/backend-deploy.sh | 101 ++++++++++++++++++++++++ scripts/deploy/frontend-deploy.sh | 54 +++++++++++++ scripts/deploy/nginx.conf | 47 +++++++++++ 8 files changed, 537 insertions(+) create mode 100644 .github/workflows/backend-ci.yml create mode 100644 .github/workflows/cicd.yml create mode 100644 .github/workflows/frontend-ci.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 scripts/deploy/backend-deploy.sh create mode 100644 scripts/deploy/frontend-deploy.sh create mode 100644 scripts/deploy/nginx.conf diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml new file mode 100644 index 0000000..26dfeac --- /dev/null +++ b/.github/workflows/backend-ci.yml @@ -0,0 +1,93 @@ +name: Backend CI + +on: + push: + branches: [ main, develop, feature/** ] + paths: + - 'pom.xml' + - 'ruoyi-*/**' + - 'book-system/**' + pull_request: + branches: [ main, develop ] + paths: + - 'pom.xml' + - 'ruoyi-*/**' + - 'book-system/**' + +jobs: + build: + runs-on: ubuntu-latest + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: ry-vue + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + redis: + image: redis:7 + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Build with Maven + run: mvn clean compile -DskipTests + + - name: Run Checkstyle + run: mvn checkstyle:checkstyle + continue-on-error: true + + - name: Run tests + run: mvn test + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: '**/surefire-reports/*.xml' + + - name: Upload coverage reports + uses: codecov/codecov-action@v3 + with: + files: '**/jacoco.xml' + fail_ci_if_error: false + + - name: Package application + run: mvn package -DskipTests + + - name: Upload JAR + uses: actions/upload-artifact@v4 + with: + name: ruoyi-admin + path: ruoyi-admin/target/*.jar diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml new file mode 100644 index 0000000..e7f6de3 --- /dev/null +++ b/.github/workflows/cicd.yml @@ -0,0 +1,125 @@ +name: Full CI/CD + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + # 后端构建 + backend: + runs-on: ubuntu-latest + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: ry-vue + ports: + - 3306:3306 + + redis: + image: redis:7 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + + - name: Build and test + run: mvn clean verify + + - name: Package + run: mvn package -DskipTests + + - name: Upload JAR + uses: actions/upload-artifact@v4 + with: + name: backend-jar + path: ruoyi-admin/target/*.jar + + # 前端构建 + frontend: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ruoyi-ui-next + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + cache-dependency-path: ruoyi-ui-next/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build + + - name: Upload dist + uses: actions/upload-artifact@v4 + with: + name: frontend-dist + path: ruoyi-ui-next/dist/ + + # 集成测试 + integration-test: + needs: [backend, frontend] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Download backend JAR + uses: actions/download-artifact@v4 + with: + name: backend-jar + + - name: Download frontend dist + uses: actions/download-artifact@v4 + with: + name: frontend-dist + + - name: Run integration tests + run: | + echo "Integration tests would run here" + echo "Backend JAR and Frontend dist are available" + + # 部署(仅 main 分支) + deploy: + needs: [backend, frontend, integration-test] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Deploy to server + run: | + echo "Deploy scripts would run here" + echo "In production, use SSH or deployment tools" + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 0000000..68c51a6 --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,56 @@ +name: Frontend CI + +on: + push: + branches: [ main, develop, feature/** ] + paths: + - 'ruoyi-ui-next/**' + pull_request: + branches: [ main, develop ] + paths: + - 'ruoyi-ui-next/**' + +jobs: + build: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ruoyi-ui-next + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + cache-dependency-path: ruoyi-ui-next/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + continue-on-error: true + + - name: Type check + run: npx vue-tsc --noEmit + continue-on-error: true + + - name: Build + run: npm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: frontend-dist + path: ruoyi-ui-next/dist/ + + - name: Upload coverage reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: ruoyi-ui-next/coverage/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3c85b32 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM eclipse-temurin:11-jre + +LABEL maintainer="ruoyi" + +WORKDIR /app + +COPY ruoyi-admin/target/*.jar app.jar + +EXPOSE 8080 + +ENV JAVA_OPTS="-Xms512m -Xmx1024m -Dspring.profiles.active=prod" + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f7c3fb9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.8' + +services: + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: ry-vue + ports: + - "3306:3306" + volumes: + - mysql-data:/var/lib/mysql + - ./sql:/docker-entrypoint-initdb.d + command: --default-authentication-plugin=mysql_native_password + + redis: + image: redis:7 + ports: + - "6379:6379" + volumes: + - redis-data:/data + + backend: + build: . + ports: + - "8080:8080" + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + SPRING_DATASOURCE_USERNAME: root + SPRING_DATASOURCE_PASSWORD: root + SPRING_REDIS_HOST: redis + depends_on: + - mysql + - redis + + frontend: + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./ruoyi-ui-next/dist:/usr/share/nginx/html + - ./scripts/deploy/nginx.conf:/etc/nginx/conf.d/default.conf + depends_on: + - backend + +volumes: + mysql-data: + redis-data: diff --git a/scripts/deploy/backend-deploy.sh b/scripts/deploy/backend-deploy.sh new file mode 100644 index 0000000..b0fc946 --- /dev/null +++ b/scripts/deploy/backend-deploy.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# ============================================ +# RuoYi-Vue 后端部署脚本 +# 使用方式: ./scripts/deploy/backend-deploy.sh +# ============================================ + +set -e + +# 配置 +APP_NAME="ruoyi-admin" +DEPLOY_DIR="/opt/ruoyi" +JAR_FILE="${DEPLOY_DIR}/${APP_NAME}.jar" +BACKUP_DIR="${DEPLOY_DIR}/backup" +LOG_DIR="${DEPLOY_DIR}/logs" +PID_FILE="${DEPLOY_DIR}/${APP_NAME}.pid" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +echo_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +echo_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查 JAR 文件 +if [ ! -f "$1" ]; then + echo_error "JAR 文件不存在: $1" + exit 1 +fi + +echo_info "========================================" +echo_info "RuoYi-Vue 后端部署" +echo_info "========================================" + +# 创建目录 +mkdir -p $DEPLOY_DIR $BACKUP_DIR $LOG_DIR + +# 备份现有版本 +if [ -f "$JAR_FILE" ]; then + echo_info "备份现有版本..." + cp $JAR_FILE ${BACKUP_DIR}/${APP_NAME}-$(date +%Y%m%d_%H%M%S).jar +fi + +# 停止现有服务 +if [ -f "$PID_FILE" ]; then + PID=$(cat $PID_FILE) + if ps -p $PID > /dev/null 2>&1; then + echo_info "停止现有服务 (PID: $PID)..." + kill $PID + sleep 5 + + # 强制停止 + if ps -p $PID > /dev/null 2>&1; then + echo_warn "服务未正常停止,强制终止..." + kill -9 $PID + fi + fi +fi + +# 部署新版本 +echo_info "部署新版本..." +cp $1 $JAR_FILE + +# 启动服务 +echo_info "启动服务..." +nohup java -jar \ + -Dspring.profiles.active=prod \ + -Xms512m -Xmx1024m \ + $JAR_FILE \ + > ${LOG_DIR}/app.log 2>&1 & + +echo $! > $PID_FILE +echo_info "服务已启动 (PID: $(cat $PID_FILE))" + +# 等待启动 +echo_info "等待服务启动..." +sleep 10 + +# 健康检查 +if curl -f http://localhost:8080/actuator/health > /dev/null 2>&1; then + echo_info "✅ 部署成功!服务运行正常" +else + echo_error "❌ 部署失败!服务未正常启动" + echo_error "请查看日志: tail -f ${LOG_DIR}/app.log" + exit 1 +fi + +echo_info "========================================" +echo_info "部署完成" +echo_info "日志: ${LOG_DIR}/app.log" +echo_info "========================================" diff --git a/scripts/deploy/frontend-deploy.sh b/scripts/deploy/frontend-deploy.sh new file mode 100644 index 0000000..7936b96 --- /dev/null +++ b/scripts/deploy/frontend-deploy.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# ============================================ +# RuoYi-Vue 前端部署脚本 +# 使用方式: ./scripts/deploy/frontend-deploy.sh +# ============================================ + +set -e + +# 配置 +DEPLOY_DIR="/usr/share/nginx/html/ruoyi-ui" +BACKUP_DIR="/usr/share/nginx/html/backup" +NGINX_CONF="/etc/nginx/conf.d/ruoyi-ui.conf" + +echo_info() { + echo -e "\033[0;32m[INFO]\033[0m $1" +} + +echo_error() { + echo -e "\033[0;31m[ERROR]\033[0m $1" +} + +if [ ! -d "$1" ]; then + echo_error "构建目录不存在: $1" + exit 1 +fi + +echo_info "========================================" +echo_info "RuoYi-Vue 前端部署" +echo_info "========================================" + +# 创建目录 +mkdir -p $DEPLOY_DIR $BACKUP_DIR + +# 备份现有版本 +if [ -d "$DEPLOY_DIR" ] && [ "$(ls -A $DEPLOY_DIR)" ]; then + echo_info "备份现有版本..." + tar -czf ${BACKUP_DIR}/frontend-$(date +%Y%m%d_%H%M%S).tar.gz -C $(dirname $DEPLOY_DIR) $(basename $DEPLOY_DIR) +fi + +# 部署新版本 +echo_info "部署新版本..." +rm -rf ${DEPLOY_DIR}/* +cp -r $1/* $DEPLOY_DIR/ + +# 设置权限 +chown -R nginx:nginx $DEPLOY_DIR +chmod -R 755 $DEPLOY_DIR + +# 重新加载 Nginx +echo_info "重新加载 Nginx..." +nginx -t && systemctl reload nginx + +echo_info "✅ 部署成功!" +echo_info "========================================" diff --git a/scripts/deploy/nginx.conf b/scripts/deploy/nginx.conf new file mode 100644 index 0000000..0e132c6 --- /dev/null +++ b/scripts/deploy/nginx.conf @@ -0,0 +1,47 @@ +server { + listen 80; + server_name your-domain.com; + + # 前端静态文件 + location / { + root /usr/share/nginx/html/ruoyi-ui; + index index.html; + try_files $uri $uri/ /index.html; + + # 缓存策略 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + } + + # 后端 API 代理 + location /dev-api/ { + proxy_pass http://localhost:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时设置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 后端 API 代理(生产环境) + location /prod-api/ { + proxy_pass http://localhost:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; +}