62967e078b
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 12s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m56s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m47s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 24s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 49s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 30s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 14s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 6m0s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 4m16s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m14s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 9m33s
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 32s
- staging-api-tests.outputs.report: steps.smoke → steps.report (smoke步骤输出的是api_report,不是report;report由report步骤输出) - staging-e2e.outputs.report: steps.smoke → steps.e2e (staging-e2e的step id是e2e,不是smoke) 问题:两个job的outputs引用了错误的step id或变量名,导致汇总报告拿不到正确结果。 修复:修正outputs指向正确的step id和输出变量。
625 lines
26 KiB
YAML
Executable File
625 lines
26 KiB
YAML
Executable File
name: Daily Health Check
|
|
# 注意:使用 curl step_checkout.sh 方式以兼容 docker runner
|
|
|
|
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: ci-l2
|
|
timeout-minutes: 8
|
|
outputs:
|
|
report: ${{ steps.smoke.outputs.report }}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
shell: sh
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
curl -sH "Authorization: token $GITHUB_TOKEN" \
|
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" \
|
|
| bash
|
|
- name: Production health check & smoke test
|
|
id: smoke
|
|
shell: bash
|
|
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
|
|
|
|
- name: Report CI trace
|
|
if: always()
|
|
shell: sh
|
|
env:
|
|
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
|
run: |
|
|
STATUS="ok"
|
|
[ ${{ job.status }} = "success" ] || STATUS="error"
|
|
START_TIME=""
|
|
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
|
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
|
|
|
# ── 2. Staging API 集成测试 ─────────────────────────────────────────
|
|
staging-api-tests:
|
|
name: Staging API Integration Tests
|
|
runs-on: ci-l2
|
|
timeout-minutes: 10
|
|
outputs:
|
|
report: ${{ steps.report.outputs.report }}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
shell: sh
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
curl -sH "Authorization: token $GITHUB_TOKEN" \
|
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" \
|
|
| bash
|
|
- name: Run API smoke test on staging
|
|
id: smoke
|
|
shell: bash
|
|
env:
|
|
STAGING_TEST_USER: ${{ secrets.STAGING_TEST_USER }}
|
|
STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }}
|
|
run: |
|
|
set +e
|
|
START_TIME=$(date +%s)
|
|
chmod +x tests/e2e/api_smoke_test.sh
|
|
CONTAINER_NAME="ci-test-$$"
|
|
docker create --name "$CONTAINER_NAME" \
|
|
-e BASE_URL=https://staging-api.xiaoxiajianji.com \
|
|
-e WEB_URL=https://staging.xiaoxiajianji.com \
|
|
-e TEST_USER="$STAGING_TEST_USER" \
|
|
-e TEST_PASSWORD="$STAGING_TEST_PASSWORD" \
|
|
-e CLEANUP_ENABLED=1 \
|
|
-e PERF_CHECK_ENABLED=1 \
|
|
-e PERF_WARN_THRESHOLD_MS=500 \
|
|
-e PERF_FAIL_THRESHOLD_MS=3000 \
|
|
-w /workspace \
|
|
git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \
|
|
bash tests/e2e/api_smoke_test.sh 2>&1
|
|
docker cp . "$CONTAINER_NAME:/workspace"
|
|
docker start -a "$CONTAINER_NAME" 2>&1 | tee /tmp/staging-api-smoke.log
|
|
SMOKE_EXIT=${PIPESTATUS[0]}
|
|
docker rm "$CONTAINER_NAME" > /dev/null 2>&1 || true
|
|
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: bash
|
|
run: |
|
|
set +e
|
|
START_TIME=$(date +%s)
|
|
CONTAINER_NAME="ci-test-$$"
|
|
docker create --name "$CONTAINER_NAME" \
|
|
-e E2E_BASE_URL=https://staging.xiaoxiajianji.com \
|
|
-e E2E_API_BASE=https://staging-api.xiaoxiajianji.com/api/v1 \
|
|
-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
|
|
docker cp . "$CONTAINER_NAME:/workspace"
|
|
docker start -a "$CONTAINER_NAME" 2>&1 | tee /tmp/staging-api-e2e.log
|
|
EXIT_CODE=${PIPESTATUS[0]}
|
|
docker rm "$CONTAINER_NAME" > /dev/null 2>&1 || true
|
|
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
|
|
|
|
- name: Report CI trace
|
|
if: always()
|
|
shell: sh
|
|
env:
|
|
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
|
run: |
|
|
STATUS="ok"
|
|
[ ${{ job.status }} = "success" ] || STATUS="error"
|
|
START_TIME=""
|
|
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
|
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
|
|
|
# ── 3. Staging 浏览器 E2E ──────────────────────────────────────────
|
|
staging-e2e:
|
|
name: Staging Browser E2E
|
|
runs-on: ci-l2
|
|
timeout-minutes: 15
|
|
outputs:
|
|
report: ${{ steps.e2e.outputs.report }}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
shell: sh
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
curl -sH "Authorization: token $GITHUB_TOKEN" \
|
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" \
|
|
| bash
|
|
- name: Run Playwright E2E on staging
|
|
id: e2e
|
|
shell: bash
|
|
run: |
|
|
set +e
|
|
START_TIME=$(date +%s)
|
|
CONTAINER_NAME="ci-test-$$"
|
|
docker create --name "$CONTAINER_NAME" --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 \
|
|
-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
|
|
docker cp . "$CONTAINER_NAME:/workspace"
|
|
docker start -a "$CONTAINER_NAME" 2>&1 | tee /tmp/staging-e2e.log
|
|
EXIT_CODE=${PIPESTATUS[0]}
|
|
docker rm "$CONTAINER_NAME" > /dev/null 2>&1 || true
|
|
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
|
|
|
|
- name: Report CI trace
|
|
if: always()
|
|
shell: sh
|
|
env:
|
|
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
|
run: |
|
|
STATUS="ok"
|
|
[ ${{ job.status }} = "success" ] || STATUS="error"
|
|
START_TIME=""
|
|
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
|
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
|
|
|
# ── 4. 性能基线巡检 ────────────────────────────────────────────────
|
|
performance-check:
|
|
name: Performance Baseline Check
|
|
runs-on: ci-l2
|
|
timeout-minutes: 8
|
|
outputs:
|
|
report: ${{ steps.report.outputs.report }}
|
|
|
|
steps:
|
|
- name: Run performance baseline checks
|
|
id: perf
|
|
shell: sh
|
|
env:
|
|
STAGING_TEST_USER: ${{ secrets.STAGING_TEST_USER }}
|
|
STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }}
|
|
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 ---"
|
|
LOGIN_BODY="{\"email\":\"$STAGING_TEST_USER\",\"password\":\"$STAGING_TEST_PASSWORD\"}"
|
|
AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d "$LOGIN_BODY" \
|
|
"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 \"$LOGIN_BODY\""
|
|
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
|
|
env:
|
|
STAGING_TEST_USER: ${{ secrets.STAGING_TEST_USER }}
|
|
STAGING_TEST_PASSWORD: ${{ secrets.STAGING_TEST_PASSWORD }}
|
|
run: |
|
|
set +e
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " 性能基线巡检 - 详细报告"
|
|
echo "=========================================="
|
|
|
|
TOTAL=0
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
RESULTS=""
|
|
START_TIME=$(date +%s)
|
|
|
|
LOGIN_BODY="{\"email\":\"$STAGING_TEST_USER\",\"password\":\"$STAGING_TEST_PASSWORD\"}"
|
|
# 先登录获取 token
|
|
AUTH_RESP=$(curl -s -w "\n%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d "$LOGIN_BODY" \
|
|
"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 \"$LOGIN_BODY\""
|
|
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
|
|
|
|
- name: Report CI trace
|
|
if: always()
|
|
shell: sh
|
|
env:
|
|
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
|
run: |
|
|
STATUS="ok"
|
|
[ ${{ job.status }} = "success" ] || STATUS="error"
|
|
START_TIME=""
|
|
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
|
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
|
|
|
# ── 5. 每日巡检汇总报告 ────────────────────────────────────────────
|
|
daily-report:
|
|
name: Daily Check Report
|
|
runs-on: ci-l2
|
|
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
|
|
- name: Report CI trace
|
|
if: always()
|
|
shell: sh
|
|
env:
|
|
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
|
run: |
|
|
STATUS="ok"
|
|
[ ${{ job.status }} = "success" ] || STATUS="error"
|
|
START_TIME=""
|
|
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
|
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|