Files
blog/.github/workflows/deploy.yml
T
QunLIn f486a46b6d
Deploy to Production / pre-check (push) Successful in 1m8s
Deploy to Production / build (push) Canceled after 28m0s
Deploy to Production / deploy-edgeone (push) Canceled after 0s
Deploy to Production / finalize (push) Skipped
Deploy to Production / notify-failure (push) Skipped
修 UpYun 直传:降并发去 --strong、加三轮重试;UpYun 未成功不再刷国内 CDN
- upx sync 从 -w 10 --delete --strong 改为 -w 3 --delete + 三轮重试(单次 25 分钟)。
  本runner 在海外,UpYun v0 接口对并发敏感,-w 10 + --strong 会让一批目录的
  mkdir 全部 EOF(2026-10-02 连续两次失败,第二次直接撞 480s 超时)。
  upx 0.4.9 只有 -w/--delete/--strong 三个参数,没有 --resume/--checksum,
  所以只能靠降并发 + 整体重试。
- 多吉云 CDN 刷新改为仅在 UpYun 成功时执行:EdgeOne 是境外线路,
  它成功不代表国内源站已更新,之前会刷出一份"刷新成功但内容没变"的假象。
2026-10-02 20:39:00 +08:00

1070 lines
50 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }}
last_hash: ${{ steps.last-hash.outputs.last_hash }}
duration: ${{ steps.end-build.outputs.duration }}
file_count: ${{ steps.stats.outputs.file_count }}
total_size: ${{ steps.stats.outputs.total_size }}
need_upload: ${{ steps.check-upload.outputs.need_upload }}
# Upload to UpYun 的真实三态:success / failure / skipped(未执行)。
# 该步骤标了 continue-on-error,conclusion 恒为 success,不能用来判断成败。
# 这里刻意不用 steps.<id>.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
echo "$BUILD_HASH" > public/.build_hash
- 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: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install coscmd
run: pip install -U coscmd
- name: Configure coscmd
env:
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }}
COS_BUCKET: ${{ secrets.COS_BUCKET }}
COS_REGION: ${{ secrets.COS_REGION }}
run: coscmd config -a $COS_SECRET_ID -s $COS_SECRET_KEY -b $COS_BUCKET -r $COS_REGION -m 30
- name: Get last build hash from COS
id: last-hash
continue-on-error: true
run: |
echo "🔍 从腾讯云 COS 获取上次部署的 hash..."
coscmd download /__build_hash /tmp/last_hash 2>/dev/null || echo ""
LAST_HASH=$(cat /tmp/last_hash 2>/dev/null || echo "")
if [ -z "$LAST_HASH" ]; then
echo "⚠️ 没有找到上次部署的 hash(首次部署)"
else
echo "上次部署 hash: $LAST_HASH"
fi
echo "last_hash=${LAST_HASH}" >> $GITHUB_OUTPUT
- name: Check if upload is needed
id: check-upload
run: |
LAST_HASH="${{ steps.last-hash.outputs.last_hash }}"
CURRENT_HASH="${{ steps.hash.outputs.build_hash }}"
echo "上次部署 hash: $LAST_HASH"
echo "当前构建 hash: $CURRENT_HASH"
if [ -z "$LAST_HASH" ] || [ "$CURRENT_HASH" != "$LAST_HASH" ]; then
echo "🔄 构建产物有变化,需要上传"
echo "need_upload=true" >> $GITHUB_OUTPUT
else
echo "✅ 构建产物无变化,跳过上传"
echo "need_upload=false" >> $GITHUB_OUTPUT
fi
- name: Upload to Tencent COS
if: steps.check-upload.outputs.need_upload == 'true'
continue-on-error: true
run: |
echo "📤 打包并上传到腾讯云 COS..."
if command -v zstd; then
tar -cf - public/ | zstd -1 -T0 -o public.tar.zst
coscmd upload public.tar.zst /public.tar.zst
else
tar -czf public.tar.gz public/
coscmd upload public.tar.gz /public.tar.gz
fi
coscmd upload public/.build_hash /__build_hash
- name: Upload hash to COS (even if skipped)
if: steps.check-upload.outputs.need_upload == 'false'
run: |
echo "📤 更新 hash 文件到腾讯云 COS..."
coscmd upload public/.build_hash /__build_hash
- name: Upload to UpYun
if: steps.check-upload.outputs.need_upload == 'true'
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 }}
download_duration: ${{ steps.download-files.outputs.download_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: |
CURRENT_HASH="${{ needs.build.outputs.build_hash }}"
LAST_HASH="${{ needs.build.outputs.last_hash }}"
echo "当前 build_hash: $CURRENT_HASH"
echo "上次部署 hash: $LAST_HASH"
if [ -z "$LAST_HASH" ] || [ "$CURRENT_HASH" != "$LAST_HASH" ]; then
echo "🔄 构建产物有变化,需要部署到 EdgeOne"
echo "deployed=true" >> $GITHUB_OUTPUT
else
echo "✅ 构建产物无变化,跳过 EdgeOne 部署"
echo "deployed=false" >> $GITHUB_OUTPUT
fi
- name: Setup Python
if: steps.check.outputs.deployed == 'true'
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install coscmd
if: steps.check.outputs.deployed == 'true'
run: pip install -U coscmd
- name: Configure coscmd
if: steps.check.outputs.deployed == 'true'
env:
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }}
COS_BUCKET: ${{ secrets.COS_BUCKET }}
COS_REGION: ${{ secrets.COS_REGION }}
run: coscmd config -a $COS_SECRET_ID -s $COS_SECRET_KEY -b $COS_BUCKET -r $COS_REGION
- name: Download files from COS
if: steps.check.outputs.deployed == 'true'
id: download-files
run: |
echo "📥 从腾讯云 COS 下载构建产物..."
DOWNLOAD_START=$(date +%s)
if command -v zstd; then
coscmd download /public.tar.zst /tmp/public.tar.zst
tar -I zstd -xf /tmp/public.tar.zst
else
coscmd download /public.tar.gz /tmp/public.tar.gz
tar -xzf /tmp/public.tar.gz
fi
DOWNLOAD_END=$(date +%s)
DOWNLOAD_DURATION=$((DOWNLOAD_END - DOWNLOAD_START))
echo "download_duration=${DOWNLOAD_DURATION}" >> $GITHUB_OUTPUT
- name: Start upload timing
if: steps.check.outputs.deployed == 'true'
id: start-edgeone-upload
run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
- name: Deploy to EdgeOne Pages
if: steps.check.outputs.deployed == 'true'
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
if: steps.check.outputs.deployed == 'true'
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
if [ "${{ steps.check.outputs.deployed }}" = "true" ]; then
echo "## ⏱️ Deploy EdgeOne 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📊 部署统计" >> $GITHUB_STEP_SUMMARY
echo "- **下载: ${{ steps.download-files.outputs.download_duration }}s**" >> $GITHUB_STEP_SUMMARY
echo "- **上传部署**: ${duration}秒" >> $GITHUB_STEP_SUMMARY
echo "- **文件总数**: ${{ needs.build.outputs.file_count }}" >> $GITHUB_STEP_SUMMARY
else
echo "## EdgeOne 部署已跳过(构建产物无变化)" >> $GITHUB_STEP_SUMMARY
echo "总耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY
fi
# ============================================================
# 注意:这里原先有一个名为 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 只服务国内线路,回源是又拍云。
# EdgeOne(境外) 部署成功 ≠ 国内源站已更新 —— UpYun 失败时刷新多吉云,
# 只会把同一份旧内容再拉一遍,看着"刷新成功"却毫无变化(2026-10-02 踩过)。
if [ "$UPYUN_STATUS" = "success" ]; then
echo "cdn_refresh=true" >> $GITHUB_OUTPUT
else
echo "cdn_refresh=false" >> $GITHUB_OUTPUT
echo "⚠️ UpYun 状态为 $UPYUN_STATUS —— 国内源站(又拍云)未更新,跳过多吉云 CDN 刷新"
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_DOWNLOAD="${{ needs.deploy-edgeone.outputs.download_duration }}"
EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}"
EDGEONE_DETAIL=$(printf "\n 📥 下载: %ss\n 📤 上传: %ss" "$EDGEONE_DOWNLOAD" "$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_DOWNLOAD="${{ needs.deploy-edgeone.outputs.download_duration }}"
EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}"
EDGEONE_DETAIL=$(printf "\n 📥 下载: %ss\n 📤 上传: %ss" "$EDGEONE_DOWNLOAD" "$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
📥 下载 COS: ${{ needs.deploy-edgeone.outputs.download_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_DOWNLOAD="${{ needs.deploy-edgeone.outputs.download_duration }}"
EDGEONE_UPLOAD="${{ needs.deploy-edgeone.outputs.upload_duration }}"
else
EDGEONE_DOWNLOAD="0"
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_DOWNLOAD}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}}')"