Merge pull request #32 from yyyCode/feat/idempotent-framework #165
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and deploy OpenBlog | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - master | |
| paths-ignore: | |
| - '**/*.md' | |
| jobs: | |
| # 检测变更范围。business / message / gateway 三条链互不影响;共享契约(OpenBlog-api/**)变更会同时触发 business 与 message 双链。 | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| business: ${{ steps.filter.outputs.business }} | |
| message: ${{ steps.filter.outputs.message }} | |
| gateway: ${{ steps.filter.outputs.gateway }} | |
| frontend: ${{ steps.filter.outputs.frontend }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| business: | |
| - 'OpenBlog-business/**' | |
| - 'OpenBlog-framework*/**' | |
| - 'OpenBlog-common/**' | |
| - 'OpenBlog-api/**' | |
| - 'deploy/business/**' | |
| - 'pom.xml' | |
| message: | |
| - 'OpenBlog-message/**' | |
| - 'OpenBlog-common/**' | |
| - 'OpenBlog-api/**' | |
| - 'deploy/message/**' | |
| - 'pom.xml' | |
| gateway: | |
| - 'OpenBlog-gateway/**' | |
| - 'OpenBlog-common/**' | |
| - 'OpenBlog-framework*/**' | |
| - 'deploy/gateway/**' | |
| - 'pom.xml' | |
| frontend: | |
| - 'vue/**' | |
| # ============ business 链(独立) ============ | |
| build-business: | |
| needs: changes | |
| if: ${{ needs.changes.outputs.business == 'true' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: "maven" | |
| - name: Build business (Maven) | |
| run: mvn -B -ntp package -DskipTests -pl OpenBlog-business -am | |
| - name: Upload business JAR | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: business-jar | |
| path: OpenBlog-business/target/OpenBlog-business-*.jar | |
| retention-days: 1 | |
| deploy-business: | |
| needs: build-business | |
| if: ${{ needs.build-business.result == 'success' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: openblog-backend | |
| steps: | |
| - name: Checkout(获取 deploy/business 下的 Docker 部署文件) | |
| uses: actions/checkout@v4 | |
| - name: Download business JAR | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: business-jar | |
| path: business | |
| - name: Deploy via Docker(构建镜像 + 重启容器 + 注入 JWT secret) | |
| env: | |
| OPENBLOG_JWT_SECRET: ${{ secrets.OPENBLOG_JWT_SECRET }} | |
| run: | | |
| set -euo pipefail | |
| TARGET_DIR="/www/wwwroot/java/openblog" | |
| TARGET_JAR="$TARGET_DIR/OpenBlog-business-1.0.0-SNAPSHOT.jar" | |
| # 1. 停掉旧 systemd 服务(释放 8082,避免与容器抢端口) | |
| systemctl stop openblog 2>/dev/null || true | |
| systemctl disable openblog 2>/dev/null || true | |
| # 2. 备份旧 jar | |
| if [ -f "$TARGET_JAR" ]; then | |
| mkdir -p "$TARGET_DIR/backup" | |
| cp "$TARGET_JAR" "$TARGET_DIR/backup/OpenBlog-business-$(date +%Y%m%d-%H%M%S).jar" 2>/dev/null || true | |
| fi | |
| # 3.5 写入 .env(compose 自动加载),JWT secret 与 gateway 同值 | |
| printf 'OPENBLOG_JWT_SECRET=%s\n' "$OPENBLOG_JWT_SECRET" > "$TARGET_DIR/.env" | |
| # 3. 拷贝新 jar + Docker 部署文件 | |
| JAR_FILE=$(ls business/OpenBlog-business-*.jar | head -1) | |
| cp "$JAR_FILE" "$TARGET_JAR" | |
| cp deploy/business/Dockerfile deploy/business/docker-compose.yml deploy/business/.dockerignore "$TARGET_DIR/" | |
| # 4. Docker 构建并启动容器 | |
| cd "$TARGET_DIR" | |
| docker info >/dev/null 2>&1 || { echo "!! Docker 守护进程未运行,请先启动 Docker" >&2; exit 1; } | |
| if docker compose version >/dev/null 2>&1; then | |
| DC="docker compose" | |
| elif docker-compose version >/dev/null 2>&1; then | |
| DC="docker-compose" | |
| else | |
| echo "!! 服务器上没有 docker compose / docker-compose" >&2 | |
| exit 1 | |
| fi | |
| echo "==> 使用: $DC" | |
| $DC up -d --build --remove-orphans | |
| $DC ps | |
| echo "==> Deployed OpenBlog-business via Docker" | |
| # ============ message 链(独立,与 business 互不影响) ============ | |
| build-message: | |
| needs: changes | |
| if: ${{ needs.changes.outputs.message == 'true' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: "maven" | |
| - name: Build message (Maven) | |
| run: mvn -B -ntp package -DskipTests -pl OpenBlog-message -am | |
| - name: Upload message JAR | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: message-jar | |
| path: OpenBlog-message/target/OpenBlog-message-*.jar | |
| retention-days: 1 | |
| deploy-message: | |
| needs: build-message | |
| if: ${{ needs.build-message.result == 'success' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: openblog-backend | |
| steps: | |
| - name: Checkout(获取 deploy/message 下的 Docker 部署文件) | |
| uses: actions/checkout@v4 | |
| - name: Download message JAR | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: message-jar | |
| path: message | |
| - name: Deploy via Docker(构建镜像 + 重启容器 + 注入阿里云凭据) | |
| env: | |
| ALIYUN_AK: ${{ secrets.ALIYUN_AK }} | |
| ALIYUN_SK: ${{ secrets.ALIYUN_SK }} | |
| ALIYUN_FROM: ${{ secrets.ALIYUN_FROM }} | |
| run: | | |
| set -euo pipefail | |
| TARGET_DIR="/www/wwwroot/java/openblog-message" | |
| TARGET_JAR="$TARGET_DIR/OpenBlog-message-1.0.0-SNAPSHOT.jar" | |
| # 1. 停掉旧 宝塔/手动部署的 message 进程(释放 8083/20883;若无则该步 no-op) | |
| systemctl stop openblog-message 2>/dev/null || true | |
| # 2. 备份旧 jar | |
| mkdir -p "$TARGET_DIR" | |
| if [ -f "$TARGET_JAR" ]; then | |
| mkdir -p "$TARGET_DIR/backup" | |
| cp "$TARGET_JAR" "$TARGET_DIR/backup/OpenBlog-message-$(date +%Y%m%d-%H%M%S).jar" 2>/dev/null || true | |
| fi | |
| # 3. 拷贝新 jar + Docker 部署文件 | |
| JAR_FILE=$(ls message/OpenBlog-message-*.jar | head -1) | |
| cp "$JAR_FILE" "$TARGET_JAR" | |
| cp deploy/message/Dockerfile deploy/message/docker-compose.yml deploy/message/.dockerignore "$TARGET_DIR/" | |
| # 4. 阿里云凭据写入服务器 .env(compose 自动加载),供 message 容器读取 | |
| printf 'ALIYUN_AK=%s\nALIYUN_SK=%s\nALIYUN_FROM=%s\n' \ | |
| "$ALIYUN_AK" "$ALIYUN_SK" "$ALIYUN_FROM" \ | |
| > "$TARGET_DIR/.env" | |
| # 5. Docker 构建并启动容器 | |
| cd "$TARGET_DIR" | |
| docker info >/dev/null 2>&1 || { echo "!! Docker 守护进程未运行,请先启动 Docker" >&2; exit 1; } | |
| if docker compose version >/dev/null 2>&1; then | |
| DC="docker compose" | |
| elif docker-compose version >/dev/null 2>&1; then | |
| DC="docker-compose" | |
| else | |
| echo "!! 服务器上没有 docker compose / docker-compose" >&2 | |
| exit 1 | |
| fi | |
| echo "==> 使用: $DC" | |
| $DC up -d --build --remove-orphans | |
| $DC ps | |
| echo "==> Deployed OpenBlog-message via Docker" | |
| # ============ gateway 链(独立,与 business/message 互不影响) ============ | |
| build-gateway: | |
| needs: changes | |
| if: ${{ needs.changes.outputs.gateway == 'true' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: "17" | |
| distribution: "temurin" | |
| cache: "maven" | |
| - name: Build gateway (Maven) | |
| run: mvn -B -ntp package -pl OpenBlog-gateway -am | |
| - name: Upload gateway JAR | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: gateway-jar | |
| path: OpenBlog-gateway/target/OpenBlog-gateway-*.jar | |
| retention-days: 1 | |
| deploy-gateway: | |
| needs: build-gateway | |
| if: ${{ needs.build-gateway.result == 'success' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: openblog-backend | |
| steps: | |
| - name: Checkout(获取 deploy/gateway 下的 Docker 部署文件) | |
| uses: actions/checkout@v4 | |
| - name: Download gateway JAR | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: gateway-jar | |
| path: gateway | |
| - name: Deploy via Docker(构建镜像 + 重启容器 + 注入 JWT secret) | |
| env: | |
| OPENBLOG_JWT_SECRET: ${{ secrets.OPENBLOG_JWT_SECRET }} | |
| run: | | |
| set -euo pipefail | |
| TARGET_DIR="/www/wwwroot/java/openblog-gateway" | |
| TARGET_JAR="$TARGET_DIR/OpenBlog-gateway-1.0.0-SNAPSHOT.jar" | |
| # 1. 停掉旧进程(若有,释放 8090) | |
| systemctl stop openblog-gateway 2>/dev/null || true | |
| # 2. 备份旧 jar | |
| mkdir -p "$TARGET_DIR" | |
| if [ -f "$TARGET_JAR" ]; then | |
| mkdir -p "$TARGET_DIR/backup" | |
| cp "$TARGET_JAR" "$TARGET_DIR/backup/OpenBlog-gateway-$(date +%Y%m%d-%H%M%S).jar" 2>/dev/null || true | |
| fi | |
| # 3. 拷贝新 jar + Docker 部署文件 | |
| JAR_FILE=$(ls gateway/OpenBlog-gateway-*.jar | head -1) | |
| cp "$JAR_FILE" "$TARGET_JAR" | |
| cp deploy/gateway/Dockerfile deploy/gateway/docker-compose.yml deploy/gateway/.dockerignore "$TARGET_DIR/" | |
| # 4. JWT secret 写入服务器 .env(compose 自动加载) | |
| printf 'OPENBLOG_JWT_SECRET=%s\n' "$OPENBLOG_JWT_SECRET" > "$TARGET_DIR/.env" | |
| # 5. Docker 构建并启动容器 | |
| cd "$TARGET_DIR" | |
| docker info >/dev/null 2>&1 || { echo "!! Docker 守护进程未运行,请先启动 Docker" >&2; exit 1; } | |
| if docker compose version >/dev/null 2>&1; then | |
| DC="docker compose" | |
| elif docker-compose version >/dev/null 2>&1; then | |
| DC="docker-compose" | |
| else | |
| echo "!! 服务器上没有 docker compose / docker-compose" >&2 | |
| exit 1 | |
| fi | |
| echo "==> 使用: $DC" | |
| $DC up -d --build --remove-orphans | |
| $DC ps | |
| echo "==> Deployed OpenBlog-gateway via Docker" | |
| # ============ 前端链(原样保留) ============ | |
| build-frontend: | |
| needs: changes | |
| if: ${{ needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: "npm" | |
| cache-dependency-path: vue/package-lock.json | |
| - name: Install frontend dependencies and build | |
| working-directory: vue | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Prepare release bundle for server | |
| run: | | |
| mkdir -p release | |
| cp -r vue/dist release/dist | |
| cp vue/Dockerfile vue/docker.sh vue/nginx.conf release/ | |
| - name: Build Docker image on runner | |
| run: | | |
| docker build -t openblog-web release/ | |
| docker save openblog-web | gzip > release/openblog-web.tar.gz | |
| - name: Upload release bundle | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-frontend | |
| path: release/ | |
| deploy-frontend: | |
| needs: build-frontend | |
| if: ${{ needs.build-frontend.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download release bundle | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-frontend | |
| path: release/ | |
| - name: Deploy files to server | |
| uses: easingthemes/ssh-deploy@v5.1.1 | |
| with: | |
| SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }} | |
| ARGS: "-rlgoDzvc --delete --exclude=dist/.user.ini" | |
| SOURCE: "release/dist release/Dockerfile release/docker.sh release/nginx.conf release/openblog-web.tar.gz" | |
| REMOTE_HOST: ${{ secrets.SERVER_REMOTE_HOST }} | |
| REMOTE_USER: ${{ secrets.SERVER_REMOTE_USER }} | |
| TARGET: ${{ secrets.SERVER_TARGET }} | |
| SCRIPT_BEFORE: ls -la | |
| - name: Run Docker commands on server | |
| uses: appleboy/ssh-action@v1.2.0 | |
| with: | |
| host: ${{ secrets.SERVER_REMOTE_HOST }} | |
| username: ${{ secrets.SERVER_REMOTE_USER }} | |
| key: ${{ secrets.SERVER_SSH_KEY }} | |
| command_timeout: 30m | |
| script: | | |
| cd ${{ secrets.SERVER_TARGET }} | |
| sh docker.sh |