|
8250
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
后端构建检查
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
backend-build:
name: 后端构建检查
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 构建
run: pnpm --filter juhi-api run build
- name: 验证构建产物
run: |
if [ ! -d "backend/dist" ]; then
echo "❌ 构建产物不存在"
exit 1
fi
echo "✅ 后端构建验证通过"
...
|
backend-build
|
["backend-lint"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8251
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
前端代码检查
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
frontend-lint:
name: 前端代码检查
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.frontend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: ESLint 检查
run: pnpm --filter juhi-frontend run lint
- name: 数组守卫检查
run: pnpm --filter juhi-frontend run check:array-guard
- name: TypeScript 类型检查
run: pnpm --filter juhi-frontend run type-check
...
|
frontend-lint
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
1
|
|
0
|
Edit
Delete
|
|
8252
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
前端构建检查
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
frontend-build:
name: 前端构建检查
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: 构建
run: pnpm --filter juhi-frontend run build
- name: 验证构建产物
run: |
if [ ! -d "frontend/dist" ]; then
echo "❌ 构建产物不存在"
exit 1
fi
echo "✅ 前端构建验证通过"
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: frontend-dist
path: frontend/dist
retention-days: "7"
...
|
frontend-build
|
["frontend-lint"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8253
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
移动端检查
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
mobile-check:
name: 移动端检查
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.mobile == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: ESLint 检查
run: pnpm --filter juhi-mobile run lint || true
- name: TypeScript 类型检查
run: pnpm --filter juhi-mobile run type-check || true
...
|
mobile-check
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
1
|
|
0
|
Edit
Delete
|
|
8254
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
多租户安全审计
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
security-audit:
name: 多租户安全审计
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行多租户安全审计(阻塞性)
run: pnpm --filter juhi-api run audit:tenant
- if: always()
name: 保存安全审计报告
run: |
pnpm --filter juhi-api run audit:tenant:fix --dry-run > security-audit-report.txt 2>&1 || true
- if: always()
name: 上传安全审计报告
uses: actions/upload-artifact@v4
with:
name: security-audit-report
path: security-audit-report.txt
retention-days: "30"
...
|
security-audit
|
["backend-lint"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8255
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
Kafka 事件一致性审计
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
kafka-audit:
name: Kafka 事件一致性审计
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行 Kafka 事件一致性审计
run: |
cd backend
npx tsx scripts/audit-kafka-events.ts --ci
continue-on-error: true
- if: always()
name: 保存审计报告
run: |
cd backend
npx tsx scripts/audit-kafka-events.ts --json > kafka-audit-report.json || true
- if: always()
name: 上传审计报告
uses: actions/upload-artifact@v4
with:
name: kafka-audit-report
path: backend/kafka-audit-report.json
retention-days: "30"
...
|
kafka-audit
|
["backend-lint"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8256
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
依赖安全审计
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
dependency-audit:
name: 依赖安全审计
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: pnpm 依赖审计
run: pnpm audit --audit-level high || true
...
|
dependency-audit
|
["setup"]
|
["ubuntu-latest"]
|
0
|
4
|
0
|
0
|
1773912912
|
1773912956
|
|
1
|
|
0
|
Edit
Delete
|
|
8257
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
Docker 镜像构建
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
docker-build:
name: Docker 镜像构建
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
steps:
- uses: actions/checkout@v4
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 构建后端镜像
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: ./backend
push: "false"
tags: juhi-api:${{ github.sha }}
- name: 构建前端镜像
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: ./frontend
push: "false"
tags: juhi-frontend:${{ github.sha }}
...
|
docker-build
|
["backend-build","frontend-build"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8258
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
E2E 测试
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
e2e-test:
name: E2E 测试
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
mobile/node_modules
shared/node_modules
shared/dist
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 初始化测试数据库
run: |
cd backend
npx prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/juhi_e2e
- name: 启动后端服务
run: |
cd backend
pnpm run dev &
sleep 15
env:
DATABASE_URL: postgresql://test:test@localhost:5432/juhi_e2e
REDIS_URL: redis://localhost:6379
JWT_SECRET: e2e-test-secret-key
NODE_ENV: test
PORT: 3000
- name: 后端健康检查
run: |
curl -f http://localhost:3000/health || exit 1
- name: 安装 Playwright
run: |
cd e2e
npx playwright install --with-deps chromium
- name: 运行 E2E 测试
run: |
cd e2e
pnpm run test || true
env:
E2E_BASE_URL: http://localhost:5173
E2E_API_URL: http://localhost:3000
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: 上传测试报告
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: e2e/playwright-report
retention-days: "7"
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: juhi_e2e
POSTGRES_PASSWORD: test
POSTGRES_USER: test
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
...
|
e2e-test
|
["backend-build","frontend-build"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8259
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
触发专用测试套件
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
trigger-test-suite:
name: 触发专用测试套件
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
steps:
- name: "\U0001F4DD 触发测试套件信息"
run: "echo \"## \U0001F9EA 专用测试工作流\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"以下专用测试工作流已自动触发:\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"- **Test Suite** (test.yml): 单元测试、API 测试、状态机测试\" >> $GITHUB_STEP_SUMMARY\necho \"- **E2E Tests** (e2e.yml): 端到端测试、业务流程测试\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"查看 Actions 页面了解详细测试结果。\" >> $GITHUB_STEP_SUMMARY\n"
...
|
trigger-test-suite
|
["backend-build","frontend-build"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8260
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
代码质量门控
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
quality-gate:
name: 代码质量门控
runs-on: ubuntu-latest
if: always()
steps:
- name: 检查所有任务状态
run: "echo \"## \U0001F4CA CI 验证结果\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"| 检查项 | 状态 |\" >> $GITHUB_STEP_SUMMARY\necho \"|--------|------|\" >> $GITHUB_STEP_SUMMARY\n\n# 后端测试\nif [ \"${{ needs.backend-test.result }}\" == \"success\" ]; then\n echo \"| ✅ 后端测试 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.backend-test.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ 后端测试 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ 后端测试 | 失败 |\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# 后端构建\nif [ \"${{ needs.backend-build.result }}\" == \"success\" ]; then\n echo \"| ✅ 后端构建 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.backend-build.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ 后端构建 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ 后端构建 | 失败 |\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# 前端构建\nif [ \"${{ needs.frontend-build.result }}\" == \"success\" ]; then\n echo \"| ✅ 前端构建 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.frontend-build.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ 前端构建 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ 前端构建 | 失败 |\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# 安全审计(阻塞性 - 2026-02-22 升级)\nif [ \"${{ needs.security-audit.result }}\" == \"success\" ]; then\n echo \"| ✅ 安全审计 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.security-audit.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ 安全审计 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ 安全审计 | 失败(多租户隔离 CRITICAL 问题) |\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# Kafka 审计\nif [ \"${{ needs.kafka-audit.result }}\" == \"success\" ]; then\n echo \"| ✅ Kafka 审计 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.kafka-audit.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ Kafka 审计 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ⚠️ Kafka 审计 | 警告 |\" >> $GITHUB_STEP_SUMMARY\nfi\n"
- name: 验证门控
run: "BACKEND_TEST=\"${{ needs.backend-test.result }}\"\nBACKEND_BUILD=\"${{ needs.backend-build.result }}\"\nFRONTEND_BUILD=\"${{ needs.frontend-build.result }}\"\nSECURITY_AUDIT=\"${{ needs.security-audit.result }}\"\n\n# 跳过的任务视为通过\nif [ \"$BACKEND_BUILD\" == \"failure\" ] || [ \"$FRONTEND_BUILD\" == \"failure\" ]; then\n echo \"❌ 构建失败,代码质量门控未通过\"\n exit 1\nfi\n\nif [ \"$BACKEND_TEST\" == \"failure\" ]; then\n echo \"❌ 测试失败,代码质量门控未通过\"\n exit 1\nfi\n\n# \U0001F512 安全升级(2026-02-22):安全审计失败也阻断 CI\n# 多租户隔离是 P0 红线,CRITICAL 级别问题不允许合并\nif [ \"$SECURITY_AUDIT\" == \"failure\" ]; then\n echo \"❌ 多租户安全审计失败,存在 CRITICAL 级别数据安全风险,代码质量门控未通过\"\n echo \"请运行 'cd backend && npm run audit:tenant' 查看详情,并运行 'npm run audit:tenant:fix' 自动修复\"\n exit 1\nfi\n\necho \"✅ 代码质量门控通过(含安全审计)\"\n"
...
|
quality-gate
|
["backend-test","backend-build", ["backend-test","backend-build","frontend-build","security-audit","kafka-audit"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8261
|
6995
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
CI 完成通知
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
ci-complete:
name: CI 完成通知
runs-on: ubuntu-latest
if: always()
steps:
- name: "\U0001F4E2 生成 CI 完成报告"
run: "echo \"## \U0001F389 CI 流程完成\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"**分支**: ${{ github.ref_name }}\" >> $GITHUB_STEP_SUMMARY\necho \"**提交**: ${{ github.sha }}\" >> $GITHUB_STEP_SUMMARY\necho \"**触发者**: ${{ github.actor }}\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\n\necho \"### \U0001F4CB 任务汇总\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\n\n# 质量门控\nif [ \"${{ needs.quality-gate.result }}\" == \"success\" ]; then\n echo \"- ✅ 代码质量门控: 通过\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"- ❌ 代码质量门控: 失败\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# E2E 测试\nif [ \"${{ needs.e2e-test.result }}\" == \"success\" ]; then\n echo \"- ✅ E2E 测试: 通过\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.e2e-test.result }}\" == \"skipped\" ]; then\n echo \"- ⏭️ E2E 测试: 跳过\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"- ❌ E2E 测试: 失败\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# Docker 构建\nif [ \"${{ needs.docker-build.result }}\" == \"success\" ]; then\n echo \"- ✅ Docker 镜像构建: 通过\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.docker-build.result }}\" == \"skipped\" ]; then\n echo \"- ⏭️ Docker 镜像构建: 跳过\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"- ❌ Docker 镜像构建: 失败\" >> $GITHUB_STEP_SUMMARY\nfi\n\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"---\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"### \U0001F517 相关链接\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"- [查看完整测试报告](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})\" >> $GITHUB_STEP_SUMMARY\necho \"- [查看覆盖率报告](https://codecov.io/gh/${{ github.repository }})\" >> $GITHUB_STEP_SUMMARY\n"
...
|
ci-complete
|
["quality-gate","e2e-test","do ["quality-gate","e2e-test","docker-build"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913343
|
1773912912
|
1773913343
|
|
0
|
|
0
|
Edit
Delete
|
|
8262
|
6996
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
质量门禁
|
1
|
name: Deploy
"on":
push:
branc name: Deploy
"on":
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- rollback
skip_tests:
description: '跳过测试(紧急修复时使用)'
required: false
default: false
type: boolean
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
quality-check:
name: 质量门禁
runs-on: ubuntu-latest
if: >-
!(github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'rollback') && !(github.event_name == 'workflow_dispatch' && github.event.inputs.skip_tests == 'true')
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 后端类型检查
run: npx tsc --noEmit
working-directory: backend
- name: 前端类型检查
run: npx vue-tsc --noEmit
working-directory: frontend
- name: 后端测试
run: npm run test:run -- --reporter=default --reporter=junit --outputFile=test-results.xml
working-directory: backend
env:
NODE_ENV: test
- if: always()
name: 上传测试结果
uses: actions/upload-artifact@v4
with:
name: test-results
path: backend/test-results.xml
retention-days: "7"
timeout-minutes: "15"
...
|
quality-check
|
null
|
["ubuntu-latest"]
|
7129
|
2
|
1773912956
|
1773912981
|
1773912912
|
1773912982
|
|
0
|
|
0
|
Edit
Delete
|
|
8263
|
6996
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
构建并推送镜像
|
0
|
name: Deploy
"on":
push:
branc name: Deploy
"on":
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- rollback
skip_tests:
description: '跳过测试(紧急修复时使用)'
required: false
default: false
type: boolean
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
build-and-push:
name: 构建并推送镜像
runs-on: ubuntu-latest
if: >-
always() && (
needs.quality-check.result == 'success' ||
needs.quality-check.result == 'skipped'
)
steps:
- name: 检出代码
uses: actions/checkout@v4
- id: version
name: 获取版本号
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
else
VERSION=${{ github.sha }}
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录容器仓库
uses: docker/login-action@v3
with:
password: ${{ secrets.GITHUB_TOKEN }}
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
- id: meta-api
name: 后端镜像元数据
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: 构建并推送后端镜像
uses: docker/build-push-action@v5
with:
build-args: |
NODE_ENV=production
cache-from: type=gha
cache-to: type=gha,mode=max
context: ./backend
labels: ${{ steps.meta-api.outputs.labels }}
push: "true"
tags: ${{ steps.meta-api.outputs.tags }}
- id: meta-frontend
name: 前端镜像元数据
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-frontend
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: 构建并推送前端镜像
uses: docker/build-push-action@v5
with:
build-args: |
VITE_API_BASE_URL=/v1
cache-from: type=gha
cache-to: type=gha,mode=max
context: ./frontend
labels: ${{ steps.meta-frontend.outputs.labels }}
push: "true"
tags: ${{ steps.meta-frontend.outputs.tags }}
timeout-minutes: "20"
outputs:
api-image: ${{ steps.meta-api.outputs.tags }}
frontend-image: ${{ steps.meta-frontend.outputs.tags }}
version: ${{ steps.version.outputs.version }}
permissions:
contents: read
packages: write
...
|
build-and-push
|
["quality-check"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913345
|
1773912912
|
1773913345
|
|
1
|
|
0
|
Edit
Delete
|
|
8264
|
6996
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
部署到 Staging
|
0
|
name: Deploy
"on":
push:
branc name: Deploy
"on":
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- rollback
skip_tests:
description: '跳过测试(紧急修复时使用)'
required: false
default: false
type: boolean
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'staging'
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
# 同步 docker-compose 和脚本到服务器
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging 服务器
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
# 更新镜像标签
export API_IMAGE="${API_IMAGE}"
export FRONTEND_IMAGE="${FRONTEND_IMAGE}"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 执行数据库迁移(Prisma)
echo "==> 执行数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败,终止部署"
exit 1
fi
# 滚动更新
docker compose -f docker-compose.prod.yml up -d --no-deps api
echo "==> 等待 API 服务就绪..."
# 健康检查(带重试)
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "API 健康检查超时(60s),部署失败"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (\${RETRY}/\${MAX_RETRY})"
sleep 5
done
echo "==> API 健康检查通过"
# 更新前端
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
# 重载 Nginx(应用新配置)
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
# 清理旧镜像
docker image prune -f
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
# 运行快速验证(如果脚本存在)
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick || echo "⚠️ 部署验证有警告,请检查日志"
else
# 基础健康检查
curl -sf http://localhost:3000/health || exit 1
echo "基础健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
- if: always()
name: Slack 通知
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: Staging 部署 ${{ job.status }} - ${{ needs.build-and-push.outputs.version }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
timeout-minutes: "15"
...
|
deploy-staging
|
["build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913345
|
1773912912
|
1773913345
|
|
0
|
|
0
|
Edit
Delete
|
|
8265
|
6996
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
部署到 Production
|
0
|
name: Deploy
"on":
push:
branc name: Deploy
"on":
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- rollback
skip_tests:
description: '跳过测试(紧急修复时使用)'
required: false
default: false
type: boolean
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-production:
name: 部署到 Production
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.environment == 'production'
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
# 运行部署前检查(如果脚本存在)
if [ -f "./scripts/pre-deploy-check.sh" ]; then
chmod +x ./scripts/pre-deploy-check.sh
./scripts/pre-deploy-check.sh || {
echo "部署前检查未通过,终止部署"
exit 1
}
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> 执行部署前数据库备份..."
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql"
docker compose -f docker-compose.prod.yml exec -T postgres \
pg_dump -U "${DB_USER:-juhi}" -d "${DB_NAME:-juhi_revops}" -Fc > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo "==> 备份完成: $BACKUP_FILE ($BACKUP_SIZE)"
else
echo "==> 备份失败,终止部署"
exit 1
fi
# 清理 30 天前的旧备份
find "$BACKUP_DIR" -name "pre_deploy_*.sql" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- name: 部署到 Production 服务器
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
# 更新镜像标签
export API_IMAGE="${API_IMAGE}"
export FRONTEND_IMAGE="${FRONTEND_IMAGE}"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 执行数据库迁移(Prisma)
echo "==> 执行数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败,终止部署"
exit 1
fi
# 记录部署历史(回滚用)
CURRENT_API=\$(docker inspect --format='{{.Config.Image}}' juhi-api 2>/dev/null || echo "none")
CURRENT_FE=\$(docker inspect --format='{{.Config.Image}}' juhi-frontend 2>/dev/null || echo "none")
echo "\$(date -Iseconds)|\${CURRENT_API}|\${CURRENT_FE}" >> .deploy-history
# 只保留最近 20 条部署历史
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
# 蓝绿部署 - 启动新 API 容器
echo "==> 蓝绿部署:启动新实例..."
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=2 api
# 健康检查新实例(带重试)
echo "==> 等待新实例就绪..."
RETRY=0
MAX_RETRY=15
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "新实例健康检查超时(75s),回滚到单实例"
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=1 api
exit 1
fi
echo " 等待就绪... (\${RETRY}/\${MAX_RETRY})"
sleep 5
done
echo "==> 新实例健康检查通过"
# 切换流量 - 缩减到新实例
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=1 api
# 更新前端
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
# 重载 Nginx
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
# 清理旧镜像
docker image prune -f
# 记录部署版本
echo "${VERSION}" > .deployed_version
echo "==> 部署完成:版本 ${VERSION}"
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> 执行部署后验证..."
# 运行部署后验证(如果脚本存在)
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick || {
echo "部署验证未通过"
exit 1
}
else
# 基础验证
# 1. API 健康检查
curl -sf http://localhost:3000/health || { echo "API 健康检查失败"; exit 1; }
echo "API 健康检查通过"
# 2. Nginx 代理检查
curl -sf http://localhost/health || echo "⚠️ Nginx 代理检查跳过"
# 3. 检查容器状态
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
echo "发现不健康的容器:"
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "所有容器状态正常"
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
- if: always()
name: Slack 通知
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Production 部署 ${{ job.status }} - 版本: ${{ needs.build-and-push.outputs.version }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
timeout-minutes: "20"
...
|
deploy-production
|
["build-and-push","deploy-staging" ["build-and-push","deploy-staging"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913345
|
1773912912
|
1773913345
|
|
0
|
|
0
|
Edit
Delete
|
|
8266
|
6996
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
自动回滚
|
0
|
name: Deploy
"on":
push:
branc name: Deploy
"on":
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- rollback
skip_tests:
description: '跳过测试(紧急修复时使用)'
required: false
default: false
type: boolean
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
auto-rollback:
name: 自动回滚
runs-on: ubuntu-latest
if: failure() && needs.deploy-production.result == 'failure'
steps:
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 执行自动回滚
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "=========================================="
echo "检测到部署失败,执行自动回滚..."
echo "=========================================="
# 使用项目回滚脚本
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --confirm -y
else
# 备用回滚逻辑:读取最近一条部署历史
PREV_LINE=$(tail -1 .deploy-history 2>/dev/null)
PREV_API_IMAGE=$(echo "$PREV_LINE" | cut -d'|' -f2)
PREV_FRONTEND_IMAGE=$(echo "$PREV_LINE" | cut -d'|' -f3)
if [ -n "$PREV_API_IMAGE" ] && [ "$PREV_API_IMAGE" != "none" ]; then
echo "回滚到: API=$PREV_API_IMAGE, Frontend=$PREV_FRONTEND_IMAGE"
export API_IMAGE="$PREV_API_IMAGE"
export FRONTEND_IMAGE="$PREV_FRONTEND_IMAGE"
docker compose -f docker-compose.prod.yml up -d --no-deps api frontend
# 健康检查(带重试)
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "回滚后健康检查失败(60s),请手动介入"
docker compose -f docker-compose.prod.yml logs --tail=30 api
exit 1
fi
sleep 5
done
echo "回滚成功!"
else
echo "无法获取上一版本信息,需要手动回滚"
echo "部署历史:"
cat .deploy-history 2>/dev/null || echo "(空)"
exit 1
fi
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- if: always()
name: 发送回滚通知
uses: 8398a7/action-slack@v3
with:
custom_payload: |
{
"attachments": [{
"color": "${{ job.status == 'success' && 'warning' || 'danger' }}",
"title": "生产环境自动回滚",
"text": "部署失败触发自动回滚\n回滚状态: ${{ job.status }}",
"fields": [
{ "title": "环境", "value": "Production", "short": true },
{ "title": "触发者", "value": "${{ github.actor }}", "short": true },
{ "title": "版本", "value": "${{ needs.build-and-push.outputs.version }}", "short": true }
]
}]
}
status: custom
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
timeout-minutes: "10"
...
|
auto-rollback
|
["deploy-production"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913345
|
1773912912
|
1773913345
|
|
0
|
|
0
|
Edit
Delete
|
|
8267
|
6996
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
手动回滚
|
1
|
name: Deploy
"on":
push:
branc name: Deploy
"on":
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- rollback
skip_tests:
description: '跳过测试(紧急修复时使用)'
required: false
default: false
type: boolean
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
manual-rollback:
name: 手动回滚
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'rollback'
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 列出可用版本
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "可用版本历史:"
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --list
else
echo "最近部署记录:"
tail -10 .deploy-history 2>/dev/null || echo "(无记录)"
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- name: 执行手动回滚
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
# 执行回滚到上一版本
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --version 1 --confirm
else
echo "rollback.sh 不存在,请手动回滚"
exit 1
fi
# 执行部署后验证
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- if: always()
name: Slack 通知
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 手动回滚 ${{ job.status }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
timeout-minutes: "10"
...
|
manual-rollback
|
null
|
["ubuntu-latest"]
|
7130
|
4
|
1773912982
|
1773912982
|
1773912912
|
1773912982
|
|
0
|
|
0
|
Edit
Delete
|
|
8268
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
pr-validation
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
pr-validation:
name: pr-validation
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build backend
run: |
cd backend
npm run build
- name: Build frontend
run: |
cd frontend
npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Start backend server
run: |
cd backend
npm run start &
sleep 10
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
- name: Start frontend server
run: |
cd frontend
npm run preview &
sleep 5
env:
VITE_API_URL: http://localhost:3000
- name: Run critical E2E tests
run: |
cd e2e
npx playwright test \
tests/auth/login.spec.ts \
tests/navigation/full-menu-click.spec.ts \
tests/multi-tenant/data-isolation.spec.ts \
--reporter=html
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: Upload test results
uses: actions/upload-artifact@v4
with:
name: playwright-report-pr
path: e2e/playwright-report/
retention-days: "7"
- if: always() && github.event_name == 'pull_request'
name: Comment PR with test results
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const reportPath = 'e2e/playwright-report/index.html';
const testsPassed = !fs.existsSync('e2e/test-results/');
const comment = testsPassed
? '✅ E2E 测试通过!'
: '❌ E2E 测试失败,请查看报告';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
timeout-minutes: "30"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
...
|
pr-validation
|
null
|
["ubuntu-latest"]
|
7131
|
4
|
1773912984
|
1773912984
|
1773912913
|
1773912984
|
|
0
|
|
0
|
Edit
Delete
|
|
8269
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
full-test-suite (chromium)
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
full-test-suite:
name: full-test-suite (chromium)
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_suite == 'all')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build backend
run: |
cd backend
npm run build
- name: Build frontend
run: |
cd frontend
npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps ${{ matrix.browser }}
- name: Start backend server
run: |
cd backend
npm run start &
sleep 10
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
- name: Start frontend server
run: |
cd frontend
npm run preview &
sleep 5
env:
VITE_API_URL: http://localhost:3000
- name: Run all E2E tests
run: |
cd e2e
npx playwright test --project=${{ matrix.browser }} --reporter=html,json
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: Upload test results
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.browser }}
path: e2e/playwright-report/
retention-days: "30"
- if: always()
name: Upload test artifacts
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.browser }}
path: e2e/test-results/
retention-days: "30"
timeout-minutes: "60"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
strategy:
fail-fast: "false"
matrix:
browser:
- chromium
...
|
full-test-suite
|
null
|
["ubuntu-latest"]
|
7132
|
2
|
1773912986
|
1773913012
|
1773912913
|
1773913012
|
|
0
|
|
0
|
Edit
Delete
|
|
8270
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
full-test-suite (firefox)
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
full-test-suite:
name: full-test-suite (firefox)
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_suite == 'all')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build backend
run: |
cd backend
npm run build
- name: Build frontend
run: |
cd frontend
npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps ${{ matrix.browser }}
- name: Start backend server
run: |
cd backend
npm run start &
sleep 10
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
- name: Start frontend server
run: |
cd frontend
npm run preview &
sleep 5
env:
VITE_API_URL: http://localhost:3000
- name: Run all E2E tests
run: |
cd e2e
npx playwright test --project=${{ matrix.browser }} --reporter=html,json
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: Upload test results
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.browser }}
path: e2e/playwright-report/
retention-days: "30"
- if: always()
name: Upload test artifacts
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.browser }}
path: e2e/test-results/
retention-days: "30"
timeout-minutes: "60"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
strategy:
fail-fast: "false"
matrix:
browser:
- firefox
...
|
full-test-suite
|
null
|
["ubuntu-latest"]
|
7133
|
2
|
1773913013
|
1773913039
|
1773912913
|
1773913039
|
|
0
|
|
0
|
Edit
Delete
|
|
8271
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
full-test-suite (webkit)
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
full-test-suite:
name: full-test-suite (webkit)
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_suite == 'all')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build backend
run: |
cd backend
npm run build
- name: Build frontend
run: |
cd frontend
npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps ${{ matrix.browser }}
- name: Start backend server
run: |
cd backend
npm run start &
sleep 10
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
- name: Start frontend server
run: |
cd frontend
npm run preview &
sleep 5
env:
VITE_API_URL: http://localhost:3000
- name: Run all E2E tests
run: |
cd e2e
npx playwright test --project=${{ matrix.browser }} --reporter=html,json
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: Upload test results
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.browser }}
path: e2e/playwright-report/
retention-days: "30"
- if: always()
name: Upload test artifacts
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.browser }}
path: e2e/test-results/
retention-days: "30"
timeout-minutes: "60"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
strategy:
fail-fast: "false"
matrix:
browser:
- webkit
...
|
full-test-suite
|
null
|
["ubuntu-latest"]
|
7134
|
2
|
1773913039
|
1773913065
|
1773912913
|
1773913065
|
|
0
|
|
0
|
Edit
Delete
|
|
8272
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
business-flows
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
business-flows:
name: business-flows
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.test_suite == 'business-flows'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build and start services
run: |
cd backend && npm run build && npm run start &
cd frontend && npm run build && npm run preview &
sleep 15
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
VITE_API_URL: http://localhost:3000
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Run business flow tests
run: |
cd e2e
npx playwright test tests/business-flows/ --reporter=html,json
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: Upload test results
uses: actions/upload-artifact@v4
with:
name: business-flows-report
path: e2e/playwright-report/
retention-days: "30"
timeout-minutes: "45"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
...
|
business-flows
|
null
|
["ubuntu-latest"]
|
7135
|
4
|
1773913065
|
1773913066
|
1773912913
|
1773913066
|
|
0
|
|
0
|
Edit
Delete
|
|
8273
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
performance-benchmarks
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
performance-benchmarks:
name: performance-benchmarks
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_suite == 'performance')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build and start services
run: |
cd backend && npm run build && npm run start &
cd frontend && npm run build && npm run preview &
sleep 15
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: production
VITE_API_URL: http://localhost:3000
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Run performance tests
run: |
cd e2e
npx playwright test \
tests/flows/complete-sales-flow.spec.ts \
tests/lead-to-cash.spec.ts \
--reporter=html,json
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- name: Generate performance report
run: |
cd e2e
node scripts/generate-performance-report.js
- if: always()
name: Upload performance results
uses: actions/upload-artifact@v4
with:
name: performance-report
path: |
e2e/playwright-report/
e2e/performance-results.json
retention-days: "90"
- if: github.event_name == 'schedule'
name: Comment with performance results
uses: actions/github-script@v7
with:
script: "const fs = require('fs');\nconst results = JSON.parse(fs.readFileSync('e2e/performance-results.json', 'utf8'));\n\nconst comment = `\n## \U0001F4CA 每日性能基准测试报告\n\n- **页面加载时间**: ${results.pageLoadTime}ms\n- **首次内容绘制 (FCP)**: ${results.fcp}ms\n- **最大内容绘制 (LCP)**: ${results.lcp}ms\n- **首次输入延迟 (FID)**: ${results.fid}ms\n- **累积布局偏移 (CLS)**: ${results.cls}\n\n${results.passed ? '✅ 所有性能指标达标' : '⚠️ 部分指标未达标,请关注'}\n`;\n\n// 创建 Issue 或发送通知\n// ...\n"
timeout-minutes: "30"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
...
|
performance-benchmarks
|
null
|
["ubuntu-latest"]
|
7136
|
4
|
1773913067
|
1773913068
|
1773912913
|
1773913068
|
|
0
|
|
0
|
Edit
Delete
|
|
8274
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
visual-regression
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
visual-regression:
name: visual-regression
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_suite == 'visual-regression')
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: "0"
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup test database
run: |
cd backend
npx prisma migrate deploy
npx prisma db seed
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
- name: Build and start services
run: |
cd backend && npm run build && npm run start &
cd frontend && npm run build && npm run preview &
sleep 15
env:
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/juhi_test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
VITE_API_URL: http://localhost:3000
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Run visual regression tests
run: |
cd e2e
npx playwright test tests/visual-regression/ --reporter=html
env:
E2E_BASE_URL: http://localhost:5173
E2E_TEST_USER: admin@juhi.com
E2E_TEST_PASSWORD: Admin@123
- if: always()
name: Upload visual diff results
uses: actions/upload-artifact@v4
with:
name: visual-regression-report
path: |
e2e/playwright-report/
e2e/test-results/
retention-days: "30"
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
name: Update baseline screenshots
run: |
cd e2e
npx playwright test tests/visual-regression/ --update-snapshots
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add tests/visual-regression/**/*.png
git commit -m "chore: update visual regression baselines" || echo "No changes"
git push
timeout-minutes: "30"
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: juhi_test
POSTGRES_PASSWORD: test_password
POSTGRES_USER: test_user
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
...
|
visual-regression
|
null
|
["ubuntu-latest"]
|
7137
|
2
|
1773913069
|
1773913220
|
1773912913
|
1773913220
|
|
0
|
|
0
|
Edit
Delete
|
|
8275
|
6997
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
test-summary
|
1
|
name: E2E Tests
"on":
# PR validation name: E2E Tests
"on":
# PR validation - 运行关键测试
pull_request:
branches: [main, develop]
paths:
- 'frontend/**'
- 'backend/**'
- 'e2e/**'
- 'package.json'
- 'pnpm-lock.yaml'
# Push to main - 运行完整测试套件
push:
branches: [main]
# 每日定时全量测试 (UTC 时间 00:00 = 北京时间 08:00)
schedule:
- cron: '0 0 * * *'
# 手动触发
workflow_dispatch:
inputs:
test_suite:
description: 'Test suite to run'
required: true
default: 'all'
type: choice
options:
- all
- critical
- business-flows
- visual-regression
- performance
env:
NODE_VERSION: "18"
PNPM_VERSION: "8"
jobs:
test-summary:
name: test-summary
runs-on: ubuntu-latest
if: always()
steps:
- name: Download all test artifacts
uses: actions/download-artifact@v4
- name: Generate summary report
run: "echo \"## \U0001F9EA E2E 测试汇总\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\n\nif [ -d \"playwright-report-pr\" ]; then\n echo \"### PR 快速验证\" >> $GITHUB_STEP_SUMMARY\n echo \"✅ 关键测试通过\" >> $GITHUB_STEP_SUMMARY\nfi\n\nif [ -d \"business-flows-report\" ]; then\n echo \"### 业务流程测试\" >> $GITHUB_STEP_SUMMARY\n echo \"✅ 业务流程测试完成\" >> $GITHUB_STEP_SUMMARY\nfi\n\nif [ -d \"performance-report\" ]; then\n echo \"### 性能基准测试\" >> $GITHUB_STEP_SUMMARY\n echo \"\U0001F4CA 性能测试报告已生成\" >> $GITHUB_STEP_SUMMARY\nfi\n\nif [ -d \"visual-regression-report\" ]; then\n echo \"### 视觉回归测试\" >> $GITHUB_STEP_SUMMARY\n echo \"\U0001F3A8 视觉对比完成\" >> $GITHUB_STEP_SUMMARY\nfi\n"
...
|
test-summary
|
["pr-validation","full-test-suite" ["pr-validation","full-test-suite","business-flows","performance-benchmarks","visual-regression"]...
|
["ubuntu-latest"]
|
7139
|
2
|
1773913650
|
1773913741
|
1773912913
|
1773913741
|
|
1
|
|
0
|
Edit
Delete
|
|
8276
|
6998
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
检测代码变更
|
1
|
name: Integration Tests
"on":
push:
name: Integration Tests
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
run_all_tests:
description: '运行所有集成测试(包括慢速测试)'
required: false
default: 'false'
type: boolean
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
tests:
- 'backend/tests/**'
- 'backend/vitest*.config.ts'
outputs:
backend: ${{ steps.filter.outputs.backend }}
tests: ${{ steps.filter.outputs.tests }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
7138
|
3
|
1773913224
|
1773913346
|
1773912914
|
1773913346
|
|
0
|
|
0
|
Edit
Delete
|
|
8277
|
6998
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
单元测试
|
0
|
name: Integration Tests
"on":
push:
name: Integration Tests
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
run_all_tests:
description: '运行所有集成测试(包括慢速测试)'
required: false
default: 'false'
type: boolean
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
unit-tests:
name: 单元测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 构建共享包
run: pnpm --filter @juhi/shared run build
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行单元测试
run: pnpm --filter juhi-api run test:unit
env:
NODE_ENV: test
JWT_SECRET: test-jwt-secret
JWT_REFRESH_SECRET: test-jwt-refresh-secret
- if: always()
name: 上传测试结果
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: backend/test-results/
retention-days: "7"
...
|
unit-tests
|
["detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913346
|
1773912914
|
1773913346
|
|
0
|
|
0
|
Edit
Delete
|
|
8278
|
6998
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
集成测试
|
0
|
name: Integration Tests
"on":
push:
name: Integration Tests
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
run_all_tests:
description: '运行所有集成测试(包括慢速测试)'
required: false
default: 'false'
type: boolean
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
integration-tests:
name: 集成测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 构建共享包
run: pnpm --filter @juhi/shared run build
- name: 启动测试服务
run: |
docker-compose -f docker-compose.test.yml up -d
echo "等待服务就绪..."
sleep 30
- name: 检查服务健康状态
run: |
docker-compose -f docker-compose.test.yml ps
# 检查 PostgreSQL
docker exec juhi-postgres-test pg_isready -U juhi_test -d juhi_test || exit 1
echo "✅ PostgreSQL 就绪"
# 检查 Redis
docker exec juhi-redis-test redis-cli ping || exit 1
echo "✅ Redis 就绪"
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 执行数据库迁移
run: |
cd backend
npx prisma migrate deploy
env:
DATABASE_URL: postgresql://juhi_test:test_password@localhost:5433/juhi_test
- name: 运行集成测试
run: pnpm --filter juhi-api run test:run
env:
DATABASE_URL: postgresql://juhi_test:test_password@localhost:5433/juhi_test
REDIS_URL: redis://localhost:6380
KAFKA_BROKERS: localhost:9095
MINIO_ENDPOINT: localhost
MINIO_PORT: 9002
MINIO_ACCESS_KEY: test_minio
MINIO_SECRET_KEY: test_password
JWT_SECRET: test-jwt-secret
JWT_REFRESH_SECRET: test-jwt-refresh-secret
NODE_ENV: test
- if: failure()
name: 收集测试日志
run: |
docker-compose -f docker-compose.test.yml logs > docker-logs.txt
- if: failure()
name: 上传 Docker 日志
uses: actions/upload-artifact@v4
with:
name: docker-logs
path: docker-logs.txt
retention-days: "7"
- if: always()
name: 停止测试服务
run: docker-compose -f docker-compose.test.yml down -v
- if: always()
name: 上传测试结果
uses: actions/upload-artifact@v4
with:
name: integration-test-results
path: backend/test-results/
retention-days: "7"
...
|
integration-tests
|
["detect-changes","unit-tests"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913346
|
1773912914
|
1773913346
|
|
0
|
|
0
|
Edit
Delete
|
|
8279
|
6998
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
API 端到端测试
|
0
|
name: Integration Tests
"on":
push:
name: Integration Tests
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
run_all_tests:
description: '运行所有集成测试(包括慢速测试)'
required: false
default: 'false'
type: boolean
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
api-e2e-tests:
name: API 端到端测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 构建共享包
run: pnpm --filter @juhi/shared run build
- name: 启动测试服务
run: |
docker-compose -f docker-compose.test.yml up -d
sleep 30
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 执行数据库迁移
run: |
cd backend
npx prisma migrate deploy
env:
DATABASE_URL: postgresql://juhi_test:test_password@localhost:5433/juhi_test
- name: 运行 API 测试
run: pnpm --filter juhi-api run test:api
env:
DATABASE_URL: postgresql://juhi_test:test_password@localhost:5433/juhi_test
REDIS_URL: redis://localhost:6380
KAFKA_BROKERS: localhost:9095
JWT_SECRET: test-jwt-secret
JWT_REFRESH_SECRET: test-jwt-refresh-secret
NODE_ENV: test
- if: always()
name: 停止测试服务
run: docker-compose -f docker-compose.test.yml down -v
- if: always()
name: 上传测试结果
uses: actions/upload-artifact@v4
with:
name: api-e2e-test-results
path: backend/test-results/
retention-days: "7"
...
|
api-e2e-tests
|
["integration-tests"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913346
|
1773912914
|
1773913346
|
|
0
|
|
0
|
Edit
Delete
|
|
8280
|
6998
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
测试总结
|
0
|
name: Integration Tests
"on":
push:
name: Integration Tests
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
run_all_tests:
description: '运行所有集成测试(包括慢速测试)'
required: false
default: 'false'
type: boolean
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
test-summary:
name: 测试总结
runs-on: ubuntu-latest
if: always()
steps:
- name: 生成测试报告
run: "echo \"## \U0001F9EA 集成测试结果总结\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"| 测试类型 | 状态 |\" >> $GITHUB_STEP_SUMMARY\necho \"|----------|------|\" >> $GITHUB_STEP_SUMMARY\n\n# 单元测试\nif [ \"${{ needs.unit-tests.result }}\" == \"success\" ]; then\n echo \"| ✅ 单元测试 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.unit-tests.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ 单元测试 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ 单元测试 | 失败 |\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# 集成测试\nif [ \"${{ needs.integration-tests.result }}\" == \"success\" ]; then\n echo \"| ✅ 集成测试 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.integration-tests.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ 集成测试 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ 集成测试 | 失败 |\" >> $GITHUB_STEP_SUMMARY\nfi\n\n# API E2E 测试\nif [ \"${{ needs.api-e2e-tests.result }}\" == \"success\" ]; then\n echo \"| ✅ API E2E 测试 | 通过 |\" >> $GITHUB_STEP_SUMMARY\nelif [ \"${{ needs.api-e2e-tests.result }}\" == \"skipped\" ]; then\n echo \"| ⏭️ API E2E 测试 | 跳过 |\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"| ❌ API E2E 测试 | 失败 |\" >> $GITHUB_STEP_SUMMARY\nfi\n"
- name: 检查测试结果
run: |
UNIT="${{ needs.unit-tests.result }}"
INTEGRATION="${{ needs.integration-tests.result }}"
API="${{ needs.api-e2e-tests.result }}"
if [ "$UNIT" == "failure" ] || [ "$INTEGRATION" == "failure" ] || [ "$API" == "failure" ]; then
echo "❌ 有测试失败"
exit 1
fi
echo "✅ 所有测试通过"
...
|
test-summary
|
["unit-tests","integration-tests", ["unit-tests","integration-tests","api-e2e-tests"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913346
|
1773912914
|
1773913346
|
|
0
|
|
0
|
Edit
Delete
|
|
8281
|
6999
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
静态代码分析
|
0
|
name: Code Quality Gate
"on":
push:
name: Code Quality Gate
"on":
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
strict_mode:
description: '严格模式(失败即阻止合并)'
required: false
default: true
type: boolean
env:
COVERAGE_THRESHOLD_E2E: "60"
COVERAGE_THRESHOLD_INTEGRATION: "70"
COVERAGE_THRESHOLD_UNIT: "80"
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
static-analysis:
name: 静态代码分析
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: Prisma Schema 验证
run: |
cd backend
npx prisma validate
echo "✅ Prisma Schema 验证通过"
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 后端 TypeScript 类型检查
run: |
cd backend
npx tsc --noEmit
echo "✅ 后端类型检查通过"
- name: 前端 TypeScript 类型检查
run: |
cd frontend
npm run check:array-guard
npm run type-check
echo "✅ 前端类型检查通过"
- name: ESLint 检查
run: |
cd backend
npm run lint || echo "⚠️ Lint 警告(非阻塞)"
continue-on-error: true
timeout-minutes: "20"
...
|
static-analysis
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913347
|
1773912914
|
1773913347
|
|
0
|
|
0
|
Edit
Delete
|
|
8282
|
6999
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
🔒 多租户安全审计
|
0
|
name: Code Quality Gate
"on":
push:
name: Code Quality Gate
"on":
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
strict_mode:
description: '严格模式(失败即阻止合并)'
required: false
default: true
type: boolean
env:
COVERAGE_THRESHOLD_E2E: "60"
COVERAGE_THRESHOLD_INTEGRATION: "70"
COVERAGE_THRESHOLD_UNIT: "80"
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
tenant-security:
name: "\U0001F512 多租户安全审计"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- id: tenant-audit
name: 执行多租户安全审计
run: |
cd backend
npm run audit:tenant 2>&1 | tee audit-result.txt
if grep -q "FAIL" audit-result.txt; then
echo "❌ 多租户安全审计失败"
echo "has_failures=true" >> $GITHUB_OUTPUT
exit 1
fi
echo "✅ 多租户安全审计通过"
echo "has_failures=false" >> $GITHUB_OUTPUT
- if: always()
name: 上传审计报告
uses: actions/upload-artifact@v4
with:
name: tenant-security-audit
path: backend/audit-result.txt
retention-days: "30"
timeout-minutes: "15"
...
|
tenant-security
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913347
|
1773912914
|
1773913347
|
|
0
|
|
0
|
Edit
Delete
|
|
8283
|
6999
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
📊 单元测试覆盖率门禁
|
0
|
name: Code Quality Gate
"on":
push:
name: Code Quality Gate
"on":
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
strict_mode:
description: '严格模式(失败即阻止合并)'
required: false
default: true
type: boolean
env:
COVERAGE_THRESHOLD_E2E: "60"
COVERAGE_THRESHOLD_INTEGRATION: "70"
COVERAGE_THRESHOLD_UNIT: "80"
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
unit-test-coverage:
name: "\U0001F4CA 单元测试覆盖率门禁"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行单元测试并生成覆盖率
run: |
cd backend
npm run test:coverage
env:
NODE_ENV: test
JWT_SECRET: test-jwt-secret
JWT_REFRESH_SECRET: test-jwt-refresh-secret
- id: coverage-check
name: 检查覆盖率阈值
run: "cd backend\nif [ -f \"coverage/coverage-summary.json\" ]; then\n LINES_PCT=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')\n STATEMENTS_PCT=$(cat coverage/coverage-summary.json | jq '.total.statements.pct')\n BRANCHES_PCT=$(cat coverage/coverage-summary.json | jq '.total.branches.pct')\n FUNCTIONS_PCT=$(cat coverage/coverage-summary.json | jq '.total.functions.pct')\n\n echo \"\U0001F4CA 覆盖率报告:\"\n echo \" - 行覆盖率: $LINES_PCT%\"\n echo \" - 语句覆盖率: $STATEMENTS_PCT%\"\n echo \" - 分支覆盖率: $BRANCHES_PCT%\"\n echo \" - 函数覆盖率: $FUNCTIONS_PCT%\"\n\n echo \"lines_pct=$LINES_PCT\" >> $GITHUB_OUTPUT\n echo \"statements_pct=$STATEMENTS_PCT\" >> $GITHUB_OUTPUT\n echo \"branches_pct=$BRANCHES_PCT\" >> $GITHUB_OUTPUT\n echo \"functions_pct=$FUNCTIONS_PCT\" >> $GITHUB_OUTPUT\n\n # 检查是否达到阈值(使用行覆盖率作为主要指标)\n if (( $(echo \"$LINES_PCT < ${{ env.COVERAGE_THRESHOLD_UNIT }}\" | bc -l) )); then\n echo \"❌ 行覆盖率 $LINES_PCT% 低于阈值 ${{ env.COVERAGE_THRESHOLD_UNIT }}%\"\n echo \"coverage_pass=false\" >> $GITHUB_OUTPUT\n exit 1\n fi\n\n echo \"✅ 覆盖率检查通过\"\n echo \"coverage_pass=true\" >> $GITHUB_OUTPUT\nelse\n echo \"⚠️ 未找到覆盖率报告\"\n echo \"coverage_pass=false\" >> $GITHUB_OUTPUT\n exit 1\nfi\n"
- if: always()
name: 生成覆盖率徽章
run: |
COVERAGE="${{ steps.coverage-check.outputs.lines_pct }}"
if [ -z "$COVERAGE" ]; then
COVERAGE="0"
fi
# 确定颜色
if (( $(echo "$COVERAGE >= 80" | bc -l) )); then
COLOR="brightgreen"
elif (( $(echo "$COVERAGE >= 60" | bc -l) )); then
COLOR="yellow"
else
COLOR="red"
fi
echo "Coverage: $COVERAGE% ($COLOR)"
- if: always()
name: 上传覆盖率报告
uses: actions/upload-artifact@v4
with:
name: unit-test-coverage
path: |
backend/coverage/
backend/test-results/
retention-days: "14"
- if: github.event_name == 'pull_request'
name: 评论 PR 覆盖率
uses: actions/github-script@v7
with:
script: "const coverage = '${{ steps.coverage-check.outputs.lines_pct }}';\nconst threshold = '${{ env.COVERAGE_THRESHOLD_UNIT }}';\nconst passed = '${{ steps.coverage-check.outputs.coverage_pass }}' === 'true';\n\nconst emoji = passed ? '✅' : '❌';\nconst status = passed ? '通过' : '未通过';\n\nconst body = [\n `## ${emoji} 单元测试覆盖率报告`,\n '',\n '| 指标 | 当前值 | 阈值 | 状态 |',\n '|------|--------|------|------|',\n `| 行覆盖率 | ${coverage}% | ${threshold}% | ${status} |`,\n `| 语句覆盖率 | ${{ steps.coverage-check.outputs.statements_pct }}% | - | - |`,\n `| 分支覆盖率 | ${{ steps.coverage-check.outputs.branches_pct }}% | - | - |`,\n `| 函数覆盖率 | ${{ steps.coverage-check.outputs.functions_pct }}% | - | - |`,\n '',\n passed ? '\U0001F389 恭喜!测试覆盖率达标。' : '⚠️ 请增加测试用例以提高覆盖率。'\n].join('\\n');\n\ngithub.rest.issues.createComment({\n issue_number: context.issue.number,\n owner: context.repo.owner,\n repo: context.repo.repo,\n body: body\n});\n"
timeout-minutes: "30"
...
|
unit-test-coverage
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913347
|
1773912914
|
1773913347
|
|
0
|
|
0
|
Edit
Delete
|
|
8284
|
6999
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
🔄 状态机测试
|
0
|
name: Code Quality Gate
"on":
push:
name: Code Quality Gate
"on":
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
strict_mode:
description: '严格模式(失败即阻止合并)'
required: false
default: true
type: boolean
env:
COVERAGE_THRESHOLD_E2E: "60"
COVERAGE_THRESHOLD_INTEGRATION: "70"
COVERAGE_THRESHOLD_UNIT: "80"
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
state-machine-tests:
name: "\U0001F504 状态机测试"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行状态机测试
run: |
cd backend
npx vitest run src/shared/state-machines --reporter=verbose --passWithNoTests
echo "✅ 状态机测试通过"
timeout-minutes: "15"
...
|
state-machine-tests
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913347
|
1773912914
|
1773913347
|
|
0
|
|
0
|
Edit
Delete
|
|
8285
|
6999
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
🔍 类型安全检查
|
0
|
name: Code Quality Gate
"on":
push:
name: Code Quality Gate
"on":
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
strict_mode:
description: '严格模式(失败即阻止合并)'
required: false
default: true
type: boolean
env:
COVERAGE_THRESHOLD_E2E: "60"
COVERAGE_THRESHOLD_INTEGRATION: "70"
COVERAGE_THRESHOLD_UNIT: "80"
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
type-safety:
name: "\U0001F50D 类型安全检查"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: any-check
name: 检查前端 any 类型使用
run: |
cd frontend
ANY_COUNT=$(grep -r ": any" src/ --include="*.ts" --include="*.vue" | wc -l || echo "0")
echo "发现 $ANY_COUNT 处 any 类型使用"
echo "any_count=$ANY_COUNT" >> $GITHUB_OUTPUT
if [ "$ANY_COUNT" -gt 50 ]; then
echo "⚠️ any 类型使用过多(>50处),建议优化"
fi
- name: 检查后端类型安全
run: |
cd backend
ANY_COUNT=$(grep -r ": any" src/ --include="*.ts" | grep -v "node_modules" | wc -l || echo "0")
echo "后端发现 $ANY_COUNT 处 any 类型使用"
timeout-minutes: "10"
...
|
type-safety
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913348
|
1773912914
|
1773913348
|
|
0
|
|
0
|
Edit
Delete
|
|
8286
|
6999
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
📋 质量门禁总结
|
0
|
name: Code Quality Gate
"on":
push:
name: Code Quality Gate
"on":
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
strict_mode:
description: '严格模式(失败即阻止合并)'
required: false
default: true
type: boolean
env:
COVERAGE_THRESHOLD_E2E: "60"
COVERAGE_THRESHOLD_INTEGRATION: "70"
COVERAGE_THRESHOLD_UNIT: "80"
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
quality-gate-summary:
name: "\U0001F4CB 质量门禁总结"
runs-on: ubuntu-latest
if: always()
steps:
- name: 输出质量门禁结果
run: "echo \"## \U0001F6A6 质量门禁结果\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"| 检查项 | 状态 | 说明 |\" >> $GITHUB_STEP_SUMMARY\necho \"|--------|------|------|\" >> $GITHUB_STEP_SUMMARY\necho \"| 静态代码分析 | ${{ needs.static-analysis.result }} | TypeScript/Prisma 验证 |\" >> $GITHUB_STEP_SUMMARY\necho \"| 多租户安全审计 | ${{ needs.tenant-security.result }} | 数据隔离检查 |\" >> $GITHUB_STEP_SUMMARY\necho \"| 单元测试覆盖率 | ${{ needs.unit-test-coverage.result }} | ≥80% 阈值 |\" >> $GITHUB_STEP_SUMMARY\necho \"| 状态机测试 | ${{ needs.state-machine-tests.result }} | XState 验证 |\" >> $GITHUB_STEP_SUMMARY\necho \"| 类型安全检查 | ${{ needs.type-safety.result }} | any 类型统计 |\" >> $GITHUB_STEP_SUMMARY\n"
- name: 检查是否全部通过
run: "STATIC=\"${{ needs.static-analysis.result }}\"\nTENANT=\"${{ needs.tenant-security.result }}\"\nCOVERAGE=\"${{ needs.unit-test-coverage.result }}\"\nSTATE=\"${{ needs.state-machine-tests.result }}\"\nTYPE=\"${{ needs.type-safety.result }}\"\n\n# 核心门禁(必须通过)\nCORE_FAILED=0\nif [ \"$TENANT\" == \"failure\" ]; then\n echo \"❌ 核心门禁失败:多租户安全审计\"\n CORE_FAILED=1\nfi\nif [ \"$COVERAGE\" == \"failure\" ]; then\n echo \"❌ 核心门禁失败:单元测试覆盖率\"\n CORE_FAILED=1\nfi\nif [ \"$STATIC\" == \"failure\" ]; then\n echo \"❌ 核心门禁失败:静态代码分析\"\n CORE_FAILED=1\nfi\n\nif [ $CORE_FAILED -eq 1 ]; then\n echo \"\"\n echo \"\U0001F6A8 质量门禁未通过,请修复上述问题后重新提交\"\n exit 1\nfi\n\necho \"✅ 所有质量门禁通过\"\n"
- if: github.event_name == 'pull_request' && always()
name: 评论 PR 总结
uses: actions/github-script@v7
with:
script: "const results = {\n static: '${{ needs.static-analysis.result }}',\n tenant: '${{ needs.tenant-security.result }}',\n coverage: '${{ needs.unit-test-coverage.result }}',\n state: '${{ needs.state-machine-tests.result }}',\n type: '${{ needs.type-safety.result }}'\n};\n\nconst getEmoji = (result) => {\n if (result === 'success') return '✅';\n if (result === 'failure') return '❌';\n if (result === 'skipped') return '⏭️';\n return '⚠️';\n};\n\nconst allPassed = Object.values(results).every(r => r === 'success' || r === 'skipped');\n\nconst body = [\n '## \U0001F6A6 质量门禁检查结果',\n '',\n '| 检查项 | 状态 |',\n '|--------|------|',\n `| 静态代码分析 | ${getEmoji(results.static)} |`,\n `| 多租户安全审计 | ${getEmoji(results.tenant)} |`,\n `| 单元测试覆盖率 | ${getEmoji(results.coverage)} |`,\n `| 状态机测试 | ${getEmoji(results.state)} |`,\n `| 类型安全检查 | ${getEmoji(results.type)} |`,\n '',\n allPassed ? '✅ **所有质量门禁通过,可以合并!**' : '❌ **质量门禁未通过,请修复问题后重新提交。**'\n].join('\\n');\n\n// 查找是否已有评论\nconst comments = await github.rest.issues.listComments({\n issue_number: context.issue.number,\n owner: context.repo.owner,\n repo: context.repo.repo\n});\n\nconst botComment = comments.data.find(c =>\n c.user.type === 'Bot' &&\n c.body.includes('质量门禁检查结果')\n);\n\nif (botComment) {\n await github.rest.issues.updateComment({\n comment_id: botComment.id,\n owner: context.repo.owner,\n repo: context.repo.repo,\n body: body\n });\n} else {\n await github.rest.issues.createComment({\n issue_number: context.issue.number,\n owner: context.repo.owner,\n repo: context.repo.repo,\n body: body\n });\n}\n"
...
|
quality-gate-summary
|
["static-analysis","tenant-security ["static-analysis","tenant-security","unit-test-coverage","state-machine-tests","type-safety"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913348
|
1773912914
|
1773913348
|
|
0
|
|
0
|
Edit
Delete
|
|
8287
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
检测代码变更
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
shared:
- 'shared/**'
e2e:
- 'e2e/**'
- 'frontend/**'
- 'backend/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
e2e: ${{ steps.filter.outputs.e2e }}
frontend: ${{ steps.filter.outputs.frontend }}
shared: ${{ steps.filter.outputs.shared }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913348
|
1773912915
|
1773913348
|
|
0
|
|
0
|
Edit
Delete
|
|
8288
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
安装依赖
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
setup:
name: 安装依赖
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 构建共享包
run: pnpm --filter @juhi/shared run build
- name: 缓存依赖
uses: actions/cache/save@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
...
|
setup
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913348
|
1773912915
|
1773913348
|
|
0
|
|
0
|
Edit
Delete
|
|
8289
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
共享层单元测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
shared-unit-test:
name: 共享层单元测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.shared == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 运行共享层测试
run: pnpm --filter @juhi/shared run test:coverage
- name: 上传覆盖率报告
uses: actions/upload-artifact@v4
with:
name: shared-coverage
path: shared/coverage/lcov.info
retention-days: "7"
...
|
shared-unit-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913348
|
1773912915
|
1773913348
|
|
0
|
|
0
|
Edit
Delete
|
|
8290
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
后端单元测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
backend-unit-test:
name: 后端单元测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行单元测试
run: pnpm --filter juhi-api run test:coverage
env:
NODE_ENV: test
JWT_SECRET: test-jwt-secret
JWT_REFRESH_SECRET: test-jwt-refresh-secret
- name: 检查覆盖率阈值
run: |
cd backend
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
echo "当前覆盖率: $COVERAGE%"
if (( $(echo "$COVERAGE < ${{ env.COVERAGE_THRESHOLD }}" | bc -l) )); then
echo "❌ 覆盖率 $COVERAGE% 低于阈值 ${{ env.COVERAGE_THRESHOLD }}%"
exit 1
fi
echo "✅ 覆盖率检查通过"
- name: 上传覆盖率报告
uses: actions/upload-artifact@v4
with:
name: backend-unit-coverage
path: backend/coverage/lcov.info
retention-days: "7"
- if: always()
name: 上传测试结果
uses: actions/upload-artifact@v4
with:
name: backend-unit-test-results
path: backend/test-results/
retention-days: "7"
...
|
backend-unit-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913348
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8291
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
后端 API 集成测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
backend-api-test:
name: 后端 API 集成测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true' && (github.event.inputs.run_api_tests != 'false')
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行数据库迁移
run: |
cd backend
npx prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/juhi_api_test
- name: 运行 API 集成测试
run: pnpm --filter juhi-api run test:api
env:
DATABASE_URL: postgresql://test:test@localhost:5432/juhi_api_test
REDIS_URL: redis://localhost:6379
JWT_SECRET: test-jwt-secret
JWT_REFRESH_SECRET: test-jwt-refresh-secret
NODE_ENV: test
- if: always()
name: 上传 API 测试结果
uses: actions/upload-artifact@v4
with:
name: backend-api-test-results
path: backend/test-results/
retention-days: "7"
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: juhi_api_test
POSTGRES_PASSWORD: test
POSTGRES_USER: test
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
...
|
backend-api-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8292
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
状态机测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
backend-state-machine-test:
name: 状态机测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行状态机测试
run: |
cd backend
npx vitest run src/shared/state-machines --reporter=verbose
...
|
backend-state-machine-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8293
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
多租户安全测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
backend-tenant-security-test:
name: 多租户安全测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.backend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 生成 Prisma Client
run: pnpm --filter juhi-api run db:generate
- name: 运行多租户安全审计
run: pnpm --filter juhi-api run audit:tenant
- name: 运行中间件测试
run: |
cd backend
npx vitest run src/shared/middleware --reporter=verbose
...
|
backend-tenant-security-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8294
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
前端单元测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
frontend-unit-test:
name: 前端单元测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.frontend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 数组守卫检查
run: pnpm --filter juhi-frontend run check:array-guard
- name: 运行前端单元测试
run: pnpm --filter juhi-frontend run test:coverage
- name: 上传覆盖率报告
uses: actions/upload-artifact@v4
with:
name: frontend-coverage
path: frontend/coverage/lcov.info
retention-days: "7"
- if: always()
name: 上传测试结果
uses: actions/upload-artifact@v4
with:
name: frontend-unit-test-results
path: frontend/test-results/
retention-days: "7"
...
|
frontend-unit-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8295
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
组件快照测试
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
frontend-snapshot-test:
name: 组件快照测试
runs-on: ubuntu-latest
if: needs.detect-changes.outputs.frontend == 'true'
steps:
- uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: 恢复依赖缓存
uses: actions/cache/restore@v4
with:
key: test-deps-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
path: |
node_modules
backend/node_modules
frontend/node_modules
shared/node_modules
shared/dist
e2e/node_modules
- name: 运行快照测试
run: |
cd frontend
npx vitest run --reporter=verbose || true
continue-on-error: true
...
|
frontend-snapshot-test
|
["setup","detect-changes"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8296
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
合并覆盖率报告
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
coverage-report:
name: 合并覆盖率报告
runs-on: ubuntu-latest
if: always() && !cancelled()
steps:
- uses: actions/checkout@v4
- name: 下载所有覆盖率报告
uses: actions/download-artifact@v4
with:
merge-multiple: "true"
path: coverage-reports
pattern: '*-coverage'
- name: 上传到 Codecov
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: "false"
files: coverage-reports/**/*.info
verbose: "true"
- name: 生成覆盖率摘要
run: "echo \"## \U0001F4CA 测试覆盖率报告\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"| 模块 | 覆盖率文件 |\" >> $GITHUB_STEP_SUMMARY\necho \"|------|-----------|\" >> $GITHUB_STEP_SUMMARY\nfor file in coverage-reports/*.info; do\n if [ -f \"$file\" ]; then\n echo \"| $(basename $file .info) | ✅ |\" >> $GITHUB_STEP_SUMMARY\n fi\ndone\n"
...
|
coverage-report
|
["backend-unit-test","frontend-unit-te ["backend-unit-test","frontend-unit-test","shared-unit-test"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8297
|
7000
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
测试总结
|
0
|
name: Test Suite
"on":
push:
b name: Test Suite
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
coverage_threshold:
description: '覆盖率阈值 (%)'
required: false
default: '40'
run_api_tests:
description: '运行 API 集成测试'
required: false
default: 'true'
type: boolean
env:
COVERAGE_THRESHOLD: ${{ github.event.inputs.coverage_threshold || '80' }}
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
test-summary:
name: 测试总结
runs-on: ubuntu-latest
if: always()
steps:
- name: 输出测试结果
run: "echo \"## \U0001F9EA 测试结果总结\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"| 测试套件 | 状态 |\" >> $GITHUB_STEP_SUMMARY\necho \"|----------|------|\" >> $GITHUB_STEP_SUMMARY\necho \"| 后端单元测试 | ${{ needs.backend-unit-test.result || 'skipped' }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 后端 API 测试 | ${{ needs.backend-api-test.result || 'skipped' }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 状态机测试 | ${{ needs.backend-state-machine-test.result || 'skipped' }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 多租户安全测试 | ${{ needs.backend-tenant-security-test.result || 'skipped' }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 前端单元测试 | ${{ needs.frontend-unit-test.result || 'skipped' }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 共享层测试 | ${{ needs.shared-unit-test.result || 'skipped' }} |\" >> $GITHUB_STEP_SUMMARY\n"
- name: 检查是否全部通过
run: |
BACKEND_UNIT="${{ needs.backend-unit-test.result }}"
BACKEND_API="${{ needs.backend-api-test.result }}"
STATE_MACHINE="${{ needs.backend-state-machine-test.result }}"
TENANT_SECURITY="${{ needs.backend-tenant-security-test.result }}"
FRONTEND="${{ needs.frontend-unit-test.result }}"
SHARED="${{ needs.shared-unit-test.result }}"
# 检查失败的测试
FAILED=0
for result in "$BACKEND_UNIT" "$BACKEND_API" "$STATE_MACHINE" "$TENANT_SECURITY" "$FRONTEND" "$SHARED"; do
if [ "$result" == "failure" ]; then
FAILED=1
break
fi
done
if [ $FAILED -eq 1 ]; then
echo "❌ 有测试失败"
exit 1
fi
echo "✅ 所有测试通过"
...
|
test-summary
|
["backend-unit-test","backend-api-test ["backend-unit-test","backend-api-test","backend-state-machine-test","backend-tenant-security-test","frontend-unit-test","shared-unit-test"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1773913349
|
1773912915
|
1773913349
|
|
0
|
|
0
|
Edit
Delete
|
|
8298
|
7001
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
📥 收集测试结果
|
1
|
name: Test Report
"on":
workflow_run:
name: Test Report
"on":
workflow_run:
workflows:
- 'Test Suite'
- 'E2E Tests'
- 'Performance Tests'
types:
- completed
env:
NODE_VERSION: "18"
jobs:
collect-results:
name: "\U0001F4E5 收集测试结果"
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion != 'cancelled'
steps:
- name: "\U0001F4DD 记录工作流信息"
run: |
echo "工作流: ${{ github.event.workflow_run.name }}"
echo "结果: ${{ github.event.workflow_run.conclusion }}"
echo "运行 ID: ${{ github.event.workflow_run.id }}"
echo "分支: ${{ github.event.workflow_run.head_branch }}"
- name: "\U0001F4E5 下载测试结果 artifacts"
uses: actions/github-script@v7
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
});
console.log('找到的 artifacts:');
for (const artifact of artifacts.data.artifacts) {
console.log(`- ${artifact.name} (${artifact.size_in_bytes} bytes)`);
}
// 保存 artifact 列表
const fs = require('fs');
fs.writeFileSync('artifacts.json', JSON.stringify(artifacts.data.artifacts, null, 2));
- name: "\U0001F4E4 上传 artifact 清单"
uses: actions/upload-artifact@v4
with:
name: artifact-list-${{ github.event.workflow_run.id }}
path: artifacts.json
retention-days: "7"
outputs:
run_id: ${{ github.event.workflow_run.id }}
workflow_conclusion: ${{ github.event.workflow_run.conclusion }}
workflow_name: ${{ github.event.workflow_run.name }}
...
|
collect-results
|
null
|
["ubuntu-latest"]
|
7140
|
2
|
1773913741
|
1773913806
|
1773913223
|
1773913806
|
|
0
|
|
0
|
Edit
Delete
|
|
8299
|
7001
|
6
|
5
|
bd59e5501292cb061719f669c7a7b7afd4f1a0b7
|
0
|
📊 生成综合报告
|
0
|
name: Test Report
"on":
workflow_run:
name: Test Report
"on":
workflow_run:
workflows:
- 'Test Suite'
- 'E2E Tests'
- 'Performance Tests'
types:
- completed
env:
NODE_VERSION: "18"
jobs:
generate-report:
name: "\U0001F4CA 生成综合报告"
runs-on: ubuntu-latest
steps:
- name: "\U0001F4E5 检出代码"
uses: actions/checkout@v4
- name: "\U0001F7E2 设置 Node.js"
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: "\U0001F4E5 下载触发工作流的 artifacts"
uses: dawidd6/action-download-artifact@v3
with:
path: downloaded-artifacts
run_id: ${{ github.event.workflow_run.id }}
workflow: ${{ github.event.workflow_run.workflow_id }}
continue-on-error: true
- name: "\U0001F4CA 生成综合测试报告"
run: "echo \"# \U0001F4CA 测试报告\" > test-report.md\necho \"\" >> test-report.md\necho \"**生成时间**: $(date)\" >> test-report.md\necho \"**触发工作流**: ${{ github.event.workflow_run.name }}\" >> test-report.md\necho \"**运行 ID**: ${{ github.event.workflow_run.id }}\" >> test-report.md\necho \"**分支**: ${{ github.event.workflow_run.head_branch }}\" >> test-report.md\necho \"**结果**: ${{ github.event.workflow_run.conclusion }}\" >> test-report.md\necho \"\" >> test-report.md\n\necho \"## \U0001F4C1 测试结果\" >> test-report.md\necho \"\" >> test-report.md\n\n# 遍历下载的 artifacts\nif [ -d \"downloaded-artifacts\" ]; then\n echo \"### 已下载的测试结果\" >> test-report.md\n echo \"\" >> test-report.md\n\n for dir in downloaded-artifacts/*; do\n if [ -d \"$dir\" ]; then\n name=$(basename \"$dir\")\n echo \"- **$name**\" >> test-report.md\n\n # 检查是否有 JSON 结果文件\n for json in \"$dir\"/*.json; do\n if [ -f \"$json\" ]; then\n echo \" - $(basename \"$json\")\" >> test-report.md\n fi\n done\n fi\n done\nelse\n echo \"暂无测试结果下载\" >> test-report.md\nfi\n\necho \"\" >> test-report.md\necho \"---\" >> test-report.md\necho \"*此报告由 CI/CD 自动生成*\" >> test-report.md\n"
- name: "\U0001F4DD 生成 GitHub Summary"
run: "echo \"## \U0001F4CA 测试报告汇总\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\necho \"| 项目 | 值 |\" >> $GITHUB_STEP_SUMMARY\necho \"|------|------|\" >> $GITHUB_STEP_SUMMARY\necho \"| 触发工作流 | ${{ github.event.workflow_run.name }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 运行 ID | ${{ github.event.workflow_run.id }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 分支 | ${{ github.event.workflow_run.head_branch }} |\" >> $GITHUB_STEP_SUMMARY\necho \"| 结果 | ${{ github.event.workflow_run.conclusion }} |\" >> $GITHUB_STEP_SUMMARY\necho \"\" >> $GITHUB_STEP_SUMMARY\n\nif [ \"${{ github.event.workflow_run.conclusion }}\" == \"success\" ]; then\n echo \"### ✅ 测试通过\" >> $GITHUB_STEP_SUMMARY\nelse\n echo \"### ❌ 测试失败\" >> $GITHUB_STEP_SUMMARY\n echo \"\" >> $GITHUB_STEP_SUMMARY\n echo \"请查看 [运行详情](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}) 了解更多信息。\" >> $GITHUB_STEP_SUMMARY\nfi\n"
- name: "\U0001F4E4 上传综合报告"
uses: actions/upload-artifact@v4
with:
name: test-report-${{ github.event.workflow_run.id }}
path: test-report.md
retention-days: "30"
...
|
generate-report
|
["collect-results"]
|
["ubuntu-latest"]
|
0
|
4
|
0
|
0
|
1773913223
|
1773913808
|
|
1
|
|
0
|
Edit
Delete
|