diff --git a/.gitea/workflows/daily-check.yml b/.gitea/workflows/daily-check.yml new file mode 100644 index 000000000..209ae1283 --- /dev/null +++ b/.gitea/workflows/daily-check.yml @@ -0,0 +1,658 @@ +name: Daily Health Check + +on: + schedule: + - cron: '0 19 * * *' # UTC 19:00 = 北京时间凌晨 3:00 + workflow_dispatch: + +permissions: + contents: read + +jobs: + # ── 1. 生产环境冒烟测试 ───────────────────────────────────────────── + production-smoke: + name: Production Smoke Test + runs-on: saas + timeout-minutes: 8 + outputs: + report: ${{ steps.smoke.outputs.report }} + + steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -eu + python3 - <<'PY' + import io, os, tarfile, time, urllib.request, urllib.error + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + last_err = None + for attempt in range(5): + try: + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() + break + except urllib.error.HTTPError as e: + last_err = e + if e.code >= 500 and attempt < 4: + wait = 2 ** attempt + print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + except Exception as e: + last_err = e + if attempt < 4: + wait = 2 ** attempt + print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + else: + raise last_err + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + PY + + - name: Production health check & smoke test + id: smoke + shell: sh + env: + SMOKE_ENV: production + EXISTING_TOKEN: ${{ secrets.PROD_E2E_TOKEN }} + MODULES: health,assets,generation,subscription,nginx + run: | + set +e + START_TIME=$(date +%s) + chmod +x tests/e2e/api_smoke_test.sh + BASE_URL="https://api.xiaoxiajianji.com" \ + WEB_URL="https://saas.xiaoxiajianji.com" \ + SMOKE_ENV="${SMOKE_ENV}" \ + EXISTING_TOKEN="${EXISTING_TOKEN}" \ + MODULES="${MODULES}" \ + CLEANUP_ENABLED=0 \ + PERF_CHECK_ENABLED=1 \ + PERF_WARN_THRESHOLD_MS=500 \ + PERF_FAIL_THRESHOLD_MS=5000 \ + bash tests/e2e/api_smoke_test.sh 2>&1 | tee /tmp/prod-smoke.log + SMOKE_EXIT=${PIPESTATUS[0]} + END_TIME=$(date +%s) + ELAPSED=$((END_TIME - START_TIME)) + + echo "" + echo "========== 生产冒烟测试报告 ==========" + echo "环境: https://api.xiaoxiajianji.com" + echo "耗时: ${ELAPSED}s" + # 提取通过/失败数 + grep "测试完成:" /tmp/prod-smoke.log || true + if [ "$SMOKE_EXIT" -eq 0 ]; then + echo "结果: PASS" + echo "report=PASS" >> "${GITHUB_OUTPUT}" + else + echo "结果: FAIL" + grep "失败用例:" /tmp/prod-smoke.log || true + echo "report=FAIL" >> "${GITHUB_OUTPUT}" + fi + echo "======================================" + exit $SMOKE_EXIT + + # ── 2. Staging API 集成测试 ───────────────────────────────────────── + staging-api-tests: + name: Staging API Integration Tests + runs-on: saas + timeout-minutes: 10 + outputs: + report: ${{ steps.smoke.outputs.report }} + + steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -eu + python3 - <<'PY' + import io, os, tarfile, time, urllib.request, urllib.error + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + last_err = None + for attempt in range(5): + try: + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() + break + except urllib.error.HTTPError as e: + last_err = e + if e.code >= 500 and attempt < 4: + wait = 2 ** attempt + print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + except Exception as e: + last_err = e + if attempt < 4: + wait = 2 ** attempt + print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + else: + raise last_err + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + PY + + - name: Run API smoke test on staging + id: smoke + shell: sh + run: | + set +e + START_TIME=$(date +%s) + chmod +x tests/e2e/api_smoke_test.sh + docker run --rm \ + -e BASE_URL=https://staging-api.xiaoxiajianji.com \ + -e WEB_URL=https://staging.xiaoxiajianji.com \ + -e TEST_USER=18314979086@163.com \ + -e TEST_PASSWORD=Ying1234 \ + -e CLEANUP_ENABLED=1 \ + -e PERF_CHECK_ENABLED=1 \ + -e PERF_WARN_THRESHOLD_MS=500 \ + -e PERF_FAIL_THRESHOLD_MS=3000 \ + -v "$PWD:/workspace" \ + -w /workspace \ + git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \ + bash tests/e2e/api_smoke_test.sh 2>&1 | tee /tmp/staging-api-smoke.log + SMOKE_EXIT=${PIPESTATUS[0]} + END_TIME=$(date +%s) + ELAPSED=$((END_TIME - START_TIME)) + + echo "" + echo "========== Staging API 冒烟测试报告 ==========" + echo "环境: https://staging-api.xiaoxiajianji.com" + echo "耗时: ${ELAPSED}s" + grep "测试完成:" /tmp/staging-api-smoke.log || true + if [ "$SMOKE_EXIT" -eq 0 ]; then + echo "结果: PASS" + echo "api_report=PASS" >> "${GITHUB_OUTPUT}" + else + echo "结果: FAIL" + grep "失败用例:" /tmp/staging-api-smoke.log || true + echo "api_report=FAIL" >> "${GITHUB_OUTPUT}" + fi + echo "==============================================" + exit $SMOKE_EXIT + + - name: Run Staging API Integration Tests (Playwright) + id: e2e_api + shell: sh + run: | + set +e + START_TIME=$(date +%s) + docker run --rm \ + -e E2E_BASE_URL=https://staging.xiaoxiajianji.com \ + -e E2E_API_BASE=https://staging-api.xiaoxiajianji.com/api/v1 \ + -v "$PWD:/workspace" \ + -w /workspace/apps/web \ + 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" 2>&1 | tee /tmp/staging-api-e2e.log + EXIT_CODE=${PIPESTATUS[0]} + END_TIME=$(date +%s) + ELAPSED=$((END_TIME - START_TIME)) + + echo "" + echo "========== Staging API 集成测试报告 ==========" + echo "环境: https://staging-api.xiaoxiajianji.com" + echo "耗时: ${ELAPSED}s" + grep -E "passed|failed|timed out" /tmp/staging-api-e2e.log || true + if [ "$EXIT_CODE" -eq 0 ]; then + echo "结果: PASS" + echo "int_report=PASS" >> "${GITHUB_OUTPUT}" + else + echo "结果: FAIL" + echo "int_report=FAIL" >> "${GITHUB_OUTPUT}" + fi + echo "==============================================" + exit $EXIT_CODE + + - name: Set report output + id: report + shell: sh + run: | + if [ "${{ steps.smoke.outputs.api_report }}" = "PASS" ] && [ "${{ steps.e2e_api.outputs.int_report }}" = "PASS" ]; then + echo "report=PASS" >> "${GITHUB_OUTPUT}" + else + echo "report=FAIL" >> "${GITHUB_OUTPUT}" + fi + + # ── 3. Staging 浏览器 E2E ────────────────────────────────────────── + staging-e2e: + name: Staging Browser E2E + runs-on: saas + timeout-minutes: 15 + outputs: + report: ${{ steps.smoke.outputs.report }} + + steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -eu + python3 - <<'PY' + import io, os, tarfile, time, urllib.request, urllib.error + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + last_err = None + for attempt in range(5): + try: + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() + break + except urllib.error.HTTPError as e: + last_err = e + if e.code >= 500 and attempt < 4: + wait = 2 ** attempt + print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + except Exception as e: + last_err = e + if attempt < 4: + wait = 2 ** attempt + print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + else: + raise last_err + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + PY + + - name: Run Playwright E2E on staging + id: e2e + shell: sh + run: | + set +e + START_TIME=$(date +%s) + docker run --rm --ipc=host \ + -e E2E_BASE_URL=https://staging.xiaoxiajianji.com \ + -e E2E_API_BASE=https://staging-api.xiaoxiajianji.com/api/v1 \ + -e E2E_BROWSER_CHANNEL=chromium \ + -e PLAYWRIGHT_HEADLESS=1 \ + -v "$PWD:/workspace" \ + -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' 2>&1 | tee /tmp/staging-e2e.log + EXIT_CODE=${PIPESTATUS[0]} + END_TIME=$(date +%s) + ELAPSED=$((END_TIME - START_TIME)) + + echo "" + echo "========== Staging E2E 测试报告 ==========" + echo "环境: https://staging.xiaoxiajianji.com" + echo "耗时: ${ELAPSED}s" + grep -E "passed|failed|timed out" /tmp/staging-e2e.log || true + if [ "$EXIT_CODE" -eq 0 ]; then + echo "结果: PASS" + echo "report=PASS" >> "${GITHUB_OUTPUT}" + else + echo "结果: FAIL" + echo "report=FAIL" >> "${GITHUB_OUTPUT}" + fi + echo "==========================================" + exit $EXIT_CODE + + # ── 4. 性能基线巡检 ──────────────────────────────────────────────── + performance-check: + name: Performance Baseline Check + runs-on: saas + timeout-minutes: 8 + outputs: + report: ${{ steps.report.outputs.report }} + + steps: + - name: Run performance baseline checks + id: perf + shell: sh + run: | + set +e + START_TIME=$(date +%s) + echo "==========================================" + echo " 性能基线巡检 - Staging API" + echo " 目标: https://staging-api.xiaoxiajianji.com" + echo "==========================================" + echo "" + + TOTAL=0 + PASS=0 + FAIL=0 + WARN=0 + WARN_LIST="" + FAIL_LIST="" + + # 核心接口配置: 名称|路径|方法|阈值(ms)|失败阈值(ms) + # 核心接口(core): 500ms + # 普通接口(normal): 1000ms + # 重操作接口(heavy): 3000ms + ENDPOINTS=" + 登录|/api/v1/auth/login|POST|500|3000 + 获取当前用户|/api/v1/auth/me|GET|500|3000 + 项目列表|/api/v1/projects|GET|500|3000 + 素材列表|/api/v1/assets|GET|500|3000 + 模板列表|/api/v1/templates|GET|500|3000 + 剪辑计划列表|/api/v1/edit-plans|GET|500|3000 + 生成任务列表|/api/v1/generation/tasks|GET|500|3000 + 订阅信息|/api/v1/subscription/current|GET|500|3000 + 音色列表|/api/v1/voices|GET|1000|5000 + 健康检查|/health|GET|200|1000 + " + + # 先登录获取 token + echo "--- 准备: 获取测试 Token ---" + AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -d '{"email":"18314979086@163.com","password":"Ying1234"}' \ + "https://staging-api.xiaoxiajianji.com/api/v1/auth/login" \ + --max-time 10 2>&1) + AUTH_CODE=$(echo "$AUTH_RESP" | tail -1) + AUTH_BODY=$(echo "$AUTH_RESP" | sed '$d') + + if [ "$AUTH_CODE" = "200" ]; then + TOKEN=$(echo "$AUTH_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('access_token',''))" 2>/dev/null) + if [ -n "$TOKEN" ]; then + echo "Token 获取成功" + else + echo "Token 解析失败,部分接口可能无法测试" + TOKEN="" + fi + else + echo "登录失败 (HTTP $AUTH_CODE),部分接口将跳过鉴权测试" + TOKEN="" + fi + + echo "" + echo "--- 开始性能测试 ---" + echo "" + + echo "$ENDPOINTS" | while IFS='|' read -r name path method warn_ms fail_ms; do + [ -z "$name" ] && continue + TOTAL=$((TOTAL + 1)) + + # 构建 curl 命令 + CURL_ARGS="-s -o /dev/null -w '%{http_code} %{time_total}' --max-time 30" + if [ "$method" = "POST" ]; then + CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"18314979086@163.com\",\"password\":\"Ying1234\"}'" + fi + if [ -n "$TOKEN" ] && [ "$name" != "健康检查" ]; then + CURL_ARGS="$CURL_ARGS -H 'Authorization: Bearer $TOKEN'" + fi + + # 执行请求 + RESP=$(eval curl $CURL_ARGS "https://staging-api.xiaoxiajianji.com${path}" 2>&1) + HTTP_CODE=$(echo "$RESP" | awk '{print $1}') + TIME_TOTAL=$(echo "$RESP" | awk '{print $2}') + ELAPSED_MS=$(python3 -c "print(int(float('${TIME_TOTAL:-0}') * 1000))" 2>/dev/null || echo "0") + + if [ "$HTTP_CODE" -ge 500 ] 2>/dev/null; then + FAIL=$((FAIL + 1)) + FAIL_LIST="$FAIL_LIST\n ❌ $name - HTTP $HTTP_CODE (${ELAPSED_MS}ms)" + echo "❌ $name - HTTP $HTTP_CODE - ${ELAPSED_MS}ms (FAIL)" + elif [ "$ELAPSED_MS" -ge "$fail_ms" ] 2>/dev/null; then + FAIL=$((FAIL + 1)) + FAIL_LIST="$FAIL_LIST\n ❌ $name - ${ELAPSED_MS}ms > ${fail_ms}ms" + echo "❌ $name - ${ELAPSED_MS}ms > ${fail_ms}ms (FAIL)" + elif [ "$ELAPSED_MS" -ge "$warn_ms" ] 2>/dev/null; then + WARN=$((WARN + 1)) + WARN_LIST="$WARN_LIST\n ⚠️ $name - ${ELAPSED_MS}ms > ${warn_ms}ms" + echo "⚠️ $name - ${ELAPSED_MS}ms (WARN, threshold: ${warn_ms}ms)" + PASS=$((PASS + 1)) + else + PASS=$((PASS + 1)) + echo "✅ $name - ${ELAPSED_MS}ms (OK, threshold: ${warn_ms}ms)" + fi + done + + # 由于 while 在子 shell 中执行,用文件传递结果 + # 重新跑一次用文件计数方式 + echo "" + echo "--- 汇总性能数据 ---" + + END_TIME=$(date +%s) + ELAPSED=$((END_TIME - START_TIME)) + + echo "" + echo "========== 性能基线巡检报告 ==========" + echo "环境: https://staging-api.xiaoxiajianji.com" + echo "耗时: ${ELAPSED}s" + echo "======================================" + + - name: Generate performance report + id: report + shell: sh + run: | + set +e + echo "" + echo "==========================================" + echo " 性能基线巡检 - 详细报告" + echo "==========================================" + + TOTAL=0 + PASS=0 + FAIL=0 + WARN=0 + RESULTS="" + START_TIME=$(date +%s) + + # 先登录获取 token + AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -d '{"email":"18314979086@163.com","password":"Ying1234"}' \ + "https://staging-api.xiaoxiajianji.com/api/v1/auth/login" \ + --max-time 10 2>&1) + AUTH_CODE=$(echo "$AUTH_RESP" | tail -1) + AUTH_BODY=$(echo "$AUTH_RESP" | sed '$d') + TOKEN="" + if [ "$AUTH_CODE" = "200" ]; then + TOKEN=$(echo "$AUTH_BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('access_token',''))" 2>/dev/null || echo "") + fi + + run_perf_test() { + local name="$1" path="$2" method="$3" warn_ms="$4" fail_ms="$5" + TOTAL=$((TOTAL + 1)) + + local CURL_ARGS="-s -o /dev/null -w '%{http_code} %{time_total}' --max-time 30" + if [ "$method" = "POST" ]; then + CURL_ARGS="$CURL_ARGS -X POST -H 'Content-Type: application/json' -d '{\"email\":\"18314979086@163.com\",\"password\":\"Ying1234\"}'" + fi + if [ -n "$TOKEN" ] && [ "$name" != "健康检查" ]; then + CURL_ARGS="$CURL_ARGS -H 'Authorization: Bearer $TOKEN'" + fi + + local RESP=$(eval curl $CURL_ARGS "https://staging-api.xiaoxiajianji.com${path}" 2>&1) + local HTTP_CODE=$(echo "$RESP" | awk '{print $1}') + local TIME_TOTAL=$(echo "$RESP" | awk '{print $2}') + local ELAPSED_MS=$(python3 -c "print(int(float('${TIME_TOTAL:-0}') * 1000))" 2>/dev/null || echo "0") + + if echo "$HTTP_CODE" | grep -q "^[5]"; then + FAIL=$((FAIL + 1)) + RESULTS="$RESULTS\n ❌ $name - HTTP $HTTP_CODE (${ELAPSED_MS}ms)" + echo "❌ $name - HTTP $HTTP_CODE - ${ELAPSED_MS}ms [FAIL]" + return 1 + elif [ "$ELAPSED_MS" -ge "$fail_ms" ] 2>/dev/null; then + FAIL=$((FAIL + 1)) + RESULTS="$RESULTS\n ❌ $name - ${ELAPSED_MS}ms > ${fail_ms}ms [FAIL]" + echo "❌ $name - ${ELAPSED_MS}ms > ${fail_ms}ms [FAIL]" + return 1 + elif [ "$ELAPSED_MS" -ge "$warn_ms" ] 2>/dev/null; then + WARN=$((WARN + 1)) + PASS=$((PASS + 1)) + RESULTS="$RESULTS\n ⚠️ $name - ${ELAPSED_MS}ms (阈值: ${warn_ms}ms) [WARN]" + echo "⚠️ $name - ${ELAPSED_MS}ms > 阈值 ${warn_ms}ms [WARN]" + return 0 + else + PASS=$((PASS + 1)) + RESULTS="$RESULTS\n ✅ $name - ${ELAPSED_MS}ms (阈值: ${warn_ms}ms) [OK]" + echo "✅ $name - ${ELAPSED_MS}ms (阈值: ${warn_ms}ms) [OK]" + return 0 + fi + } + + echo "" + echo "=== 核心接口 (阈值: 500ms / 3000ms) ===" + run_perf_test "登录" "/api/v1/auth/login" "POST" 500 3000 || true + run_perf_test "获取当前用户" "/api/v1/auth/me" "GET" 500 3000 || true + run_perf_test "项目列表" "/api/v1/projects" "GET" 500 3000 || true + run_perf_test "素材列表" "/api/v1/assets" "GET" 500 3000 || true + run_perf_test "模板列表" "/api/v1/templates" "GET" 500 3000 || true + run_perf_test "剪辑计划列表" "/api/v1/edit-plans" "GET" 500 3000 || true + run_perf_test "生成任务列表" "/api/v1/generation/tasks" "GET" 500 3000 || true + run_perf_test "订阅信息" "/api/v1/subscription/current" "GET" 500 3000 || true + + echo "" + echo "=== 普通接口 (阈值: 1000ms / 5000ms) ===" + run_perf_test "音色列表" "/api/v1/voices" "GET" 1000 5000 || true + + echo "" + echo "=== 基础接口 (阈值: 200ms / 1000ms) ===" + run_perf_test "健康检查" "/health" "GET" 200 1000 || true + + END_TIME=$(date +%s) + ELAPSED=$((END_TIME - START_TIME)) + + echo "" + echo "========== 性能基线巡检报告 ==========" + echo "环境: https://staging-api.xiaoxiajianji.com" + echo "总接口: ${TOTAL}" + echo "通过: ${PASS}" + echo "失败: ${FAIL}" + echo "警告: ${WARN}" + echo "耗时: ${ELAPSED}s" + echo "======================================" + + # 写入结果文件供 report job 使用 + echo "${TOTAL}" > /tmp/perf_total + echo "${PASS}" > /tmp/perf_pass + echo "${FAIL}" > /tmp/perf_fail + echo "${WARN}" > /tmp/perf_warn + echo "${ELAPSED}" > /tmp/perf_elapsed + + if [ "$FAIL" -gt 0 ]; then + echo "report=FAIL" >> "${GITHUB_OUTPUT}" + echo "perf_detail=fail:${FAIL}:warn:${WARN}" >> "${GITHUB_OUTPUT}" + exit 1 + else + echo "report=PASS" >> "${GITHUB_OUTPUT}" + if [ "$WARN" -gt 0 ]; then + echo "perf_detail=pass:warn:${WARN}" >> "${GITHUB_OUTPUT}" + else + echo "perf_detail=pass" >> "${GITHUB_OUTPUT}" + fi + exit 0 + fi + + # ── 5. 每日巡检汇总报告 ──────────────────────────────────────────── + daily-report: + name: Daily Check Report + runs-on: saas + timeout-minutes: 2 + if: always() + needs: + - production-smoke + - staging-api-tests + - staging-e2e + - performance-check + + steps: + - name: Print summary report + shell: sh + run: | + echo "" + echo "╔══════════════════════════════════════════════════════╗" + echo "║ 每日巡检报告 ║" + echo "╠══════════════════════════════════════════════════════╣" + + # 获取各 job 状态 + PROD_STATUS="${{ needs.production-smoke.result }}" + STAGING_API_STATUS="${{ needs.staging-api-tests.result }}" + STAGING_E2E_STATUS="${{ needs.staging-e2e.result }}" + PERF_STATUS="${{ needs.performance-check.result }}" + + format_result() { + if [ "$1" = "success" ]; then + echo "✅ PASS" + elif [ "$1" = "failure" ]; then + echo "❌ FAIL" + elif [ "$1" = "skipped" ]; then + echo "⏭️ SKIP" + else + echo "❓ UNKNOWN ($1)" + fi + } + + echo "║" + echo "║ 生产冒烟测试: $(format_result "$PROD_STATUS")" + echo "║ Staging API: $(format_result "$STAGING_API_STATUS")" + echo "║ Staging E2E: $(format_result "$STAGING_E2E_STATUS")" + echo "║ 性能基线巡检: $(format_result "$PERF_STATUS")" + echo "║" + echo "║ 巡检时间: $(date '+%Y-%m-%d %H:%M:%S UTC')" + echo "║" + + # 判断整体状态 + ALL_PASS=true + FAILED_ITEMS="" + for status_name in "$PROD_STATUS:生产冒烟" "$STAGING_API_STATUS:Staging API" "$STAGING_E2E_STATUS:Staging E2E" "$PERF_STATUS:性能基线"; do + STATUS=$(echo "$status_name" | cut -d: -f1) + NAME=$(echo "$status_name" | cut -d: -f2) + if [ "$STATUS" != "success" ] && [ "$STATUS" != "skipped" ]; then + ALL_PASS=false + FAILED_ITEMS="$FAILED_ITEMS $NAME" + fi + done + + echo "╠══════════════════════════════════════════════════════╣" + if [ "$ALL_PASS" = "true" ]; then + echo "║ 整体状态: ✅ 全部通过 ║" + else + echo "║ 整体状态: ❌ 存在失败 ║" + echo "║ 失败项: ${FAILED_ITEMS} ║" + fi + echo "╚══════════════════════════════════════════════════════╝" + echo "" + + # 如果有失败项,以非零退出码结束(方便 Gitea 标记流水线失败) + if [ "$ALL_PASS" = "false" ]; then + echo "⚠️ 部分巡检项失败,请检查上方日志获取详细信息。" + # 不 exit 1,因为我们用了 always(),保持 report job 成功, + # 但其他失败的 job 已经让整体流水线标记为失败 + fi diff --git a/tests/e2e/api_smoke_test.sh b/tests/e2e/api_smoke_test.sh index 1ccd96ef1..a59ceb258 100755 --- a/tests/e2e/api_smoke_test.sh +++ b/tests/e2e/api_smoke_test.sh @@ -22,6 +22,31 @@ set -euo pipefail +# ===== 环境预设 (SMOKE_ENV) ===== +# 支持 SMOKE_ENV=production / staging 快捷预设 +SMOKE_ENV="${SMOKE_ENV:-}" +if [ "$SMOKE_ENV" = "production" ]; then + # 生产环境预设:安全优先,默认只读 + BASE_URL="${BASE_URL:-https://api.xiaoxiajianji.com}" + WEB_URL="${WEB_URL:-https://saas.xiaoxiajianji.com}" + # 如果没有提供 EXISTING_TOKEN,默认只跑不需要鉴权的模块(只读) + if [ -z "${EXISTING_TOKEN:-}" ]; then + MODULES="${MODULES:-health,nginx}" + else + # 有 token 时跑只读安全模块 + MODULES="${MODULES:-health,assets,generation,subscription,nginx}" + fi + CLEANUP_ENABLED="${CLEANUP_ENABLED:-0}" + PRODUCTION_MODE=1 +elif [ "$SMOKE_ENV" = "staging" ]; then + BASE_URL="${BASE_URL:-https://staging-api.xiaoxiajianji.com}" + WEB_URL="${WEB_URL:-https://staging.xiaoxiajianji.com}" + CLEANUP_ENABLED="${CLEANUP_ENABLED:-1}" + PRODUCTION_MODE=0 +else + PRODUCTION_MODE=0 +fi + # ===== 配置 ===== BASE_URL="${BASE_URL:-}" TEST_USER="${TEST_USER:-e2e_$(date +%s)}" @@ -717,6 +742,13 @@ main() { echo "║ API E2E 冒烟测试 ║" echo "╚══════════════════════════════════════════════════╝" echo "" + if [ "$PRODUCTION_MODE" = "1" ]; then + echo "⚠️ 生产环境模式 - 安全只读" + echo " - 不注册新用户" + echo " - 不创建测试数据" + echo " - CLEANUP_ENABLED=0" + echo "" + fi echo "环境: $BASE_URL" echo "模块: $MODULES" echo "清理: $CLEANUP_ENABLED"