diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index 3e875526e..dc732d20a 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -563,6 +563,15 @@ jobs: -w /workspace/apps/web \ docker.m.daocloud.io/library/node:20 \ sh -lc 'npx vitest run src/test' + + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Frontend Lint" python3 scripts/ci_notify_failure.py + deploy-staging: name: Build & Push Staging (Watchtower auto-deploy) runs-on: saas @@ -694,6 +703,23 @@ jobs: echo "Branch: ${GITHUB_REF_NAME}" echo "Commit: ${GITHUB_SHA}" + - name: Notify CI success + if: success() + shell: sh + run: | + set +e + echo "=== CI 成功通知 ===" + SUCCESS_JOB="Staging部署成功" python3 scripts/ci_notify_success.py + + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Build & Push Staging (Watchtower auto-deploy)" python3 scripts/ci_notify_failure.py + + staging-e2e: name: Staging E2E Tests @@ -762,6 +788,15 @@ jobs: git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ sh -lc "npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts" + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Staging E2E Tests" python3 scripts/ci_notify_failure.py + + staging-api-tests: name: Staging API Integration Tests runs-on: saas @@ -827,6 +862,15 @@ jobs: git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ sh -lc 'npm ci && npx playwright test --reporter=line e2e/test_auth.spec.ts e2e/test_asset.spec.ts e2e/test_project.spec.ts' + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Staging API Integration Tests" python3 scripts/ci_notify_failure.py + + build-production-runtime-images: name: Build Production Runtime Images @@ -907,6 +951,15 @@ jobs: echo "Disk usage after cleanup:" df -h / | tail -1 + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Build Production Runtime Images" python3 scripts/ci_notify_failure.py + + deploy-production: name: Deploy Production runs-on: saas @@ -972,6 +1025,23 @@ jobs: echo "$DEPLOY_B64" | base64 -d | ssh -p 22222 -i "$key_path" "$production_user@$production_host" "IMAGE_TAG='${GITHUB_REF_NAME}' REGISTRY_TOKEN='${REGISTRY_TOKEN}' sh" + - name: Notify CI success + if: success() + shell: sh + run: | + set +e + echo "=== CI 成功通知 ===" + SUCCESS_JOB="生产部署成功" python3 scripts/ci_notify_success.py + + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Deploy Production" python3 scripts/ci_notify_failure.py + + production-e2e: name: Production Browser E2E runs-on: saas @@ -1040,3 +1110,12 @@ jobs: -w /workspace/apps/web \ git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ sh -lc 'npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts' + + - name: Notify CI failure + if: failure() + shell: sh + run: | + set +e + echo "=== CI 失败通知 ===" + FAILED_JOB="Production Browser E2E" python3 scripts/ci_notify_failure.py + diff --git a/scripts/ci_notify_success.py b/scripts/ci_notify_success.py new file mode 100644 index 000000000..873aa5424 --- /dev/null +++ b/scripts/ci_notify_success.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""发送 CI 成功通知到飞书/项目群 webhook。""" + +import json +import os +import sys +import urllib.request + + +def main() -> int: + webhook = os.environ.get("CI_NOTIFY_WEBHOOK", "") + if not webhook: + print("未配置 CI_NOTIFY_WEBHOOK,跳过成功通知") + print("如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK") + return 0 + + success_job = os.environ.get("SUCCESS_JOB", "Unknown Job") + branch = os.environ.get("GITHUB_REF_NAME", "unknown") + commit = os.environ.get("GITHUB_SHA", "unknown")[:8] + actor = os.environ.get("GITHUB_ACTOR", "unknown") + run_id = os.environ.get("GITHUB_RUN_ID", "unknown") + repo = os.environ.get("GITHUB_REPOSITORY", "unknown") + run_url = f"https://git.xiaoxiajianji.com/{repo}/actions/runs/{run_id}" + + payload = { + "msg_type": "interactive", + "card": { + "header": { + "title": { + "tag": "plain_text", + "content": "✅ CI 构建成功", + }, + "status": "green", + }, + "elements": [ + { + "tag": "div", + "text": { + "tag": "lark_md", + "content": ( + f"**任务**: {success_job}\n" + f"**分支**: {branch}\n" + f"**提交**: {commit}\n" + f"**提交者**: {actor}\n" + f"**Run ID**: {run_id}" + ), + }, + }, + { + "tag": "action", + "actions": [ + { + "tag": "button", + "text": {"tag": "plain_text", "content": "查看构建详情"}, + "url": run_url, + "type": "primary", + } + ], + }, + ], + }, + } + + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + webhook, + data=data, + headers={"Content-Type": "application/json"}, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + resp.read() + print("成功通知已发送") + except Exception as e: + print(f"成功通知发送失败: {e}", file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main())