name: Deploy to Production on: push: branches: - main schedule: - cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00 workflow_dispatch: inputs: skip_font_subset: description: 'Skip font subset step' type: boolean default: true concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: TZ: Asia/Shanghai jobs: # ============================================================ # Job 0: Pre-Flight Checks (前置检查) # ============================================================ pre-check: runs-on: ubuntu-latest timeout-minutes: 2 outputs: can_deploy: ${{ steps.repo-check.outputs.can_deploy }} steps: - name: Start timing id: start-pre-check run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 1 - name: Validate repository id: repo-check run: | if [ "${{ github.repository }}" != "zqlit/blog" ]; then echo "❌ 只能在主仓库执行部署" echo "can_deploy=false" >> $GITHUB_OUTPUT exit 1 fi echo "✅ 仓库验证通过" echo "can_deploy=true" >> $GITHUB_OUTPUT - name: Validate commit (only for push events) if: github.event_name == 'push' id: commit-check run: | COMMIT_AUTHOR="${{ github.event.head_commit.author.name }}" COMMIT_MESSAGE="${{ github.event.head_commit.message }}" echo "📝 提交作者: $COMMIT_AUTHOR" echo "📄 提交信息: $COMMIT_MESSAGE" if echo "$COMMIT_MESSAGE" | grep -qE "^(feat|fix|chore|docs|style|refactor|test): "; then echo "✅ 提交信息符合规范" else echo "⚠️ 提交信息格式建议: feat/fix/chore/docs/style/refactor/test: description" fi - name: Summary run: | echo "## 🔍 前置检查完成" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- **仓库**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "- **触发方式**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY echo "- **Commit SHA**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - name: End timing id: end-pre-check run: | end_time=$(date +%s) start_time=${{ steps.start-pre-check.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT echo "## ⏱️ Pre-check 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 1: Font Subset (字体子集化 - 可选并行任务) # ============================================================ subset-fonts: runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: write needs: pre-check if: | needs.pre-check.outputs.can_deploy == 'true' && inputs.skip_font_subset != 'true' && github.event_name == 'workflow_dispatch' outputs: font_updated: ${{ steps.update.outputs.font_updated }} char_count: ${{ steps.stats.outputs.char_count }} original_size: ${{ steps.stats.outputs.original_size }} subset_size: ${{ steps.stats.outputs.subset_size }} steps: - name: Start timing id: start-subset run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install dependencies run: pip install fonttools brotli zopfli - name: Generate font subset id: update run: | # 运行字体子集化脚本 # 会自动扫描: # 1. 静态文件 (content, layouts, public) # 2. 本地 JSON 数据 (友链、朋友圈) # 3. 远程 API (api.usj.cc 的友链、订阅源、朋友圈) python scripts/subset-font-safe.py # 检查是否有变化 git config --global user.name "GitHub Actions" git config --global user.email "actions@github.com" git add themes/Ying/static/font/zql-v2-subset.woff2 themes/Ying/static/font/used_chars.txt if ! git diff --staged --quiet; then git commit -m "chore: update font subset [skip ci]" git push origin HEAD:main echo "font_updated=true" >> $GITHUB_OUTPUT echo "✅ 字体子集已更新并推送" else echo "font_updated=false" >> $GITHUB_OUTPUT echo "ℹ️ 字体子集无变化,跳过提交" fi - name: Collect stats id: stats if: always() run: | CHAR_COUNT=$(wc -m < themes/Ying/static/font/used_chars.txt | tr -d ' ') ORIGINAL_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2.woff2) SUBSET_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2) echo "char_count=$CHAR_COUNT" >> $GITHUB_OUTPUT echo "original_size=$ORIGINAL_SIZE" >> $GITHUB_OUTPUT echo "subset_size=$SUBSET_SIZE" >> $GITHUB_OUTPUT - name: Create font subset summary if: always() run: | ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc) SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc) REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc) PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc) echo "## 🔤 字体子集化" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.update.outputs.font_updated }}" = "true" ]; then echo "✅ **字体子集已更新**" >> $GITHUB_STEP_SUMMARY else echo "ℹ️ **字体子集无变化**" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY echo "### 📊 统计信息" >> $GITHUB_STEP_SUMMARY echo "- **字符数:** ${{ steps.stats.outputs.char_count }}" >> $GITHUB_STEP_SUMMARY echo "- **原始大小:** ${ORIGINAL_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY echo "- **子集大小:** ${SUBSET_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY echo "- **节省空间:** ${REDUCTION_KB} KB (${PERCENTAGE}%)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 🌐 扫描的数据源" >> $GITHUB_STEP_SUMMARY echo "- 静态文件: content, layouts, public" >> $GITHUB_STEP_SUMMARY echo "- 本地 JSON: link_lite.json, friend_circle_data.json" >> $GITHUB_STEP_SUMMARY echo "- 远程 API: api.usj.cc (links, feeds, articles)" >> $GITHUB_STEP_SUMMARY - name: Send font subset notification if: steps.update.outputs.font_updated == 'true' env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} run: | ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc) SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc) REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc) PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc) if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then MESSAGE="✅ **字体子集化完成** 📦 项目: ${{ github.repository }} 🌿 分支: ${{ github.ref_name }} 📊 优化统计: - 字符数: ${{ steps.stats.outputs.char_count }} - 原始大小: ${ORIGINAL_SIZE_KB} KB - 子集大小: ${SUBSET_SIZE_KB} KB - 节省空间: ${REDUCTION_KB} KB (${PERCENTAGE}%) 🌐 数据源: - 静态文件 + 本地JSON + 远程API" curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d chat_id="${TELEGRAM_CHAT_ID}" \ -d text="${MESSAGE}" \ -d parse_mode="Markdown" fi if [ -n "$FEISHU_WEBHOOK_URL" ]; then curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ -H "Content-Type: application/json" \ -d '{ "msg_type": "text", "content": { "text": "✅ **字体子集化完成**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n\n📊 优化统计:\n- 字符数: ${{ steps.stats.outputs.char_count }}\n- 原始大小: '"${ORIGINAL_SIZE_KB}"' KB\n- 子集大小: '"${SUBSET_SIZE_KB}"' KB\n- 节省空间: '"${REDUCTION_KB}"' KB ('"${PERCENTAGE}"'%)\n\n🌐 数据源: 静态文件 + 本地JSON + 远程API" } }' fi - name: End timing id: end-subset run: | end_time=$(date +%s) start_time=${{ steps.start-subset.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT echo "## ⏱️ Subset-fonts 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 2: Build (构建) # ============================================================ build: runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: write outputs: build_hash: ${{ steps.hash.outputs.build_hash }} needs: pre-check if: needs.pre-check.outputs.can_deploy == 'true' steps: - name: Start timing id: start-build run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: '0.128.2' extended: true - name: Setup Node uses: actions/setup-node@v4 with: node-version: '22' cache: 'npm' - name: Install Node.js dependencies run: npm ci - name: Cache Hugo resources uses: actions/cache@v4 with: path: | resources/_gen /home/runner/.cache/hugo_cache key: ${{ runner.os }}-hugo-${{ hashFiles('go.sum', 'package-lock.json', 'hugo.toml', 'config/_default/*.toml') }} restore-keys: | ${{ runner.os }}-hugo- - name: Run Pre-Build Scripts shell: pwsh run: ./scripts/add_draft_to_hidden.ps1 - name: Optimize Images continue-on-error: true timeout-minutes: 5 run: node scripts/optimize_images.js - name: Commit Optimized Images continue-on-error: true run: | git config --global user.name "GitHub Actions" git config --global user.email "actions@github.com" git add content/posts static if ! git diff --staged --quiet; then git commit -m "chore: auto-optimize images [skip ci]" else echo "No changes to commit" fi - name: Build Hugo site timeout-minutes: 10 run: rm -rf public && hugo --minify - name: Generate build hash id: hash run: echo "build_hash=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT - name: Upload build artifact uses: actions/upload-artifact@v4 with: name: hugo-public path: public/ retention-days: 1 compression-level: 0 - name: Push Image Optimizations continue-on-error: true run: | git fetch origin main git stash push -m "temp stash for rebase" || true git rebase origin/main || true git stash pop || true if git log --oneline -1 | grep -q "\[skip ci\]"; then git push origin HEAD:main else echo "No auto-commits to push" fi - name: End timing id: end-build run: | end_time=$(date +%s) start_time=${{ steps.start-build.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT echo "## ⏱️ Build 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 3: Deploy to EdgeOne Pages (并行) # ============================================================ deploy-edgeone: runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read needs: build outputs: deployed: ${{ steps.check.outputs.deployed }} steps: - name: Start timing id: start-edgeone run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Check build hash cache id: cache-check uses: actions/cache/restore@v4 with: path: .last_deploy_hash key: ${{ runner.os }}-deploy-edgeone-${{ needs.build.outputs.build_hash }} restore-keys: | ${{ runner.os }}-deploy-edgeone- - name: Check if deployment is needed id: check run: | CURRENT_HASH="${{ needs.build.outputs.build_hash }}" echo "当前 build_hash: $CURRENT_HASH" if [ -f .last_deploy_hash ]; then LAST_HASH=$(cat .last_deploy_hash) echo "上次部署 hash: $LAST_HASH" if [ "$CURRENT_HASH" = "$LAST_HASH" ]; then echo "✅ 构建产物无变化,跳过 EdgeOne 部署" echo "deployed=false" >> $GITHUB_OUTPUT echo "skip_reason=hash_match" >> $GITHUB_OUTPUT exit 0 fi fi echo "🔄 构建产物有变化,需要部署到 EdgeOne" echo "deployed=true" >> $GITHUB_OUTPUT - name: Checkout code (for scripts) if: steps.check.outputs.deployed == 'true' uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download build artifact if: steps.check.outputs.deployed == 'true' uses: actions/download-artifact@v4 with: name: hugo-public path: public/ - name: Deploy to EdgeOne Pages if: steps.check.outputs.deployed == 'true' env: EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }} run: | chmod +x scripts/deploy_edgeone.sh ./scripts/deploy_edgeone.sh hugo-blog ./public overseas - name: Save deploy hash if: steps.check.outputs.deployed == 'true' run: echo "${{ needs.build.outputs.build_hash }}" > .last_deploy_hash - name: Save build hash cache if: steps.check.outputs.deployed == 'true' uses: actions/cache/save@v4 with: path: .last_deploy_hash key: ${{ runner.os }}-deploy-edgeone-${{ needs.build.outputs.build_hash }} - name: End timing id: end-edgeone run: | end_time=$(date +%s) start_time=${{ steps.start-edgeone.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT if [ "${{ steps.check.outputs.deployed }}" = "true" ]; then echo "## ⏱️ Deploy EdgeOne 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY else echo "## EdgeOne 部署已跳过(构建产物无变化)" >> $GITHUB_STEP_SUMMARY echo "总耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY fi # ============================================================ # Job 4: Deploy to UpYun (并行) # ============================================================ deploy-upyun: runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read needs: build outputs: deployed: ${{ steps.check.outputs.deployed }} steps: - name: Start timing id: start-upyun run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Check build hash cache id: cache-check uses: actions/cache/restore@v4 with: path: .last_deploy_hash key: ${{ runner.os }}-deploy-upyun-${{ needs.build.outputs.build_hash }} restore-keys: | ${{ runner.os }}-deploy-upyun- - name: Check if deployment is needed id: check run: | CURRENT_HASH="${{ needs.build.outputs.build_hash }}" echo "当前 build_hash: $CURRENT_HASH" if [ -f .last_deploy_hash ]; then LAST_HASH=$(cat .last_deploy_hash) echo "上次部署 hash: $LAST_HASH" if [ "$CURRENT_HASH" = "$LAST_HASH" ]; then echo "✅ 构建产物无变化,跳过 UpYun 部署" echo "deployed=false" >> $GITHUB_OUTPUT echo "skip_reason=hash_match" >> $GITHUB_OUTPUT exit 0 fi fi echo "🔄 构建产物有变化,需要部署到 UpYun" echo "deployed=true" >> $GITHUB_OUTPUT - name: Start artifact download timing if: steps.check.outputs.deployed == 'true' id: start-upyun-download run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Checkout code (for scripts) if: steps.check.outputs.deployed == 'true' uses: actions/checkout@v4 with: fetch-depth: 1 - name: Download build artifact if: steps.check.outputs.deployed == 'true' uses: actions/download-artifact@v4 with: name: hugo-public path: public/ - name: End artifact download timing if: steps.check.outputs.deployed == 'true' id: end-upyun-download run: | end_time=$(date +%s) start_time=${{ steps.start-upyun-download.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT echo "## UpYun artifact download duration: ${duration}s" >> $GITHUB_STEP_SUMMARY - name: Start UpYun upload timing if: steps.check.outputs.deployed == 'true' id: start-upyun-upload run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Deploy to UpYun if: steps.check.outputs.deployed == 'true' env: UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }} UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }} UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }} run: | chmod +x scripts/deploy_upyun.sh ./scripts/deploy_upyun.sh ./public / 20 - name: Save deploy hash if: steps.check.outputs.deployed == 'true' run: echo "${{ needs.build.outputs.build_hash }}" > .last_deploy_hash - name: Save build hash cache if: steps.check.outputs.deployed == 'true' uses: actions/cache/save@v4 with: path: .last_deploy_hash key: ${{ runner.os }}-deploy-upyun-${{ needs.build.outputs.build_hash }} - name: End timing id: end-upyun run: | end_time=$(date +%s) total_start_time=${{ steps.start-upyun.outputs.start_time }} total_duration=$((end_time - total_start_time)) echo "duration=${total_duration}" >> $GITHUB_OUTPUT if [ "${{ steps.check.outputs.deployed }}" = "true" ]; then upload_start_time=${{ steps.start-upyun-upload.outputs.start_time }} upload_duration=$((end_time - upload_start_time)) echo "upload_duration=${upload_duration}" >> $GITHUB_OUTPUT echo "## Deploy UpYun total duration: ${total_duration}s" >> $GITHUB_STEP_SUMMARY echo "## UpYun upload duration: ${upload_duration}s" >> $GITHUB_STEP_SUMMARY else echo "## UpYun 部署已跳过(构建产物无变化)" >> $GITHUB_STEP_SUMMARY echo "总耗时: ${total_duration}s" >> $GITHUB_STEP_SUMMARY fi # ============================================================ # Job 5: CDN Refresh & Notifications (after all deployments) # ============================================================ finalize: runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read needs: [pre-check, subset-fonts, build, deploy-edgeone, deploy-upyun] if: always() && needs.build.result == 'success' && needs.deploy-edgeone.result == 'success' && needs.deploy-upyun.result == 'success' steps: - name: Start timing id: start-finalize run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v4 with: node-version: '22' cache: 'npm' - name: Install Node.js dependencies run: npm ci - name: Determine deployment status id: deploy-status run: | EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" UPYUN_DEPLOYED="${{ needs.deploy-upyun.outputs.deployed }}" echo "EdgeOne deployed: $EDGEONE_DEPLOYED" echo "UpYun deployed: $UPYUN_DEPLOYED" if [ "$EDGEONE_DEPLOYED" = "true" ] || [ "$UPYUN_DEPLOYED" = "true" ]; then echo "any_deployed=true" >> $GITHUB_OUTPUT else echo "any_deployed=false" >> $GITHUB_OUTPUT fi - name: Refresh DogeCloud CDN if: steps.deploy-status.outputs.any_deployed == 'true' env: DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} CDN_URL_LIST: ${{ secrets.CDN_URL_LIST }} run: node scripts/refresh_cdn.js - name: Generate Notification Report if: steps.deploy-status.outputs.any_deployed == 'true' continue-on-error: true run: node scripts/generate_notification_report.js - name: Send Telegram notification env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then echo "⚠️ Telegram secrets 未配置,跳过通知" exit 0 fi EDGEONE_STATUS="${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" UPYUN_STATUS="${{ needs.deploy-upyun.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" CDN_STATUS="${{ steps.deploy-status.outputs.any_deployed == 'true' && '✅' || '⏭️ 跳过' }}" if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then TITLE="✅ **部署成功**" else TITLE="ℹ️ **无变化,跳过部署**" fi MESSAGE="${TITLE} 📦 项目: ${{ github.repository }} 🌿 分支: ${{ github.ref_name }} 🔗 Commit: ${{ github.sha }} 🕐 时间: $(date +'%Y-%m-%d %H:%M:%S') 📊 部署目标: - EdgeOne Pages ${EDGEONE_STATUS} - UpYun ${UPYUN_STATUS} - CDN 刷新 ${CDN_STATUS}" curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d chat_id="${TELEGRAM_CHAT_ID}" \ -d text="${MESSAGE}" \ -d parse_mode="Markdown" - name: Send Feishu notification env: FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} run: | if [ -z "$FEISHU_WEBHOOK_URL" ]; then echo "⚠️ 飞书 Webhook URL 未配置,跳过通知" exit 0 fi EDGEONE_STATUS="${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" UPYUN_STATUS="${{ needs.deploy-upyun.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" CDN_STATUS="${{ steps.deploy-status.outputs.any_deployed == 'true' && '✅' || '⏭️ 跳过' }}" if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then TITLE="✅ **部署成功**" else TITLE="ℹ️ **无变化,跳过部署**" fi CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ -H "Content-Type: application/json" \ -d '{ "msg_type": "text", "content": { "text": "'"${TITLE}"'\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '"${CURRENT_TIME}"'\n\n📊 部署目标:\n- EdgeOne Pages '"${EDGEONE_STATUS}"'\n- UpYun '"${UPYUN_STATUS}"'\n- CDN 刷新 '"${CDN_STATUS}"'" } }' - name: Get current date (CN) if: steps.deploy-status.outputs.any_deployed == 'true' id: date run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - name: Send deployment notification if: steps.deploy-status.outputs.any_deployed == 'true' uses: dawidd6/action-send-mail@v3 with: server_address: smtp.qq.com server_port: 465 secure: true username: ${{ secrets.MAIL_USERNAME }} password: ${{ secrets.MAIL_PASSWORD }} subject: 部署报告 - ${{ steps.date.outputs.date }} to: ${{ secrets.MAIL_USERNAME }} from: GitHub Actions <${{ github.event_name }}> body: | ${{ env.DEPLOY_REPORT }} - name: Generate timing summary id: timing-summary run: | end_time=$(date +%s) start_time=${{ steps.start-finalize.outputs.start_time }} finalize_duration=$((end_time - start_time)) EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" UPYUN_DEPLOYED="${{ needs.deploy-upyun.outputs.deployed }}" if [ "$EDGEONE_DEPLOYED" = "true" ]; then EDGEONE_DISPLAY="✅ 已部署" else EDGEONE_DISPLAY="⏭️ 跳过(无变化)" fi if [ "$UPYUN_DEPLOYED" = "true" ]; then UPYUN_DISPLAY="✅ 已部署" else UPYUN_DISPLAY="⏭️ 跳过(无变化)" fi echo "## ⏱️ 部署耗时统计" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| 阶段 | 状态 |" >> $GITHUB_STEP_SUMMARY echo "|------|------|" >> $GITHUB_STEP_SUMMARY echo "| Pre-check | ${{ needs.pre-check.result == 'success' && '✅ 成功' || needs.pre-check.result == 'skipped' && '⏭️ 跳过' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY echo "| Subset-fonts | ${{ needs.subset-fonts.result == 'success' && '✅ 成功' || needs.subset-fonts.result == 'skipped' && '⏭️ 跳过' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY echo "| Build | ${{ needs.build.result == 'success' && '✅ 成功' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY echo "| Deploy EdgeOne | ${EDGEONE_DISPLAY} |" >> $GITHUB_STEP_SUMMARY echo "| Deploy UpYun | ${UPYUN_DISPLAY} |" >> $GITHUB_STEP_SUMMARY echo "| Finalize | ✅ 成功 (${finalize_duration}秒) |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Finalize 阶段耗时:** ${finalize_duration}秒" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 6: Failure Notification (失败通知) # ============================================================ notify-failure: runs-on: ubuntu-latest timeout-minutes: 3 permissions: contents: read needs: [pre-check, subset-fonts, build, deploy-edgeone, deploy-upyun, finalize] if: always() && (needs.pre-check.result == 'failure' || needs.build.result == 'failure' || needs.deploy-edgeone.result == 'failure' || needs.deploy-upyun.result == 'failure' || needs.finalize.result == 'failure') steps: - name: Send Telegram failure notification env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then echo "⚠️ Telegram secrets 未配置,跳过失败通知" exit 0 fi MESSAGE="❌ **部署失败** 📦 项目: ${{ github.repository }} 🌿 分支: ${{ github.ref_name }} 🔗 Commit: ${{ github.sha }} 🕐 时间: $(date +'%Y-%m-%d %H:%M:%S') 📝 工作流: ${{ github.workflow }} 🔍 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d chat_id="${TELEGRAM_CHAT_ID}" \ -d text="${MESSAGE}" \ -d parse_mode="Markdown" - name: Send Feishu failure notification env: FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} run: | if [ -z "$FEISHU_WEBHOOK_URL" ]; then echo "⚠️ 飞书 Webhook URL 未配置,跳过失败通知" exit 0 fi CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ -H "Content-Type: application/json" \ -d '{ "msg_type": "text", "content": { "text": "❌ **部署失败**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '"${CURRENT_TIME}"'\n\n📝 工作流: ${{ github.workflow }}\n🔍 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" } }'