name: Deploy to Production on: push: branches: - main schedule: - cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00 workflow_dispatch: 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.decide.outputs.can_deploy }} duration: ${{ steps.end-pre-check.outputs.duration }} 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 # 注意:commit message 是用户可控的多行文本,必须经 env 传入。 # 直接写 ${{ github.event.head_commit.message }} 会把内容展开进脚本, # 多行信息或含 $ / ` 的字符会导致 "bad substitution" 甚至命令注入。 env: COMMIT_AUTHOR: ${{ github.event.head_commit.author.name }} COMMIT_MESSAGE: ${{ github.event.head_commit.message }} run: | 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: Decide whether to deploy id: decide run: | # 基础决策来自仓库校验 CAN_DEPLOY="${{ steps.repo-check.outputs.can_deploy }}" echo "📌 仓库校验结果: can_deploy=${CAN_DEPLOY}" # 定时触发:若 1 小时内无新提交则跳过,避免重复构建 if [ "${{ github.event_name }}" = "schedule" ]; then LAST_COMMIT_TIME=$(git log -1 --format=%ct) CURRENT_TIME=$(date +%s) DIFF_HOURS=$(( (CURRENT_TIME - LAST_COMMIT_TIME) / 3600 )) echo "⏰ 最后一次提交时间: $(date -d @$LAST_COMMIT_TIME)" echo "⏰ 当前时间: $(date)" echo "⏰ 距离上次提交: ${DIFF_HOURS} 小时" if [ "$DIFF_HOURS" -ge 1 ]; then echo "⏭️ 超过 1 小时没有新提交,跳过定时构建" CAN_DEPLOY="false" else echo "✅ 最近有提交,继续构建" fi fi echo "can_deploy=${CAN_DEPLOY}" >> $GITHUB_OUTPUT echo "## 🚦 部署决策: can_deploy=${CAN_DEPLOY}" >> $GITHUB_STEP_SUMMARY - 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: Build (构建) # ============================================================ build: runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: write outputs: build_hash: ${{ steps.hash.outputs.build_hash }} duration: ${{ steps.end-build.outputs.duration }} file_count: ${{ steps.stats.outputs.file_count }} total_size: ${{ steps.stats.outputs.total_size }} # Upload to UpYun 的真实三态:success / failure / skipped(未执行)。 # 该步骤标了 continue-on-error,conclusion 恒为 success,不能用来判断成败。 # 这里刻意不用 steps..outcome,而是让步骤自己在退出时写 GITHUB_OUTPUT, # 只依赖最通用的 output 传递机制,GitHub / Gitea 两边都稳。 upyun_status: ${{ steps.upload.outputs.upyun_status || 'skipped' }} upyun_duration: ${{ steps.upload.outputs.duration }} 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 # Hugo 的 lastmod 用了 :git,必须保留完整提交历史(commit/tree), # 但历史版本的文件内容(blob,多为图片)不需要。 # partial clone 只按需拉取当前工作区的 blob,实测本仓库可省约 200MB 传输。 filter: blob:none - 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' # Gitea 的 runner 缓存服务与 job 容器跨 bridge 网络不可达, # 每次 restore/save 各死等约 4 分半后 ETIMEDOUT(纯浪费约 9 分钟), # 因此仅在 GitHub 上启用缓存;Gitea 上传空值让 setup-node 跳过缓存。 cache: ${{ github.server_url == 'https://github.com' && 'npm' || '' }} - name: Install Node.js dependencies run: npm ci --no-audit --no-fund - name: Cache Hugo resources # 同 Setup Node:Gitea 环境缓存服务不可达,跳过以免每次白等 if: github.server_url == 'https://github.com' uses: actions/cache@v4 id: hugo-cache 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: bash run: python3 scripts/add_draft_to_hidden.py - 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 resources/_gen && hugo --minify --gc - name: Generate build hash id: hash run: | BUILD_HASH=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}') echo "build_hash=${BUILD_HASH}" >> $GITHUB_OUTPUT # 写两份: # .build_hash —— 历史沿用(也用于 upx put 到又拍云 /__build_hash) # build_hash.txt —— 非隐藏。upload-artifact(v3/v4)对「以 . 开头的隐藏 # 文件」处理不一致,可能整个丢掉,非隐藏文件才稳。 # 中转机优先读 .build_hash,读不到退到 build_hash.txt,都没有才本地复算。 # 注意:本步的 find 在写这两个文件之前执行,所以 hash 不含它们自身。 echo "$BUILD_HASH" > public/.build_hash echo "$BUILD_HASH" > public/build_hash.txt - name: Build statistics id: stats run: | FILE_COUNT=$(find public -type f | wc -l) TOTAL_SIZE=$(du -sh public | awk '{print $1}') echo "file_count=${FILE_COUNT}" >> $GITHUB_OUTPUT echo "total_size=${TOTAL_SIZE}" >> $GITHUB_OUTPUT - name: Upload build artifact (public/) # 2026-10-04 起产物不再上传腾讯云 COS,改用 Gitea artifact 传递: # - deploy-edgeone 用 actions/download-artifact 直接拿(同 runner,零跨境) # - 国内中转机用 Gitea API 下载 artifact zip(替代原先的「从 COS 下载」) # # ★ 必须用 v3,不能用 v4:实测 v4 在 Gitea 上直接失败 —— # "@actions/artifact v2.0.0+, upload-artifact@v4+ and download-artifact@v4+ # are not currently supported on GHES."(Gitea 被识别为 GHES,未实现新版 # artifact 服务)。v3 走的是 Gitea 已实现的旧 artifact API。 # v3 也没有 include-hidden-files 选项;隐藏文件能不能进 artifact 不可靠, # 所以 hash 另外写了一份非隐藏的 public/build_hash.txt 兜底。 uses: actions/upload-artifact@v3 with: name: hugo-public path: public/ retention-days: 7 - name: Report deploy status (built) # 上报「构建完成、产物已进 artifact」到 api.200181.xyz 的部署状态接口, # 中转机同步完成后会上报 phase=synced —— 打开 # https://api.200181.xyz/api/deploy-status 即可看到国内线路是否同步完。 # 需要 Gitea 仓库 Secrets 里配置 RSS_API_TOKEN = rss-robot-cf 的站点口令; # 未配置时静默跳过上报(不影响部署)。 continue-on-error: true env: REPORT_TOKEN: ${{ secrets.RSS_API_TOKEN }} run: | if [ -z "$REPORT_TOKEN" ]; then echo "ℹ️ 未配置 RSS_API_TOKEN,跳过部署状态上报" exit 0 fi BUILD_HASH="${{ steps.hash.outputs.build_hash }}" curl -s --max-time 20 -X POST \ "https://api.200181.xyz/api/deploy-status?token=${REPORT_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"hash\":\"${BUILD_HASH}\",\"phase\":\"built\"}" \ && echo "✅ 已上报 built: ${BUILD_HASH:0:12}" - name: Upload to UpYun # ┌─────────────────────────────────────────────────────────────────┐ # │ 2026-10-03 起永久跳过海外直传:本 runner 在境外,对 UpYun v0 接口 │ # │ 无论 -w 10/--strong 还是 -w 3 均为 70 秒左右 EOF,3 轮重试全败, │ # │ 每次部署白耗 5 分钟(2026-10-03 build #25 日志实锤)。 │ # │ 国内线路由腾讯云广州中转机兜底(1Panel 计划任务「又拍云同步(国内 │ # │ 中转)」每分钟比对 COS build hash → 下载 355M → upx sync → │ # │ 又拍云 purge → 多吉云刷新,实测 5 分钟内完成且它自己会刷 CDN)。 │ # │ 将来若把 runner 挪到国内,把下面的 if: false 删掉即可恢复。 │ # └─────────────────────────────────────────────────────────────────┘ if: false timeout-minutes: 30 env: UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }} UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }} UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }} id: upload # 直传失败不阻断整条流水线 —— 国内线路已由腾讯云广州的中转机兜底 # (1Panel 计划任务「又拍云同步(国内中转)」每分钟比对 COS 上的 build hash, # 有变化才增量推送)。但失败必须被如实上报,绝不能再伪装成成功。 continue-on-error: true run: | echo "📤 上传构建产物到 UpYun..." START_TS=$(date +%s) # 无论成功、失败还是中途退出,都在退出时如实上报结果与耗时。 # 这是"不再假绿"的核心:过去无论传没传上去,通知都显示成功。 on_exit() { rc=$? if [ "$rc" -eq 0 ]; then echo "upyun_status=success" >> $GITHUB_OUTPUT else echo "upyun_status=failure" >> $GITHUB_OUTPUT fi echo "duration=$(($(date +%s) - START_TS))" >> $GITHUB_OUTPUT } trap on_exit EXIT UPX_VERSION="0.4.9" UPX_BIN="/tmp/upyun-upx" # 主源用 GitHub Releases(实测 0.5s 稳定), # 官方 collection.b0.upaiyun.com 实测 2/3 概率整站无响应,仅作兜底 UPX_URLS=( "https://github.com/upyun/upx/releases/download/v${UPX_VERSION}/upx_${UPX_VERSION}_linux_amd64.tar.gz" "https://collection.b0.upaiyun.com/softwares/upx/upx_${UPX_VERSION}_linux_amd64.tar.gz" ) if [ ! -x "$UPX_BIN" ]; then ok=false for U in "${UPX_URLS[@]}"; do echo "⬇️ 下载 upx: $U" if curl -fsSL --connect-timeout 10 --max-time 60 --retry 2 --retry-delay 2 \ -o /tmp/upx.tar.gz "$U"; then if tar -xzf /tmp/upx.tar.gz -C /tmp && [ -f /tmp/upx ]; then mv /tmp/upx "$UPX_BIN" && chmod +x "$UPX_BIN" && ok=true echo "✅ upx 就绪: $("$UPX_BIN" --version 2>&1 | head -1)" break fi fi echo "⚠️ 该源失败,尝试下一个..." done if [ "$ok" != "true" ]; then echo "❌ 所有 upx 下载源均失败,跳过 UpYun 上传" exit 1 fi fi for i in 1 2 3 4 5; do echo "🔐 UpYun 登录 (尝试 $i/5)..." if timeout 30 "$UPX_BIN" login "$UPYUN_SERVICE" "$UPYUN_OPERATOR" "$UPYUN_PASSWORD" 2>&1; then echo "✅ 登录成功" break else echo "⚠️ 登录失败,等待 10 秒后重试..." sleep 10 fi done echo "🔄 开始上传到 UpYun..." # ⚠️ 参数是踩过坑调出来的,别改回 -w 10 / --strong: # 本 runner 在海外,UpYun 的 v0 接口对并发敏感。2026-10-02 连续两次失败, # 现象是一批目录的 mkdir 全部 EOF: # sync .../public/20210212 → /20210212 FAIL mkdir: Post "https://v0.api.upyun.com/...": EOF # -w 10 + --strong(每文件强校验)把请求量放大到 UpYun 扛不住的量级。 # 现在:并发 3、去掉 --strong(默认按 mtime/size 增量比对)、 # 单次超时放宽到 25 分钟、整轮重试 3 次(sync 可重复执行,已传的会跳过)。 UPYUN_OK=false for attempt in 1 2 3; do echo "⬆️ 上传尝试 $attempt/3(并发 3,无 --strong)" if timeout 1500 "$UPX_BIN" sync public/ / -w 3 --delete; then UPYUN_OK=true break fi echo "⚠️ 第 $attempt/3 次失败(多为 EOF 瞬断),20 秒后重试" sleep 20 done if [ "$UPYUN_OK" != "true" ]; then echo "❌ UpYun 连续 3 次上传失败,国内源站仍是旧内容" exit 1 fi timeout 60 "$UPX_BIN" put public/.build_hash /__build_hash echo "✅ UpYun 上传完成" - 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 echo "" >> $GITHUB_STEP_SUMMARY echo "### 📊 构建统计" >> $GITHUB_STEP_SUMMARY echo "- **文件总数**: ${{ steps.stats.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY echo "- **总大小**: ${{ steps.stats.outputs.total_size }}" >> $GITHUB_STEP_SUMMARY echo "- **构建哈希**: ${{ steps.hash.outputs.build_hash }}" >> $GITHUB_STEP_SUMMARY if [ "${{ github.server_url }}" = "https://github.com" ]; then echo "- **Hugo 缓存**: ${{ steps.hugo-cache.outputs.cache-hit == 'true' && '✅ 命中' || '🔄 未命中' }}" >> $GITHUB_STEP_SUMMARY else echo "- **Hugo 缓存**: ⏭️ 已跳过 (Gitea 缓存服务跨网段不可达)" >> $GITHUB_STEP_SUMMARY fi # ============================================================ # Job 3: Deploy to EdgeOne Pages (并行) # ============================================================ deploy-edgeone: runs-on: ubuntu-latest timeout-minutes: 20 permissions: contents: read needs: build outputs: deployed: ${{ steps.check.outputs.deployed }} duration: ${{ steps.end-edgeone.outputs.duration }} upload_duration: ${{ steps.end-edgeone-upload.outputs.duration }} steps: - name: Start timing id: start-edgeone run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Check if deployment is needed id: check run: | # 2026-10-04 起去掉「比对 last_hash 跳过未变化部署」的优化: # last_hash 原本存 COS,砍 COS 后无处持久化(Gitea 缓存服务跨网段不可达)。 # 且 push 才触发构建,本就意味着有内容变化,直接部署即可。 echo "🔄 构建完成,部署到 EdgeOne" echo "deployed=true" >> $GITHUB_OUTPUT - name: Download build artifact id: download-files # 从 Gitea artifact 下载 public/(build job 上传的,同 runner 零跨境、零费用) # ★ 必须与上传端同为 v3:v4 在 Gitea(被识别为 GHES)上不受支持。 uses: actions/download-artifact@v3 with: name: hugo-public path: public/ - name: Normalize artifact layout # upload-artifact 对「目录路径」的打包语义存在版本/实现差异: # 解出来的可能是 public/index.html(内容直接在根),也可能是 # public/public/index.html(多一层目录)。这里统一拍平成 public/index.html, # 免得 EdgeOne 部署时找不到入口、或站点多出一层 /public/ 前缀。 run: | if [ ! -f public/index.html ] && [ -f public/public/index.html ]; then echo "ℹ️ 检测到产物多一层 public/,已拍平" mv public/public public__flat rm -rf public mv public__flat public fi if [ ! -f public/index.html ]; then echo "❌ 产物结构异常:public/index.html 不存在" find public -maxdepth 2 | head -20 exit 1 fi echo "✅ public/index.html 就位,文件数 $(find public -type f | wc -l)" - name: Start upload timing id: start-edgeone-upload run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Deploy to EdgeOne Pages env: EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }} run: | # 内联 scripts/deploy_edgeone.sh —— 本步骤只需一个部署命令, # 原先为此 checkout 整个仓库要花约 81 秒,不值得。 if [ -z "$EDGEONE_API_TOKEN" ]; then echo "Error: EDGEONE_API_TOKEN is not set." exit 1 fi echo "Deploying to EdgeOne Pages..." echo "Project: hugo-blog | Directory: ./public | Area: overseas" # 国际站必须加 --area overseas npx edgeone pages deploy ./public -n hugo-blog -t "$EDGEONE_API_TOKEN" --area overseas - name: End upload timing id: end-edgeone-upload run: | end_time=$(date +%s) start_time=${{ steps.start-edgeone-upload.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT - 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 echo "## ⏱️ Deploy EdgeOne 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 📊 部署统计" >> $GITHUB_STEP_SUMMARY echo "- **上传部署**: ${duration}秒" >> $GITHUB_STEP_SUMMARY echo "- **文件总数**: ${{ needs.build.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY # ============================================================ # 注意:这里原先有一个名为 deploy-upyun 的 job —— 它是纯空壳: # 零上传动作,却无条件打印「UpYun 已同步最新内容」,且它的输出被 # Telegram / 飞书 / 邮件通知当成"UpYun 部署成功"的依据。 # 因它持续掩盖真实故障(7/25 起国内线路停更两个多月),已于 2026-10-01 删除。 # 又拍云的真实状态现在来自 build job 的 upyun_status 输出。 # ============================================================ # ============================================================ # Job 5: CDN Refresh & Notifications (after all deployments) # ============================================================ finalize: runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: write needs: [pre-check, build, deploy-edgeone] if: always() && needs.build.result == 'success' && needs.deploy-edgeone.result == 'success' steps: - name: Start timing id: start-finalize run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT - name: Setup Node uses: actions/setup-node@v4 with: node-version: '22' # 本 job 不再安装 npm 依赖(CDN 刷新脚本只用 node 内置模块), # 因此不启用 npm 缓存 —— 也免得在没有 package-lock.json 时算缓存 key 出错。 - name: Determine deployment status id: deploy-status run: | EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" UPYUN_STATUS="${{ needs.build.outputs.upyun_status }}" echo "EdgeOne deployed: $EDGEONE_DEPLOYED" echo "UpYun status: $UPYUN_STATUS" if [ "$EDGEONE_DEPLOYED" = "true" ] || [ "$UPYUN_STATUS" = "success" ]; then echo "any_deployed=true" >> $GITHUB_OUTPUT else echo "any_deployed=false" >> $GITHUB_OUTPUT fi # 多吉云 CDN 只服务国内线路,回源是又拍云。 # 2026-10-03 起海外直传 UpYun 已永久跳过,国内线路由中转机同步, # sync.sh 同步完成后会自己刷多吉云 —— 这里保持 false 即可。 if [ "$UPYUN_STATUS" = "success" ]; then echo "cdn_refresh=true" >> $GITHUB_OUTPUT else echo "cdn_refresh=false" >> $GITHUB_OUTPUT echo "ℹ️ 国内线路由中转机同步(sync.sh 完成后会自行刷新多吉云),CI 侧跳过" fi - name: Refresh DogeCloud CDN if: steps.deploy-status.outputs.cdn_refresh == 'true' env: DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} CDN_URL_LIST: ${{ secrets.CDN_URL_LIST }} run: | # 内联 scripts/refresh_cdn.js(修改该文件时需同步这里)。 # 该脚本零外部依赖 —— 仅用 node 内置的 crypto 与 fetch —— # 因此不必 checkout 整个仓库、也不必 npm ci(原先这两步合计约 211 秒)。 cat > /tmp/refresh_cdn.js <<'REFRESH_CDN_EOF' const crypto = require("crypto"); async function dogecloudApi(apiPath, data = {}) { const accessKey = process.env.DOGECLOUD_ACCESS_KEY; const secretKey = process.env.DOGECLOUD_SECRET_KEY; if (!accessKey || !secretKey) { console.error("Error: DOGECLOUD_ACCESS_KEY or DOGECLOUD_SECRET_KEY not set."); process.exit(1); } const body = JSON.stringify(data); const signStr = apiPath + "\n" + body; const sign = crypto.createHmac("sha1", secretKey).update(signStr).digest("hex"); const authorization = `TOKEN ${accessKey}:${sign}`; const res = await fetch(`https://api.dogecloud.com${apiPath}`, { method: "POST", headers: { Authorization: authorization, "Content-Type": "application/json", }, body, }); return res.json(); } async function main() { const cdnUrlListStr = process.env.CDN_URL_LIST; if (!cdnUrlListStr) { console.log("Warning: CDN_URL_LIST not found or empty."); return; } const urlList = cdnUrlListStr .replace(/\n/g, ",") .split(",") .map((u) => u.trim()) .filter(Boolean); if (urlList.length === 0) { console.log("No URLs to refresh."); return; } console.log(`Refreshing ${urlList.length} URLs...`); const result = await dogecloudApi("/cdn/refresh/add.json", { rtype: "path", urls: JSON.stringify(urlList), }); if (result && result.code === 200) { console.log(`Refresh task submitted. Task ID: ${result.data.task_id}`); } else { console.error(`Failed to submit refresh task. Response: ${JSON.stringify(result)}`); process.exit(1); } } main(); REFRESH_CDN_EOF node /tmp/refresh_cdn.js - name: Send Telegram notification with detailed timing 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 PRECHECK_DUR="${{ needs.pre-check.outputs.duration }}" BUILD_DUR="${{ needs.build.outputs.duration }}" EDGEONE_DUR="${{ needs.deploy-edgeone.outputs.duration }}" EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" UPYUN_DUR="${{ needs.build.outputs.upyun_duration }}" UPYUN_STATUS_RAW="${{ needs.build.outputs.upyun_status }}" FILE_COUNT="${{ needs.build.outputs.file_count }}" TOTAL_SIZE="${{ needs.build.outputs.total_size }}" EDGEONE_DETAIL="" if [ "$EDGEONE_DEPLOYED" = "true" ]; then EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}" EDGEONE_DETAIL=$(printf "\n 📤 上传: %ss" "$EDGEONE_UPLOAD") fi # 三态映射:直传结果如实呈现,不再有"文件已在 Build 阶段同步"这种没验证过的说辞 case "$UPYUN_STATUS_RAW" in success) UPYUN_TEXT="✅ 已直传";; failure) UPYUN_TEXT="❌ 直传失败(国内中转机兜底)";; skipped) UPYUN_TEXT="⏭️ 跳过(无变化)";; *) UPYUN_TEXT="❔ 未执行";; esac if [ -n "$UPYUN_DUR" ]; then UPYUN_DUR_TEXT="${UPYUN_DUR}s"; else UPYUN_DUR_TEXT="—"; fi if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then STATUS_ICON="🎉" STATUS_TEXT="部署成功" else STATUS_ICON="⏭️" STATUS_TEXT="无变化,跳过部署" fi EDGEONE_STATUS=$([ "$EDGEONE_DEPLOYED" = "true" ] && echo "✅ 已部署" || echo "⏭️ 跳过") CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') MESSAGE="$STATUS_ICON *$STATUS_TEXT*" MESSAGE="$MESSAGE"$'\n\n' MESSAGE="$MESSAGE""📦 *项目*: \`${{ github.repository }}\`"$'\n' MESSAGE="$MESSAGE""🌿 *分支*: \`${{ github.ref_name }}\`"$'\n' MESSAGE="$MESSAGE""🔗 *Commit*: \`${{ github.sha }}\`"$'\n' MESSAGE="$MESSAGE""⏰ *时间*: $CURRENT_TIME"$'\n' MESSAGE="$MESSAGE""🚀 *触发方式*: ${{ github.event_name }}"$'\n\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""📊 *构建统计*"$'\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""📁 文件总数: $FILE_COUNT"$'\n' MESSAGE="$MESSAGE""💾 总大小: $TOTAL_SIZE"$'\n\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""⏱️ *耗时统计*"$'\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""🔍 Pre-check: ${PRECHECK_DUR}s"$'\n' MESSAGE="$MESSAGE""🏗️ Build: ${BUILD_DUR}s"$'\n' MESSAGE="$MESSAGE""🌐 EdgeOne: ${EDGEONE_DUR}s$EDGEONE_DETAIL"$'\n' MESSAGE="$MESSAGE""☁️ UpYun 直传: ${UPYUN_DUR_TEXT}"$'\n\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""📌 *部署状态*"$'\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""🌐 EdgeOne Pages: $EDGEONE_STATUS"$'\n' MESSAGE="$MESSAGE""☁️ UpYun: $UPYUN_TEXT"$'\n\n' if [ "${{ github.server_url }}" = "https://github.com" ]; then RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" else RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" fi MESSAGE="$MESSAGE""🔗 [查看详情]($RUN_URL)" 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" \ -d disable_web_page_preview=true - name: Send Feishu notification with detailed timing env: FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} run: | if [ -z "$FEISHU_WEBHOOK_URL" ]; then echo "⚠️ 飞书 Webhook URL 未配置,跳过通知" exit 0 fi PRECHECK_DUR="${{ needs.pre-check.outputs.duration }}" BUILD_DUR="${{ needs.build.outputs.duration }}" EDGEONE_DUR="${{ needs.deploy-edgeone.outputs.duration }}" EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" UPYUN_DUR="${{ needs.build.outputs.upyun_duration }}" UPYUN_STATUS_RAW="${{ needs.build.outputs.upyun_status }}" FILE_COUNT="${{ needs.build.outputs.file_count }}" TOTAL_SIZE="${{ needs.build.outputs.total_size }}" EDGEONE_DETAIL="" if [ "$EDGEONE_DEPLOYED" = "true" ]; then EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}" EDGEONE_DETAIL=$(printf "\n 📤 上传: %ss" "$EDGEONE_UPLOAD") fi # 三态映射:直传结果如实呈现,不再有"文件已在 Build 阶段同步"这种没验证过的说辞 case "$UPYUN_STATUS_RAW" in success) UPYUN_TEXT="✅ 已直传";; failure) UPYUN_TEXT="❌ 直传失败(国内中转机兜底)";; skipped) UPYUN_TEXT="⏭️ 跳过(无变化)";; *) UPYUN_TEXT="❔ 未执行";; esac if [ -n "$UPYUN_DUR" ]; then UPYUN_DUR_TEXT="${UPYUN_DUR}s"; else UPYUN_DUR_TEXT="—"; fi if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then STATUS_ICON="🎉" STATUS_TEXT="部署成功" else STATUS_ICON="⏭️" STATUS_TEXT="无变化,跳过部署" fi EDGEONE_STATUS=$([ "$EDGEONE_DEPLOYED" = "true" ] && echo "✅ 已部署" || echo "⏭️ 跳过") CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') MESSAGE_TEXT="$STATUS_ICON $STATUS_TEXT" MESSAGE_TEXT="$MESSAGE_TEXT"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""📦 项目: ${{ github.repository }}"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🌿 分支: ${{ github.ref_name }}"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🔗 Commit: ${{ github.sha }}"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""⏰ 时间: $CURRENT_TIME"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🚀 触发方式: ${{ github.event_name }}"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""📊 构建统计"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""📁 文件总数: $FILE_COUNT"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""💾 总大小: $TOTAL_SIZE"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""⏱️ 耗时统计"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🔍 Pre-check: ${PRECHECK_DUR}s"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🏗️ Build: ${BUILD_DUR}s"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🌐 EdgeOne: ${EDGEONE_DUR}s$EDGEONE_DETAIL"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""☁️ UpYun 直传: ${UPYUN_DUR_TEXT}"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""📌 部署状态"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🌐 EdgeOne Pages: $EDGEONE_STATUS"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""☁️ UpYun: $UPYUN_TEXT"$'\n\n' if [ "${{ github.server_url }}" = "https://github.com" ]; then RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" else RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" fi MESSAGE_TEXT="$MESSAGE_TEXT""🔗 查看详情: $RUN_URL" echo "📤 发送飞书通知..." echo "Message length: ${#MESSAGE_TEXT}" RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST "$FEISHU_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg text "$MESSAGE_TEXT" '{"msg_type":"text","content":{"text":$text}}')") echo "飞书响应: $RESPONSE" - 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 email notification with detailed timing 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: "🎉 部署成功 - ${{ github.repository }} - ${{ steps.date.outputs.date }}" to: ${{ secrets.MAIL_USERNAME }} from: GitHub Actions <${{ github.event_name }}> body: | 🎉 部署成功 🎉 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📦 项目信息 ────────────────────────────────── 项目: ${{ github.repository }} 分支: ${{ github.ref_name }} Commit: ${{ github.sha }} ⏰ 时间: ${{ steps.date.outputs.date }} 🚀 触发方式: ${{ github.event_name }} 📊 构建统计 ────────────────────────────────── 📁 文件总数: ${{ needs.build.outputs.file_count }} 💾 总大小: ${{ needs.build.outputs.total_size }} ⏱️ 耗时统计 ────────────────────────────────── 🔍 Pre-check: ${{ needs.pre-check.outputs.duration }}s 🏗️ Build: ${{ needs.build.outputs.duration }}s 🌐 EdgeOne Pages: ${{ needs.deploy-edgeone.outputs.duration }}s 📤 上传部署: ${{ needs.deploy-edgeone.outputs.upload_duration }}s ☁️ UpYun 直传: ${{ needs.build.outputs.upyun_status == 'success' && '✅ 成功' || (needs.build.outputs.upyun_status == 'skipped' && '⏭️ 跳过(无变化)' || '❌ 失败(国内中转机兜底)') }} 📌 部署状态 ────────────────────────────────── 🌐 EdgeOne Pages: ${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅ 已部署' || '⏭️ 跳过(无变化)' }} ☁️ UpYun: ${{ needs.build.outputs.upyun_status == 'success' && '✅ 已直传' || (needs.build.outputs.upyun_status == 'skipped' && '⏭️ 跳过(无变化)' || '❌ 直传失败(中转机兜底)') }} 🔗 查看详情 ────────────────────────────────── ${{ github.server_url == 'https://github.com' && 'https://github.com/zqlit/blog' || 'http://23.254.236.47:3001/zqlit/blog' }}/actions/runs/${{ github.run_id }} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 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)) PRECHECK_DUR="${{ needs.pre-check.outputs.duration }}" BUILD_DUR="${{ needs.build.outputs.duration }}" EDGEONE_DUR="${{ needs.deploy-edgeone.outputs.duration }}" EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" UPYUN_DUR="${{ needs.build.outputs.upyun_duration }}" if [ "$EDGEONE_DEPLOYED" = "true" ]; then EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}" else EDGEONE_UPLOAD="0" fi UPYUN_DUR_CELL="${UPYUN_DUR}s"; [ -z "$UPYUN_DUR" ] && UPYUN_DUR_CELL="—" echo "## 📊 部署报告" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 基本信息" >> $GITHUB_STEP_SUMMARY echo "- **项目**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY echo "- **触发方式**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY echo "- **文件总数**: ${{ needs.build.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY echo "- **总大小**: ${{ needs.build.outputs.total_size }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### ⏱️ 耗时统计" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| 阶段 | 总耗时 | 上传 |" >> $GITHUB_STEP_SUMMARY echo "|------|--------|------|" >> $GITHUB_STEP_SUMMARY echo "| Pre-check | ${PRECHECK_DUR}s | - |" >> $GITHUB_STEP_SUMMARY echo "| Build | ${BUILD_DUR}s | - |" >> $GITHUB_STEP_SUMMARY echo "| EdgeOne | ${EDGEONE_DUR}s | ${EDGEONE_UPLOAD}s |" >> $GITHUB_STEP_SUMMARY echo "| UpYun 直传 | ${UPYUN_DUR_CELL} | ${UPYUN_DUR_CELL} |" >> $GITHUB_STEP_SUMMARY echo "| Finalize | ${finalize_duration}s | - |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 🚀 部署状态" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| 目标 | 状态 |" >> $GITHUB_STEP_SUMMARY echo "|------|------|" >> $GITHUB_STEP_SUMMARY echo "| EdgeOne Pages | ${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅ 已部署' || '⏭️ 跳过(无变化)' }} |" >> $GITHUB_STEP_SUMMARY echo "| UpYun 直传 | ${{ needs.build.outputs.upyun_status == 'success' && '✅ 成功' || (needs.build.outputs.upyun_status == 'skipped' && '⏭️ 跳过(无变化)' || '❌ 失败(中转机兜底)') }} |" >> $GITHUB_STEP_SUMMARY echo "| CDN 刷新 | ${{ steps.deploy-status.outputs.any_deployed == 'true' && '✅ 已刷新' || '⏭️ 跳过' }} |" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 6: Failure Notification (失败通知) # ============================================================ notify-failure: runs-on: ubuntu-latest timeout-minutes: 3 permissions: contents: read needs: [pre-check, build, deploy-edgeone, finalize] if: always() && (needs.pre-check.result == 'failure' || needs.build.result == 'failure' || needs.deploy-edgeone.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 FAILURES="" if [ "${{ needs.pre-check.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Pre-check" "$FAILURES"); fi if [ "${{ needs.build.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Build" "$FAILURES"); fi if [ "${{ needs.deploy-edgeone.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Deploy EdgeOne" "$FAILURES"); fi if [ "${{ needs.build.outputs.upyun_status }}" = "failure" ]; then FAILURES=$(printf "%s\n ⚠️ UpYun 直传失败(不阻断,国内中转机兜底)" "$FAILURES"); fi if [ "${{ needs.finalize.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Finalize" "$FAILURES"); fi PRECHECK_STATUS=$([ "${{ needs.pre-check.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.pre-check.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) BUILD_STATUS=$([ "${{ needs.build.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.build.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) EDGEONE_STATUS=$([ "${{ needs.deploy-edgeone.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.deploy-edgeone.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) UPYUN_STATUS=$([ "${{ needs.build.outputs.upyun_status }}" = "success" ] && echo "✅ 直传成功" || ([ "${{ needs.build.outputs.upyun_status }}" = "failure" ] && echo "❌ 直传失败(中转兜底)" || echo "⏭️ 跳过(无变化)")) FINALIZE_STATUS=$([ "${{ needs.finalize.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.finalize.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') MESSAGE="💥 *部署失败*" MESSAGE="$MESSAGE"$'\n\n' MESSAGE="$MESSAGE""📦 *项目*: \`${{ github.repository }}\`"$'\n' MESSAGE="$MESSAGE""🌿 *分支*: \`${{ github.ref_name }}\`"$'\n' MESSAGE="$MESSAGE""🔗 *Commit*: \`${{ github.sha }}\`"$'\n' MESSAGE="$MESSAGE""⏰ *时间*: $CURRENT_TIME"$'\n' MESSAGE="$MESSAGE""🚀 *触发方式*: ${{ github.event_name }}"$'\n\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""⚠️ *失败阶段*"$'\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━$FAILURES"$'\n\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""📊 *各阶段状态*"$'\n' MESSAGE="$MESSAGE""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE="$MESSAGE""🔍 Pre-check: $PRECHECK_STATUS"$'\n' MESSAGE="$MESSAGE""🏗️ Build: $BUILD_STATUS"$'\n' MESSAGE="$MESSAGE""🌐 Deploy EdgeOne: $EDGEONE_STATUS"$'\n' MESSAGE="$MESSAGE""☁️ UpYun 直传: $UPYUN_STATUS"$'\n' MESSAGE="$MESSAGE""📤 Finalize: $FINALIZE_STATUS"$'\n\n' if [ "${{ github.server_url }}" = "https://github.com" ]; then RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" else RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" fi MESSAGE="$MESSAGE""🔗 [查看详情]($RUN_URL)" 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" \ -d disable_web_page_preview=true - 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 FAILURES="" if [ "${{ needs.pre-check.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Pre-check" "$FAILURES"); fi if [ "${{ needs.build.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Build" "$FAILURES"); fi if [ "${{ needs.deploy-edgeone.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Deploy EdgeOne" "$FAILURES"); fi if [ "${{ needs.build.outputs.upyun_status }}" = "failure" ]; then FAILURES=$(printf "%s\n ⚠️ UpYun 直传失败(不阻断,国内中转机兜底)" "$FAILURES"); fi if [ "${{ needs.finalize.result }}" = "failure" ]; then FAILURES=$(printf "%s\n 🔴 Finalize" "$FAILURES"); fi PRECHECK_STATUS=$([ "${{ needs.pre-check.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.pre-check.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) BUILD_STATUS=$([ "${{ needs.build.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.build.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) EDGEONE_STATUS=$([ "${{ needs.deploy-edgeone.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.deploy-edgeone.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) UPYUN_STATUS=$([ "${{ needs.build.outputs.upyun_status }}" = "success" ] && echo "✅ 直传成功" || ([ "${{ needs.build.outputs.upyun_status }}" = "failure" ] && echo "❌ 直传失败(中转兜底)" || echo "⏭️ 跳过(无变化)")) FINALIZE_STATUS=$([ "${{ needs.finalize.result }}" = "success" ] && echo "✅ 成功" || ([ "${{ needs.finalize.result }}" = "failure" ] && echo "❌ 失败" || echo "⏭️ 跳过")) CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') MESSAGE_TEXT="💥 部署失败" MESSAGE_TEXT="$MESSAGE_TEXT"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""📦 项目: ${{ github.repository }}"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🌿 分支: ${{ github.ref_name }}"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🔗 Commit: ${{ github.sha }}"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""⏰ 时间: $CURRENT_TIME"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🚀 触发方式: ${{ github.event_name }}"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""⚠️ 失败阶段"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━$FAILURES"$'\n\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""📊 各阶段状态"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""━━━━━━━━━━━━━━━━━━━━━━"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🔍 Pre-check: $PRECHECK_STATUS"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🏗️ Build: $BUILD_STATUS"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""🌐 Deploy EdgeOne: $EDGEONE_STATUS"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""☁️ UpYun 直传: $UPYUN_STATUS"$'\n' MESSAGE_TEXT="$MESSAGE_TEXT""📤 Finalize: $FINALIZE_STATUS"$'\n\n' if [ "${{ github.server_url }}" = "https://github.com" ]; then RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" else RUN_URL="http://23.254.236.47:3001/${{ github.repository }}/actions/runs/${{ github.run_id }}" fi MESSAGE_TEXT="$MESSAGE_TEXT""🔗 查看详情: $RUN_URL" curl -s -X POST "$FEISHU_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg text "$MESSAGE_TEXT" '{"msg_type":"text","content":{"text":$text}}')" # ============================================================ # Job 7: 网络链路探测(仅手动触发,不改任何线上数据) # ============================================================ # 目的:评估「境外 runner → 国内源站/CDN」各条链路的真实耗时与稳定性, # 为「境外 EdgeOne + 境内多吉云」架构决策提供数据,而不是拍脑袋。 # # 触发方式:workflow_dispatch(手动),绝不随 push/schedule 自动跑。 # 安全:只做连通性/耗时探测,不下载、不上传任何真实产物,不刷新 CDN。 # # 探测项: # A. 又拍云 API(v0.api.upyun.com) —— 境外直传又拍云的入口,已知会 EOF # B. 又拍云对象存储上传探针(upx login) —— 登录握手能否成功(不传文件) # C. 腾讯云 COS(cos.ap-*.myqcloud.com) —— 现状中转,作为稳定对照 # D. 多吉云 API(api.dogecloud.com) —— CDN 刷新 API 跨境响应耗时 # E. 多吉云 CDN 边缘节点 —— 国内加速域名的连通耗时 # ============================================================ probe-network: runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read if: github.event_name == 'workflow_dispatch' steps: - name: 探测结果汇总头 run: | echo "## 🌐 境外 Runner → 国内链路探测" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| 探测项 | 结果 | 耗时 |" >> $GITHUB_STEP_SUMMARY echo "|--------|------|------|" >> $GITHUB_STEP_SUMMARY - name: A. 又拍云 API 连通性 run: | echo "📡 探测又拍云 v0 API..." T0=$(date +%s%3N) CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://v0.api.upyun.com/" 2>&1 || echo "FAIL") T1=$(date +%s%3N) DUR=$(( (T1 - T0) / 1000 )) echo " HTTP $CODE,耗时 ${DUR}s" echo "| 又拍云 v0 API | $([ "$CODE" = "200" ] || [ "$CODE" = "401" ] || [ "$CODE" = "403" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - name: B. 又拍云 upx 登录握手(不传文件) env: UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }} UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }} UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }} run: | echo "📡 探测又拍云 upx 登录握手..." if [ -z "$UPYUN_SERVICE" ] || [ -z "$UPYUN_OPERATOR" ] || [ -z "$UPYUN_PASSWORD" ]; then echo " ⚠️ UpYun secrets 未配置,跳过" echo "| upx 登录握手 | ⏭️ 无密钥 | — |" >> $GITHUB_STEP_SUMMARY exit 0 fi UPX_VERSION="0.4.9" curl -fsSL --connect-timeout 10 --max-time 60 -o /tmp/upx.tar.gz \ "https://github.com/upyun/upx/releases/download/v${UPX_VERSION}/upx_${UPX_VERSION}_linux_amd64.tar.gz" \ && tar -xzf /tmp/upx.tar.gz -C /tmp && mv /tmp/upx /tmp/upyun-upx && chmod +x /tmp/upyun-upx T0=$(date +%s%3N) if timeout 30 /tmp/upyun-upx login "$UPYUN_SERVICE" "$UPYUN_OPERATOR" "$UPYUN_PASSWORD" 2>&1; then R="✅ 握手成功" else R="❌ 握手失败" fi T1=$(date +%s%3N) DUR=$(( (T1 - T0) / 1000 )) echo " $R,耗时 ${DUR}s" echo "| upx 登录握手 | $R | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - name: C. 腾讯云 COS 连通性(现状对照) env: COS_BUCKET: ${{ secrets.COS_BUCKET }} COS_REGION: ${{ secrets.COS_REGION }} run: | echo "📡 探测腾讯云 COS..." if [ -z "$COS_BUCKET" ]; then echo " ⚠️ COS secrets 未配置,跳过" echo "| 腾讯云 COS | ⏭️ 无密钥 | — |" >> $GITHUB_STEP_SUMMARY exit 0 fi HOST="${COS_BUCKET}.cos.${COS_REGION}.myqcloud.com" T0=$(date +%s%3N) CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://${HOST}/" 2>&1 || echo "FAIL") T1=$(date +%s%3N) DUR=$(( (T1 - T0) / 1000 )) echo " ${HOST} → HTTP $CODE,耗时 ${DUR}s" echo "| 腾讯云 COS | $([ "$CODE" != "FAIL" ] && [ "$CODE" != "000" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - name: D. 多吉云 API 跨境响应 env: DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} run: | echo "📡 探测多吉云 API(不带签名,只测连通+延迟)..." T0=$(date +%s%3N) CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://api.dogecloud.com/cdn/refresh/add.json" 2>&1 || echo "FAIL") T1=$(date +%s%3N) DUR=$(( (T1 - T0) / 1000 )) echo " HTTP $CODE,耗时 ${DUR}s" echo "| 多吉云 API | $([ "$CODE" != "FAIL" ] && [ "$CODE" != "000" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - name: E. 多吉云 CDN 边缘节点连通性 run: | echo "📡 探测多吉云 CDN 边缘节点..." # 用当前博客域名(国内加速走多吉云)测连通 + 耗时 T0=$(date +%s%3N) CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 20 "https://usj.cc/" 2>&1 || echo "FAIL") T1=$(date +%s%3N) DUR=$(( (T1 - T0) / 1000 )) echo " usj.cc → HTTP $CODE,耗时 ${DUR}s" echo "| 博客站点(多吉云加速) | $([ "$CODE" = "200" ] && echo '✅ 可达' || echo '⚠️ $CODE') | ${DUR}s |" >> $GITHUB_STEP_SUMMARY - name: 探测结果汇总尾 run: | echo "" >> $GITHUB_STEP_SUMMARY echo "> 结论判读:若「upx 登录握手」与「又拍云 v0 API」耗时远高于「COS / 多吉云 API」," >> $GITHUB_STEP_SUMMARY echo "> 说明境外直传又拍云仍不稳定,国内线路应继续走中转机兜底,不宜改成境外直传。" >> $GITHUB_STEP_SUMMARY