Commit 0aa3cf67 authored by shaw's avatar shaw
Browse files

chore: 清理一些无用的文件

parent 72961c58
# Repository Guidelines
## Project Structure & Module Organization
- `backend/`: Go service. `cmd/server` is the entrypoint, `internal/` contains handlers/services/repositories/server wiring, `ent/` holds Ent schemas and generated ORM code, `migrations/` stores DB migrations, and `internal/web/dist/` is the embedded frontend build output.
- `frontend/`: Vue 3 + TypeScript app. Main folders are `src/api`, `src/components`, `src/views`, `src/stores`, `src/composables`, `src/utils`, and test files in `src/**/__tests__`.
- `deploy/`: Docker and deployment assets (`docker-compose*.yml`, `.env.example`, `config.example.yaml`).
- `openspec/`: Spec-driven change docs (`changes/<id>/{proposal,design,tasks}.md`).
- `tools/`: Utility scripts (security/perf checks).
## Build, Test, and Development Commands
```bash
make build # Build backend + frontend
make test # Backend tests + frontend lint/typecheck
cd backend && make build # Build backend binary
cd backend && make test-unit # Go unit tests
cd backend && make test-integration # Go integration tests
cd backend && make test # go test ./... + golangci-lint
cd frontend && pnpm install --frozen-lockfile
cd frontend && pnpm dev # Vite dev server
cd frontend && pnpm build # Type-check + production build
cd frontend && pnpm test:run # Vitest run
cd frontend && pnpm test:coverage # Vitest + coverage report
python3 tools/secret_scan.py # Secret scan
```
## Coding Style & Naming Conventions
- Go: format with `gofmt`; lint with `golangci-lint` (`backend/.golangci.yml`).
- Respect layering: `internal/service` and `internal/handler` must not import `internal/repository`, `gorm`, or `redis` directly (enforced by depguard).
- Frontend: Vue SFC + TypeScript, 2-space indentation, ESLint rules from `frontend/.eslintrc.cjs`.
- Naming: components use `PascalCase.vue`, composables use `useXxx.ts`, Go tests use `*_test.go`, frontend tests use `*.spec.ts`.
## Go & Frontend Development Standards
- Control branch complexity: `if` nesting must not exceed 3 levels. Refactor with guard clauses, early returns, helper functions, or strategy maps when deeper logic appears.
- JSON hot-path rule: for read-only/partial-field extraction, prefer `gjson` over full `encoding/json` struct unmarshal to reduce allocations and improve latency.
- Exception rule: if full schema validation or typed writes are required, `encoding/json` is allowed, but PR must explain why `gjson` is not suitable.
### Go Performance Rules
- Optimization workflow rule: benchmark/profile first, then optimize. Use `go test -bench`, `go tool pprof`, and runtime diagnostics before changing hot-path code.
- For hot functions, run escape analysis (`go build -gcflags=all='-m -m'`) and prioritize stack allocation where reasonable.
- Every external I/O path must use `context.Context` with explicit timeout/cancel.
- When creating derived contexts (`WithTimeout` / `WithDeadline`), always `defer cancel()` to release resources.
- Preallocate slices/maps when size can be estimated (`make([]T, 0, n)`, `make(map[K]V, n)`).
- Avoid unnecessary allocations in loops; reuse buffers and prefer `strings.Builder`/`bytes.Buffer`.
- Prohibit N+1 query patterns; batch DB/Redis operations and verify indexes for new query paths.
- For hot-path changes, include benchmark or latency comparison evidence (e.g., `go test -bench` before/after).
- Keep goroutine growth bounded (worker pool/semaphore), and avoid unbounded fan-out.
- Lock minimization rule: if a lock can be avoided, do not use a lock. Prefer ownership transfer (channel), sharding, immutable snapshots, copy-on-write, or atomic operations to reduce contention.
- When locks are unavoidable, keep critical sections minimal, avoid nested locks, and document why lock-free alternatives are not feasible.
- Follow `sync` guidance: prefer channels for higher-level synchronization; use low-level mutex primitives only where necessary.
- Avoid reflection and `interface{}`-heavy conversions in hot paths; use typed structs/functions.
- Use `sync.Pool` only when benchmark proves allocation reduction; remove if no measurable gain.
- Avoid repeated `time.Now()`/`fmt.Sprintf` in tight loops; hoist or cache when possible.
- For stable high-traffic binaries, maintain representative `default.pgo` profiles and keep `go build -pgo=auto` enabled.
### Data Access & Cache Rules
- Every new/changed SQL query must be checked with `EXPLAIN` (or `EXPLAIN ANALYZE` in staging) and include index rationale in PR.
- Default to keyset pagination for large tables; avoid deep `OFFSET` scans on hot endpoints.
- Query only required columns; prohibit broad `SELECT *` in latency-sensitive paths.
- Keep transactions short; never perform external RPC/network calls inside DB transactions.
- Connection pool must be explicitly tuned and observed via `DB.Stats` (`SetMaxOpenConns`, `SetMaxIdleConns`, `SetConnMaxIdleTime`, `SetConnMaxLifetime`).
- Avoid overly small `MaxOpenConns` that can turn DB access into lock/semaphore bottlenecks.
- Cache keys must be versioned (e.g., `user_usage:v2:{id}`) and TTL should include jitter to avoid thundering herd.
- Use request coalescing (`singleflight` or equivalent) for high-concurrency cache miss paths.
### Frontend Performance Rules
- Route-level and heavy-module code splitting is required; lazy-load non-critical views/components.
- API requests must support cancellation and deduplication; use debounce/throttle for search-like inputs.
- Minimize unnecessary reactivity: avoid deep watch chains when computed/cache can solve it.
- Prefer stable props and selective rendering controls (`v-once`, `v-memo`) for expensive subtrees when data is static or keyed.
- Large data rendering must use pagination or virtualization (especially tables/lists >200 rows).
- Move expensive CPU work off the main thread (Web Worker) or chunk tasks to avoid UI blocking.
- Keep bundle growth controlled; avoid adding heavy dependencies without clear ROI and alternatives review.
- Avoid expensive inline computations in templates; move to cached `computed` selectors.
- Keep state normalized; avoid duplicated derived state across multiple stores/components.
- Load charts/editors/export libraries on demand only (`dynamic import`) instead of app-entry import.
- Core Web Vitals targets (p75): `LCP <= 2.5s`, `INP <= 200ms`, `CLS <= 0.1`.
- Main-thread task budget: keep individual tasks below ~50ms; split long tasks and yield between chunks.
- Enforce frontend budgets in CI (Lighthouse CI with `budget.json`) for critical routes.
### Performance Budget & PR Evidence
- Performance budget is mandatory for hot-path PRs: backend p95/p99 latency and CPU/memory must not regress by more than 5% versus baseline.
- Frontend budget: new route-level JS should not increase by more than 30KB gzip without explicit approval.
- For any gateway/protocol hot path, attach a reproducible benchmark command and results (input size, concurrency, before/after table).
- Profiling evidence is required for major optimizations (`pprof`, flamegraph, browser performance trace, or bundle analyzer output).
### Quality Gate
- Any changed code must include new or updated unit tests.
- Coverage must stay above 85% (global frontend threshold and no regressions for touched backend modules).
- If any rule is intentionally violated, document reason, risk, and mitigation in the PR description.
## Testing Guidelines
- Backend suites: `go test -tags=unit ./...`, `go test -tags=integration ./...`, and e2e where relevant.
- Frontend uses Vitest (`jsdom`); keep tests near modules (`__tests__`) or as `*.spec.ts`.
- Enforce unit-test and coverage rules defined in `Quality Gate`.
- Before opening a PR, run `make test` plus targeted tests for touched areas.
## Commit & Pull Request Guidelines
- Follow Conventional Commits: `feat(scope): ...`, `fix(scope): ...`, `chore(scope): ...`, `docs(scope): ...`.
- PRs should include a clear summary, linked issue/spec, commands run for verification, and screenshots/GIFs for UI changes.
- For behavior/API changes, add or update `openspec/changes/...` artifacts.
- If dependencies change, commit `frontend/pnpm-lock.yaml` in the same PR.
## Security & Configuration Tips
- Use `deploy/.env.example` and `deploy/config.example.yaml` as templates; do not commit real credentials.
- Set stable `JWT_SECRET`, `TOTP_ENCRYPTION_KEY`, and strong database passwords outside local dev.
......@@ -137,8 +137,6 @@ curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install
使用 Docker Compose 部署,包含 PostgreSQL 和 Redis 容器。
如果你的服务器是 **Ubuntu 24.04**,建议直接参考:`deploy/ubuntu24-docker-compose-aicodex.md`,其中包含「安装最新版 Docker + docker-compose-aicodex.yml 部署」的完整步骤。
#### 前置条件
- Docker 20.10+
......
......@@ -122,7 +122,7 @@ func (s *SettingRepoSuite) TestSet_EmptyValue() {
func (s *SettingRepoSuite) TestSetMultiple_WithEmptyValues() {
// 模拟保存站点设置,部分字段有值,部分字段为空
settings := map[string]string{
"site_name": "AICodex2API",
"site_name": "Sub2api",
"site_subtitle": "Subscription to API",
"site_logo": "", // 用户未上传Logo
"api_base_url": "", // 用户未设置API地址
......@@ -136,7 +136,7 @@ func (s *SettingRepoSuite) TestSetMultiple_WithEmptyValues() {
result, err := s.repo.GetMultiple(s.ctx, []string{"site_name", "site_subtitle", "site_logo", "api_base_url", "contact_info", "doc_url"})
s.Require().NoError(err, "GetMultiple after SetMultiple with empty values")
s.Require().Equal("AICodex2API", result["site_name"])
s.Require().Equal("Sub2api", result["site_name"])
s.Require().Equal("Subscription to API", result["site_subtitle"])
s.Require().Equal("", result["site_logo"], "empty site_logo should be preserved")
s.Require().Equal("", result["api_base_url"], "empty api_base_url should be preserved")
......
#!/usr/bin/env bash
# 本地构建镜像的快速脚本,避免在命令行反复输入构建参数。
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
docker build -t sub2api:latest \
--build-arg GOPROXY=https://goproxy.cn,direct \
--build-arg GOSUMDB=sum.golang.google.cn \
-f "${SCRIPT_DIR}/Dockerfile" \
"${SCRIPT_DIR}"
......@@ -112,7 +112,7 @@ POSTGRES_DB=sub2api
DATABASE_PORT=5432
# -----------------------------------------------------------------------------
# PostgreSQL 服务端参数(可选;主要用于 deploy/docker-compose-aicodex.yml
# PostgreSQL 服务端参数(可选)
# -----------------------------------------------------------------------------
# POSTGRES_MAX_CONNECTIONS:PostgreSQL 服务端允许的最大连接数。
# 必须 >=(所有 Sub2API 实例的 DATABASE_MAX_OPEN_CONNS 之和)+ 预留余量(例如 20%)。
......@@ -163,7 +163,7 @@ REDIS_PORT=6379
# Leave empty for no password (default for local development)
REDIS_PASSWORD=
REDIS_DB=0
# Redis 服务端最大客户端连接数(可选;主要用于 deploy/docker-compose-aicodex.yml
# Redis 服务端最大客户端连接数(可选)
REDIS_MAXCLIENTS=50000
# Redis 连接池大小(默认 1024)
REDIS_POOL_SIZE=4096
......
# =============================================================================
# Sub2API Docker Compose Test Configuration (Local Build)
# =============================================================================
# Quick Start:
# 1. Copy .env.example to .env and configure
# 2. docker-compose -f docker-compose-test.yml up -d --build
# 3. Check logs: docker-compose -f docker-compose-test.yml logs -f sub2api
# 4. Access: http://localhost:8080
#
# This configuration builds the image from source (Dockerfile in project root).
# All configuration is done via environment variables.
# No Setup Wizard needed - the system auto-initializes on first run.
# =============================================================================
services:
# ===========================================================================
# Sub2API Application
# ===========================================================================
sub2api:
image: sub2api:latest
build:
context: ..
dockerfile: Dockerfile
container_name: sub2api
restart: unless-stopped
ulimits:
nofile:
soft: 100000
hard: 100000
ports:
- "${BIND_HOST:-0.0.0.0}:${SERVER_PORT:-8080}:8080"
volumes:
# Data persistence (config.yaml will be auto-generated here)
- sub2api_data:/app/data
# Mount custom config.yaml (optional, overrides auto-generated config)
# - ./config.yaml:/app/data/config.yaml:ro
environment:
# =======================================================================
# Auto Setup (REQUIRED for Docker deployment)
# =======================================================================
- AUTO_SETUP=true
# =======================================================================
# Server Configuration
# =======================================================================
- SERVER_HOST=0.0.0.0
- SERVER_PORT=8080
- SERVER_MODE=${SERVER_MODE:-release}
- RUN_MODE=${RUN_MODE:-standard}
# =======================================================================
# Database Configuration (PostgreSQL)
# =======================================================================
- DATABASE_HOST=postgres
- DATABASE_PORT=5432
- DATABASE_USER=${POSTGRES_USER:-sub2api}
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
- DATABASE_SSLMODE=disable
- DATABASE_MAX_OPEN_CONNS=${DATABASE_MAX_OPEN_CONNS:-50}
- DATABASE_MAX_IDLE_CONNS=${DATABASE_MAX_IDLE_CONNS:-10}
- DATABASE_CONN_MAX_LIFETIME_MINUTES=${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}
- DATABASE_CONN_MAX_IDLE_TIME_MINUTES=${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}
# =======================================================================
# Redis Configuration
# =======================================================================
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
- REDIS_DB=${REDIS_DB:-0}
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
- REDIS_MIN_IDLE_CONNS=${REDIS_MIN_IDLE_CONNS:-10}
# =======================================================================
# Admin Account (auto-created on first run)
# =======================================================================
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@sub2api.local}
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-}
# =======================================================================
# JWT Configuration
# =======================================================================
# Leave empty to auto-generate (recommended)
- JWT_SECRET=${JWT_SECRET:-}
- JWT_EXPIRE_HOUR=${JWT_EXPIRE_HOUR:-24}
# =======================================================================
# Timezone Configuration
# This affects ALL time operations in the application:
# - Database timestamps
# - Usage statistics "today" boundary
# - Subscription expiry times
# - Log timestamps
# Common values: Asia/Shanghai, America/New_York, Europe/London, UTC
# =======================================================================
- TZ=${TZ:-Asia/Shanghai}
# =======================================================================
# Gemini OAuth Configuration (for Gemini accounts)
# =======================================================================
- GEMINI_OAUTH_CLIENT_ID=${GEMINI_OAUTH_CLIENT_ID:-}
- GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-}
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
# Built-in OAuth client secrets (optional)
# SECURITY: This repo does not embed third-party client_secret.
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
# =======================================================================
# Security Configuration (URL Allowlist)
# =======================================================================
# Allow private IP addresses for CRS sync (for internal deployments)
- SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=${SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS:-true}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- sub2api-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ===========================================================================
# PostgreSQL Database
# ===========================================================================
postgres:
image: postgres:18-alpine
container_name: sub2api-postgres
restart: unless-stopped
ulimits:
nofile:
soft: 100000
hard: 100000
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
# postgres:18-alpine 默认 PGDATA=/var/lib/postgresql/18/docker(位于镜像声明的匿名卷 /var/lib/postgresql 内)。
# 若不显式设置 PGDATA,则即使挂载了 postgres_data 到 /var/lib/postgresql/data,数据也不会落盘到该命名卷,
# docker compose down/up 后会触发 initdb 重新初始化,导致用户/密码等数据丢失。
- PGDATA=/var/lib/postgresql/data
- POSTGRES_USER=${POSTGRES_USER:-sub2api}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
- POSTGRES_DB=${POSTGRES_DB:-sub2api}
- TZ=${TZ:-Asia/Shanghai}
networks:
- sub2api-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# 注意:不暴露端口到宿主机,应用通过内部网络连接
# 如需调试,可临时添加:ports: ["127.0.0.1:5433:5432"]
# ===========================================================================
# Redis Cache
# ===========================================================================
redis:
image: redis:8-alpine
container_name: sub2api-redis
restart: unless-stopped
ulimits:
nofile:
soft: 100000
hard: 100000
volumes:
- redis_data:/data
command: >
redis-server
--save 60 1
--appendonly yes
--appendfsync everysec
${REDIS_PASSWORD:+--requirepass ${REDIS_PASSWORD}}
environment:
- TZ=${TZ:-Asia/Shanghai}
# REDISCLI_AUTH is used by redis-cli for authentication (safer than -a flag)
- REDISCLI_AUTH=${REDIS_PASSWORD:-}
networks:
- sub2api-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
# =============================================================================
# Volumes
# =============================================================================
volumes:
sub2api_data:
driver: local
postgres_data:
driver: local
redis_data:
driver: local
# =============================================================================
# Networks
# =============================================================================
networks:
sub2api-network:
driver: bridge
# =============================================================================
# Docker Compose Override Configuration Example
# =============================================================================
# This file provides examples for customizing the Docker Compose setup.
# Copy this file to docker-compose.override.yml and modify as needed.
#
# Usage:
# cp docker-compose.override.yml.example docker-compose.override.yml
# # Edit docker-compose.override.yml with your settings
# docker-compose up -d
#
# IMPORTANT: docker-compose.override.yml is gitignored and will not be committed.
# =============================================================================
# =============================================================================
# Scenario 1: Use External Database and Redis (Recommended for Production)
# =============================================================================
# Use this when you have PostgreSQL and Redis running on the host machine
# or on separate servers.
#
# Prerequisites:
# - PostgreSQL running on host (accessible via host.docker.internal)
# - Redis running on host (accessible via host.docker.internal)
# - Update DATABASE_PORT and REDIS_PORT in .env file if using non-standard ports
#
# Security Notes:
# - Ensure PostgreSQL pg_hba.conf allows connections from Docker network
# - Use strong passwords for database and Redis
# - Consider using SSL/TLS for database connections in production
# =============================================================================
services:
sub2api:
# Remove dependencies on containerized postgres/redis
depends_on: []
# Enable access to host machine services
extra_hosts:
- "host.docker.internal:host-gateway"
# Override database and Redis connection settings
environment:
# PostgreSQL Configuration
DATABASE_HOST: host.docker.internal
DATABASE_PORT: "5678" # Change to your PostgreSQL port
# DATABASE_USER: postgres # Uncomment to override
# DATABASE_PASSWORD: your_password # Uncomment to override
# DATABASE_DBNAME: sub2api # Uncomment to override
# Redis Configuration
REDIS_HOST: host.docker.internal
REDIS_PORT: "6379" # Change to your Redis port
# REDIS_PASSWORD: your_redis_password # Uncomment if Redis requires auth
# REDIS_DB: 0 # Uncomment to override
# Disable containerized PostgreSQL
postgres:
deploy:
replicas: 0
scale: 0
# Disable containerized Redis
redis:
deploy:
replicas: 0
scale: 0
# =============================================================================
# Scenario 2: Development with Local Services (Alternative)
# =============================================================================
# Uncomment this section if you want to use the containerized postgres/redis
# but expose their ports for local development tools.
#
# Usage: Comment out Scenario 1 above and uncomment this section.
# =============================================================================
# services:
# sub2api:
# # Keep default dependencies
# pass
#
# postgres:
# ports:
# - "127.0.0.1:5432:5432" # Expose PostgreSQL on localhost
#
# redis:
# ports:
# - "127.0.0.1:6379:6379" # Expose Redis on localhost
# =============================================================================
# Scenario 3: Custom Network Configuration
# =============================================================================
# Uncomment if you need to connect to an existing Docker network
# =============================================================================
# networks:
# default:
# external: true
# name: your-existing-network
# =============================================================================
# Scenario 4: Resource Limits (Production)
# =============================================================================
# Uncomment to set resource limits for the sub2api container
# =============================================================================
# services:
# sub2api:
# deploy:
# resources:
# limits:
# cpus: '2.0'
# memory: 2G
# reservations:
# cpus: '1.0'
# memory: 1G
# =============================================================================
# Scenario 5: Custom Volumes
# =============================================================================
# Uncomment to mount additional volumes (e.g., for logs, backups)
# =============================================================================
# services:
# sub2api:
# volumes:
# - ./logs:/app/logs
# - ./backups:/app/backups
# =============================================================================
# Scenario 6: 启用宿主机 datamanagementd(数据管理)
# =============================================================================
# 说明:
# - datamanagementd 运行在宿主机(systemd 或手动)
# - 主进程固定探测 /tmp/sub2api-datamanagement.sock
# - 需要把宿主机 socket 挂载到容器内同路径
#
# services:
# sub2api:
# volumes:
# - /tmp/sub2api-datamanagement.sock:/tmp/sub2api-datamanagement.sock
# =============================================================================
# Additional Notes
# =============================================================================
# - This file overrides settings in docker-compose.yml
# - Environment variables in .env file take precedence
# - For more information, see: https://docs.docker.com/compose/extends/
# - Check the main README.md for detailed configuration instructions
# =============================================================================
```mermaid
flowchart TD
%% Master dispatch
A[HTTP Request] --> B{Route}
B -->|v1 messages| GA0
B -->|openai v1 responses| OA0
B -->|v1beta models model action| GM0
B -->|v1 messages count tokens| GT0
B -->|v1beta models list or get| GL0
%% =========================
%% FLOW A: Claude Gateway
%% =========================
subgraph FLOW_A["v1 messages Claude Gateway"]
GA0[Auth middleware] --> GA1[Read body]
GA1 -->|empty| GA1E[400 invalid_request_error]
GA1 --> GA2[ParseGatewayRequest]
GA2 -->|parse error| GA2E[400 invalid_request_error]
GA2 --> GA3{model present}
GA3 -->|no| GA3E[400 invalid_request_error]
GA3 --> GA4[streamStarted false]
GA4 --> GA5[IncrementWaitCount user]
GA5 -->|queue full| GA5E[429 rate_limit_error]
GA5 --> GA6[AcquireUserSlotWithWait]
GA6 -->|timeout or fail| GA6E[429 rate_limit_error]
GA6 --> GA7[BillingEligibility check post wait]
GA7 -->|fail| GA7E[403 billing_error]
GA7 --> GA8[Generate sessionHash]
GA8 --> GA9[Resolve platform]
GA9 --> GA10{platform gemini}
GA10 -->|yes| GA10Y[sessionKey gemini hash]
GA10 -->|no| GA10N[sessionKey hash]
GA10Y --> GA11
GA10N --> GA11
GA11[SelectAccountWithLoadAwareness] -->|err and no failed| GA11E1[503 no available accounts]
GA11 -->|err and failed| GA11E2[map failover error]
GA11 --> GA12[Warmup intercept]
GA12 -->|yes| GA12Y[return mock and release if held]
GA12 -->|no| GA13[Acquire account slot or wait]
GA13 -->|wait queue full| GA13E1[429 rate_limit_error]
GA13 -->|wait timeout| GA13E2[429 concurrency limit]
GA13 --> GA14[BindStickySession if waited]
GA14 --> GA15{account platform antigravity}
GA15 -->|yes| GA15Y[ForwardGemini antigravity]
GA15 -->|no| GA15N[Forward Claude]
GA15Y --> GA16[Release account slot and dec account wait]
GA15N --> GA16
GA16 --> GA17{UpstreamFailoverError}
GA17 -->|yes| GA18[mark failedAccountIDs and map error if exceed]
GA18 -->|loop| GA11
GA17 -->|no| GA19[success async RecordUsage and return]
GA19 --> GA20[defer release user slot and dec wait count]
end
%% =========================
%% FLOW B: OpenAI
%% =========================
subgraph FLOW_B["openai v1 responses"]
OA0[Auth middleware] --> OA1[Read body]
OA1 -->|empty| OA1E[400 invalid_request_error]
OA1 --> OA2[json Unmarshal body]
OA2 -->|parse error| OA2E[400 invalid_request_error]
OA2 --> OA3{model present}
OA3 -->|no| OA3E[400 invalid_request_error]
OA3 --> OA4{User Agent Codex CLI}
OA4 -->|no| OA4N[set default instructions]
OA4 -->|yes| OA4Y[no change]
OA4N --> OA5
OA4Y --> OA5
OA5[streamStarted false] --> OA6[IncrementWaitCount user]
OA6 -->|queue full| OA6E[429 rate_limit_error]
OA6 --> OA7[AcquireUserSlotWithWait]
OA7 -->|timeout or fail| OA7E[429 rate_limit_error]
OA7 --> OA8[BillingEligibility check post wait]
OA8 -->|fail| OA8E[403 billing_error]
OA8 --> OA9[sessionHash sha256 session_id]
OA9 --> OA10[SelectAccountWithLoadAwareness]
OA10 -->|err and no failed| OA10E1[503 no available accounts]
OA10 -->|err and failed| OA10E2[map failover error]
OA10 --> OA11[Acquire account slot or wait]
OA11 -->|wait queue full| OA11E1[429 rate_limit_error]
OA11 -->|wait timeout| OA11E2[429 concurrency limit]
OA11 --> OA12[BindStickySession openai hash if waited]
OA12 --> OA13[Forward OpenAI upstream]
OA13 --> OA14[Release account slot and dec account wait]
OA14 --> OA15{UpstreamFailoverError}
OA15 -->|yes| OA16[mark failedAccountIDs and map error if exceed]
OA16 -->|loop| OA10
OA15 -->|no| OA17[success async RecordUsage and return]
OA17 --> OA18[defer release user slot and dec wait count]
end
%% =========================
%% FLOW C: Gemini Native
%% =========================
subgraph FLOW_C["v1beta models model action Gemini Native"]
GM0[Auth middleware] --> GM1[Validate platform]
GM1 -->|invalid| GM1E[400 googleError]
GM1 --> GM2[Parse path modelName action]
GM2 -->|invalid| GM2E[400 googleError]
GM2 --> GM3{action supported}
GM3 -->|no| GM3E[404 googleError]
GM3 --> GM4[Read body]
GM4 -->|empty| GM4E[400 googleError]
GM4 --> GM5[streamStarted false]
GM5 --> GM6[IncrementWaitCount user]
GM6 -->|queue full| GM6E[429 googleError]
GM6 --> GM7[AcquireUserSlotWithWait]
GM7 -->|timeout or fail| GM7E[429 googleError]
GM7 --> GM8[BillingEligibility check post wait]
GM8 -->|fail| GM8E[403 googleError]
GM8 --> GM9[Generate sessionHash]
GM9 --> GM10[sessionKey gemini hash]
GM10 --> GM11[SelectAccountWithLoadAwareness]
GM11 -->|err and no failed| GM11E1[503 googleError]
GM11 -->|err and failed| GM11E2[mapGeminiUpstreamError]
GM11 --> GM12[Acquire account slot or wait]
GM12 -->|wait queue full| GM12E1[429 googleError]
GM12 -->|wait timeout| GM12E2[429 googleError]
GM12 --> GM13[BindStickySession if waited]
GM13 --> GM14{account platform antigravity}
GM14 -->|yes| GM14Y[ForwardGemini antigravity]
GM14 -->|no| GM14N[ForwardNative]
GM14Y --> GM15[Release account slot and dec account wait]
GM14N --> GM15
GM15 --> GM16{UpstreamFailoverError}
GM16 -->|yes| GM17[mark failedAccountIDs and map error if exceed]
GM17 -->|loop| GM11
GM16 -->|no| GM18[success async RecordUsage and return]
GM18 --> GM19[defer release user slot and dec wait count]
end
%% =========================
%% FLOW D: CountTokens
%% =========================
subgraph FLOW_D["v1 messages count tokens"]
GT0[Auth middleware] --> GT1[Read body]
GT1 -->|empty| GT1E[400 invalid_request_error]
GT1 --> GT2[ParseGatewayRequest]
GT2 -->|parse error| GT2E[400 invalid_request_error]
GT2 --> GT3{model present}
GT3 -->|no| GT3E[400 invalid_request_error]
GT3 --> GT4[BillingEligibility check]
GT4 -->|fail| GT4E[403 billing_error]
GT4 --> GT5[ForwardCountTokens]
end
%% =========================
%% FLOW E: Gemini Models List Get
%% =========================
subgraph FLOW_E["v1beta models list or get"]
GL0[Auth middleware] --> GL1[Validate platform]
GL1 -->|invalid| GL1E[400 googleError]
GL1 --> GL2{force platform antigravity}
GL2 -->|yes| GL2Y[return static fallback models]
GL2 -->|no| GL3[SelectAccountForAIStudioEndpoints]
GL3 -->|no gemini and has antigravity| GL3Y[return fallback models]
GL3 -->|no accounts| GL3E[503 googleError]
GL3 --> GL4[ForwardAIStudioGET]
GL4 -->|error| GL4E[502 googleError]
GL4 --> GL5[Passthrough response or fallback]
end
%% =========================
%% SHARED: Account Selection
%% =========================
subgraph SELECT["SelectAccountWithLoadAwareness detail"]
S0[Start] --> S1{concurrencyService nil OR load batch disabled}
S1 -->|yes| S2[SelectAccountForModelWithExclusions legacy]
S2 --> S3[tryAcquireAccountSlot]
S3 -->|acquired| S3Y[SelectionResult Acquired true ReleaseFunc]
S3 -->|not acquired| S3N[WaitPlan FallbackTimeout MaxWaiting]
S1 -->|no| S4[Resolve platform]
S4 --> S5[List schedulable accounts]
S5 --> S6[Layer1 Sticky session]
S6 -->|hit and valid| S6A[tryAcquireAccountSlot]
S6A -->|acquired| S6AY[SelectionResult Acquired true]
S6A -->|not acquired and waitingCount < StickyMax| S6AN[WaitPlan StickyTimeout Max]
S6 --> S7[Layer2 Load aware]
S7 --> S7A[Load batch concurrency plus wait to loadRate]
S7A --> S7B[Sort priority load LRU OAuth prefer for Gemini]
S7B --> S7C[tryAcquireAccountSlot in order]
S7C -->|first success| S7CY[SelectionResult Acquired true]
S7C -->|none| S8[Layer3 Fallback wait]
S8 --> S8A[Sort priority LRU]
S8A --> S8B[WaitPlan FallbackTimeout Max]
end
%% =========================
%% SHARED: Wait Acquire
%% =========================
subgraph WAIT["AcquireXSlotWithWait detail"]
W0[Try AcquireXSlot immediately] -->|acquired| W1[return ReleaseFunc]
W0 -->|not acquired| W2[Wait loop with timeout]
W2 --> W3[Backoff 100ms x1.5 jitter max2s]
W2 --> W4[If streaming and ping format send SSE ping]
W2 --> W5[Retry AcquireXSlot on timer]
W5 -->|acquired| W1
W2 -->|timeout| W6[ConcurrencyError IsTimeout true]
end
%% =========================
%% SHARED: Account Wait Queue
%% =========================
subgraph AQ["Account Wait Queue Redis Lua"]
Q1[IncrementAccountWaitCount] --> Q2{current >= max}
Q2 -->|yes| Q2Y[return false]
Q2 -->|no| Q3[INCR and if first set TTL]
Q3 --> Q4[return true]
Q5[DecrementAccountWaitCount] --> Q6[if current > 0 then DECR]
end
%% =========================
%% SHARED: Background cleanup
%% =========================
subgraph CLEANUP["Slot Cleanup Worker"]
C0[StartSlotCleanupWorker interval] --> C1[List schedulable accounts]
C1 --> C2[CleanupExpiredAccountSlots per account]
C2 --> C3[Repeat every interval]
end
```
# 后端热点 API 性能优化审计与行动计划(2026-02-22)
## 1. 目标与范围
本次文档用于沉淀后端热点 API 的性能审计结果,并给出可执行优化方案。
重点链路:
- `POST /v1/messages`
- `POST /v1/responses`
- `POST /sora/v1/chat/completions`
- `POST /v1beta/models/*modelAction`(Gemini 兼容链路)
- 相关调度、计费、Ops 记录链路
## 2. 审计方式与结论边界
- 审计方式:静态代码审阅(只读),未对生产环境做侵入变更。
- 结论类型:以“高置信度可优化点”为主,均附 `file:line` 证据。
- 未覆盖项:本轮未执行压测与火焰图采样,吞吐增益需在压测环境量化确认。
## 3. 优先级总览
| 优先级 | 数量 | 结论 |
|---|---:|---|
| P0(Critical) | 2 | 存在资源失控风险,建议立即修复 |
| P1(High) | 2 | 明确的热点 DB/Redis 放大路径,建议本迭代完成 |
| P2(Medium) | 4 | 可观收益优化项,建议并行排期 |
## 4. 详细问题清单
### 4.1 P0-1:使用量记录为“每请求一个 goroutine”,高峰下可能无界堆积
证据位置:
- `backend/internal/handler/gateway_handler.go:435`
- `backend/internal/handler/gateway_handler.go:704`
- `backend/internal/handler/openai_gateway_handler.go:382`
- `backend/internal/handler/sora_gateway_handler.go:400`
- `backend/internal/handler/gemini_v1beta_handler.go:523`
问题描述:
- 记录用量使用 `go func(...)` 直接异步提交,未设置全局并发上限与排队背压。
- 当 DB/Redis 变慢时,goroutine 数会随请求持续累积。
性能影响:
- `goroutine` 激增导致调度开销上升与内存占用增加。
- 与数据库连接池(默认 `max_open_conns=256`)竞争,放大尾延迟。
优化建议:
- 引入“有界队列 + 固定 worker 池”替代每请求 goroutine。
- 队列满时采用明确策略:丢弃(采样告警)或降级为同步短路。
-`RecordUsage` 路径增加超时、重试上限与失败计数指标。
验收指标:
- 峰值 `goroutines` 稳定,无线性增长。
- 用量记录成功率、丢弃率、队列长度可观测。
---
### 4.2 P0-2:Ops 错误日志队列携带原始请求体,存在内存放大风险
证据位置:
- 队列容量与 job 结构:`backend/internal/handler/ops_error_logger.go:38``backend/internal/handler/ops_error_logger.go:43`
- 入队逻辑:`backend/internal/handler/ops_error_logger.go:132`
- 请求体放入 context:`backend/internal/handler/ops_error_logger.go:261`
- 读取并入队:`backend/internal/handler/ops_error_logger.go:548``backend/internal/handler/ops_error_logger.go:563``backend/internal/handler/ops_error_logger.go:727``backend/internal/handler/ops_error_logger.go:737`
- 入库前才裁剪:`backend/internal/service/ops_service.go:332``backend/internal/service/ops_service.go:339`
- 请求体默认上限:`backend/internal/config/config.go:1082``backend/internal/config/config.go:1086`
问题描述:
- 队列元素包含 `[]byte requestBody`,在请求体较大且错误风暴时会显著占用内存。
- 当前裁剪发生在 worker 消费时,而不是入队前。
性能影响:
- 容易造成瞬时高内存与频繁 GC。
- 极端情况下可能触发 OOM 或服务抖动。
优化建议:
- 入队前进行“脱敏 + 裁剪”,仅保留小尺寸结构化片段(建议 8KB~16KB)。
- 队列存放轻量 DTO,避免持有大块 `[]byte`
- 按错误类型控制采样率,避免同类错误洪峰时日志放大。
验收指标:
- Ops 错误风暴期间 RSS/GC 次数显著下降。
- 队列满时系统稳定且告警可见。
---
### 4.3 P1-1:窗口费用检查在缓存 miss 时逐账号做 DB 聚合
证据位置:
- 候选筛选多处调用:`backend/internal/service/gateway_service.go:1109``backend/internal/service/gateway_service.go:1137``backend/internal/service/gateway_service.go:1291``backend/internal/service/gateway_service.go:1354`
- miss 后单账号聚合:`backend/internal/service/gateway_service.go:1791`
- SQL 聚合实现:`backend/internal/repository/usage_log_repo.go:889`
- 窗口费用缓存 TTL:`backend/internal/repository/session_limit_cache.go:33`
- 已有批量读取接口但未利用:`backend/internal/repository/session_limit_cache.go:310`
问题描述:
- 路由候选过滤阶段频繁调用窗口费用检查。
- 缓存未命中时逐账号执行聚合查询,账号多时放大 DB 压力。
性能影响:
- 路由耗时上升,数据库聚合 QPS 增长。
- 高并发下可能形成“缓存抖动 + 聚合风暴”。
优化建议:
- 先批量 `GetWindowCostBatch`,仅对 miss 账号执行批量 SQL 聚合。
- 将聚合结果批量回写缓存,降低重复查询。
- 评估窗口费用缓存 TTL 与刷新策略,减少抖动。
验收指标:
- 路由阶段 DB 查询次数下降。
- `SelectAccountWithLoadAwareness` 平均耗时下降。
---
### 4.4 P1-2:记录用量时每次查询用户分组倍率,形成稳定 DB 热点
证据位置:
- `backend/internal/service/gateway_service.go:5316`
- `backend/internal/service/gateway_service.go:5531`
- `backend/internal/repository/user_group_rate_repo.go:45`
问题描述:
- `RecordUsage``RecordUsageWithLongContext` 每次都执行 `GetByUserAndGroup`
- 热路径重复读数据库,且与 usage 写入、扣费路径竞争连接池。
性能影响:
- 增加 DB 往返与延迟,降低热点接口吞吐。
优化建议:
- 在鉴权或路由阶段预热倍率并挂载上下文复用。
- 引入 L1/L2 缓存(短 TTL + singleflight),减少重复 SQL。
验收指标:
- `GetByUserAndGroup` 调用量明显下降。
- 计费链路 p95 延迟下降。
---
### 4.5 P2-1:Claude 消息链路重复 JSON 解析
证据位置:
- 首次解析:`backend/internal/handler/gateway_handler.go:129`
- 二次解析入口:`backend/internal/handler/gateway_handler.go:146`
- 二次 `json.Unmarshal``backend/internal/handler/gateway_helper.go:22``backend/internal/handler/gateway_helper.go:26`
问题描述:
- 同一请求先 `ParseGatewayRequest`,后 `SetClaudeCodeClientContext` 再做 `Unmarshal`
性能影响:
- 增加 CPU 与内存分配,尤其对大 `messages` 请求更明显。
优化建议:
- 仅在 `User-Agent` 命中 Claude CLI 规则后再做 body 深解析。
- 或直接复用首轮解析结果,避免重复反序列化。
---
### 4.6 P2-2:同一请求中粘性会话账号查询存在重复 Redis 读取
证据位置:
- Handler 预取:`backend/internal/handler/gateway_handler.go:242`
- Service 再取:`backend/internal/service/gateway_service.go:941``backend/internal/service/gateway_service.go:1129``backend/internal/service/gateway_service.go:1277`
问题描述:
- 同一会话映射在同请求链路被多次读取。
性能影响:
- 增加 Redis RTT 与序列化开销,抬高路由延迟。
优化建议:
- 统一在 `SelectAccountWithLoadAwareness` 内读取并复用。
- 或将上层已读到的 sticky account 显式透传给 service。
---
### 4.7 P2-3:并发等待路径存在重复抢槽
证据位置:
- 首次 TryAcquire:`backend/internal/handler/gateway_helper.go:182``backend/internal/handler/gateway_helper.go:202`
- wait 内再次立即 Acquire:`backend/internal/handler/gateway_helper.go:226``backend/internal/handler/gateway_helper.go:230``backend/internal/handler/gateway_helper.go:232`
问题描述:
- 进入 wait 流程后会再做一次“立即抢槽”,与上层 TryAcquire 重复。
性能影响:
- 在高并发下增加 Redis 操作次数,放大锁竞争。
优化建议:
- wait 流程直接进入退避循环,避免重复立即抢槽。
---
### 4.8 P2-4:`/v1/models` 每次走仓储查询与对象装配,未复用快照/短缓存
证据位置:
- 入口调用:`backend/internal/handler/gateway_handler.go:767`
- 服务查询:`backend/internal/service/gateway_service.go:6152``backend/internal/service/gateway_service.go:6154`
- 对象装配:`backend/internal/repository/account_repo.go:1276``backend/internal/repository/account_repo.go:1290``backend/internal/repository/account_repo.go:1298`
问题描述:
- 模型列表请求每次都落到账号查询与附加装配,缺少短时缓存。
性能影响:
- 高频请求下持续占用 DB 与 CPU。
优化建议:
-`groupID + platform` 建 10s~30s 本地缓存。
- 或复用调度快照 bucket 的可用账号结果做模型聚合。
## 5. 建议实施顺序
### 阶段 A(立即,P0)
- 将“用量记录每请求 goroutine”改为有界异步管道。
- Ops 错误日志改为“入队前裁剪 + 轻量队列对象”。
### 阶段 B(短期,P1)
- 批量化窗口费用检查(缓存 + SQL 双批量)。
- 用户分组倍率加缓存/上下文复用。
### 阶段 C(中期,P2)
- 消除重复 JSON 解析与重复 sticky 查询。
- 优化并发等待重复抢槽逻辑。
- `/v1/models` 接口加入短缓存或快照复用。
## 6. 压测与验证建议
建议在预发压测以下场景:
- 场景 1:常规成功流量(验证吞吐与延迟)。
- 场景 2:上游慢响应(验证 goroutine 与队列稳定性)。
- 场景 3:错误风暴(验证 Ops 队列与内存上限)。
- 场景 4:多账号大分组路由(验证窗口费用批量化收益)。
建议监控指标:
- 进程:`goroutines`、RSS、GC 次数/停顿。
- API:各热点接口 p50/p95/p99。
- DB:QPS、慢查询、连接池等待。
- Redis:命中率、RTT、命令量。
- 业务:用量记录成功率/丢弃率、Ops 日志丢弃率。
## 7. 待补充数据
- 生产真实错误率与错误体大小分布。
- `window_cost_limit` 实际启用账号比例。
- `/v1/models` 实际调用频次。
- DB/Redis 当前容量余量与瓶颈点。
---
如需进入实现阶段,建议按“阶段 A → 阶段 B → 阶段 C”分 PR 推进,每个阶段都附压测报告与回滚方案。
-- 修正 schema_migrations 中“本地改名”的迁移文件名
-- 适用场景:你已执行过旧文件名的迁移,合并后仅改了自己这边的文件名
BEGIN;
UPDATE schema_migrations
SET filename = '042b_add_ops_system_metrics_switch_count.sql'
WHERE filename = '042_add_ops_system_metrics_switch_count.sql'
AND NOT EXISTS (
SELECT 1 FROM schema_migrations WHERE filename = '042b_add_ops_system_metrics_switch_count.sql'
);
UPDATE schema_migrations
SET filename = '043b_add_group_invalid_request_fallback.sql'
WHERE filename = '043_add_group_invalid_request_fallback.sql'
AND NOT EXISTS (
SELECT 1 FROM schema_migrations WHERE filename = '043b_add_group_invalid_request_fallback.sql'
);
UPDATE schema_migrations
SET filename = '044b_add_group_mcp_xml_inject.sql'
WHERE filename = '044_add_group_mcp_xml_inject.sql'
AND NOT EXISTS (
SELECT 1 FROM schema_migrations WHERE filename = '044b_add_group_mcp_xml_inject.sql'
);
UPDATE schema_migrations
SET filename = '046b_add_group_supported_model_scopes.sql'
WHERE filename = '046_add_group_supported_model_scopes.sql'
AND NOT EXISTS (
SELECT 1 FROM schema_migrations WHERE filename = '046b_add_group_supported_model_scopes.sql'
);
COMMIT;
......@@ -10,6 +10,7 @@ import { resolve } from 'path'
function injectPublicSettings(backendUrl: string): Plugin {
return {
name: 'inject-public-settings',
apply: 'serve',
transformIndexHtml: {
order: 'pre',
async handler(html) {
......
schema: spec-driven
# Project context (optional)
# This is shown to AI when creating artifacts.
# Add your tech stack, conventions, style guides, domain knowledge, etc.
# Example:
# context: |
# Tech stack: TypeScript, React, Node.js
# We use conventional commits
# Domain: e-commerce platform
# Per-artifact rules (optional)
# Add custom rules for specific artifacts.
# Example:
# rules:
# proposal:
# - Keep proposals under 500 words
# - Always include a "Non-goals" section
# tasks:
# - Break tasks into chunks of max 2 hours
# Project Context
## Purpose
[Describe your project's purpose and goals]
## Tech Stack
- [List your primary technologies]
- [e.g., TypeScript, React, Node.js]
## Project Conventions
### Code Style
[Describe your code style preferences, formatting rules, and naming conventions]
### Architecture Patterns
[Document your architectural decisions and patterns]
### Testing Strategy
[Explain your testing approach and requirements]
### Git Workflow
[Describe your branching strategy and commit conventions]
## Domain Context
[Add domain-specific knowledge that AI assistants need to understand]
## Important Constraints
[List any technical, business, or regulatory constraints]
## External Dependencies
[Document key external services, APIs, or systems]
This diff is collapsed.
---
name: code-review-expert
description: >
通用代码审核专家 — 基于 git worktree 隔离的多 Agent 并行代码审核系统,集成 Context7 MCP 三重验证对抗代码幻觉。
语言无关,适用于任意技术栈(Go, Python, JS/TS, Rust, Java, C# 等)。
Use when: (1) 用户要求代码审核、code review、安全审计、性能审查,
(2) 用户说"审核代码"、"review"、"检查代码质量"、"安全检查",
(3) 用户要求对 PR、分支、目录或文件做全面质量检查,
(4) 用户提到"代码审核专家"或"/code-review-expert"。
五大审核维度:安全合规、架构设计、性能资源、可靠性数据完整性、代码质量可观测性。
自动创建 5 个 git worktree 隔离环境,派发 5 个专项子 Agent 并行审核,
通过 Context7 MCP 拉取最新官方文档验证 API 用法,消除 LLM 幻觉,
汇总后生成结构化 Markdown 审核报告,最终自动清理所有 worktree。
---
# Universal Code Review Expert
基于 git worktree 隔离 + 5 子 Agent 并行 + Context7 反幻觉验证的通用代码审核系统。
## Guardrails
- **只读审核**,绝不修改源代码,写入仅限报告文件
- **语言无关**,通过代码模式识别而非编译发现问题
- 每个子 Agent 在独立 **git worktree** 中工作
- 审核结束后**无条件清理**所有 worktree(即使中途出错)
- 问题必须给出**具体 `file:line`**,不接受泛泛而谈
- 涉及第三方库 API 的发现必须通过 **Context7 MCP** 验证,严禁凭记忆断言 API 状态
- 文件 > 500 个时自动启用**采样策略**
- **上下文保护**:严格遵循下方 Context Budget Control 规则,防止 200K 上下文耗尽
## Context Budget Control (上下文预算管理)
> **核心问题**:5 个子 Agent 并行审核时,每个 Agent 读取大量文件会快速耗尽 200K 上下文,导致审核卡住或失败。
### 预算分配策略
主 Agent 在 Phase 0 必须计算上下文预算,并分配给子 Agent:
```
总可用上下文 ≈ 180K tokens(预留 20K 给主 Agent 汇总)
每个子 Agent 预算 = 180K / 5 = 36K tokens
每个子 Agent 可读取的文件数 ≈ 36K / 平均文件大小
```
### 七项强制规则
1. **文件分片不重叠**:每个文件只分配给**一个主要维度**(按文件类型/路径自动判断),不要多维度重复审核同一文件。高风险文件(auth、crypto、payment)例外,可分配给最多 2 个维度。
2. **单文件读取上限**:子 Agent 读取单个文件时,使用 `Read` 工具的 `limit` 参数,每次最多读取 **300 行**。超过 300 行的文件分段读取,仅审核关键段落。
3. **子 Agent prompt 精简**:传递给子 Agent 的 prompt 只包含:
- 该维度的**精简检查清单**(不要传全部 170 项,只传该维度的 ~30 项)
- 文件列表(路径即可,不包含内容)
- C7 缓存中**该维度相关的**部分(不传全量缓存)
- 输出格式模板(一次,不重复)
4. **结果输出精简**:子 Agent 找到问题后只输出 JSON Lines,**不要**输出解释性文字、思考过程或总结。完成后只输出 status 行。
5. **子 Agent max_turns 限制**:每个子 Agent 使用 `max_turns` 参数限制最大轮次:
- 文件数 ≤ 10: `max_turns=15`
- 文件数 11-30: `max_turns=25`
- 文件数 31-60: `max_turns=40`
- 文件数 > 60: `max_turns=50`
6. **大仓库自动降级**
- 文件数 > 200:减为 **3 个子 Agent**(安全+可靠性、架构+性能、质量+可观测性)
- 文件数 > 500:减为 **2 个子 Agent**(安全重点、质量重点)+ 采样 30%
- 文件数 > 1000:单 Agent 串行 + 采样 15% + 仅审核变更文件
7. **子 Agent 使用 `run_in_background`**:所有子 Agent Task 调用设置 `run_in_background=true`,主 Agent 通过 Read 工具轮询 output_file 获取结果,避免子 Agent 的完整输出回填到主 Agent 上下文。
### 文件分配算法
按文件路径/后缀自动分配到主要维度:
| 模式 | 主维度 | 辅助维度(仅高风险文件) |
|------|--------|----------------------|
| `*auth*`, `*login*`, `*jwt*`, `*oauth*`, `*crypto*`, `*secret*` | Security | Reliability |
| `*route*`, `*controller*`, `*handler*`, `*middleware*`, `*service*` | Architecture | - |
| `*cache*`, `*pool*`, `*buffer*`, `*queue*`, `*worker*` | Performance | - |
| `*db*`, `*model*`, `*migration*`, `*transaction*` | Reliability | Performance |
| `*test*`, `*spec*`, `*log*`, `*metric*`, `*config*`, `*deploy*` | Quality | - |
| 其余文件 | 按目录轮询分配到 5 个维度 | - |
### 主 Agent 汇总时的上下文控制
Phase 3 汇总时,主 Agent **不要**重新读取子 Agent 审核过的文件。仅基于子 Agent 输出的 JSON Lines 进行:
- 去重合并
- 严重等级排序
- Context7 交叉验证(仅对 critical/high 且未验证的少数发现)
- 填充报告模板
---
## Workflow
### Phase 0 — Scope Determination
1. **确定审核范围**(按优先级):
- 用户指定的文件/目录
- 未提交变更:`git diff --name-only` + `git diff --cached --name-only`
- 未推送提交:`git log origin/{main}..HEAD --name-only --pretty=format:""`
- 全仓库(启用采样:变更文件 → 高风险目录 → 入口文件 → 其余 30% 采样)
2. **收集项目元信息**:语言构成、目录结构、文件数量
3. **生成会话 ID**
```bash
SESSION_ID="cr-$(date +%Y%m%d-%H%M%S)-$(openssl rand -hex 4)"
WORKTREE_BASE="/tmp/${SESSION_ID}"
```
4. 将文件分配给 5 个审核维度(每个文件可被多维度审核)
### Phase 0.5 — Context7 Documentation Warm-up (反幻觉第一重)
> 详细流程见 [references/context7-integration.md](references/context7-integration.md)
1. 扫描依赖清单(go.mod, package.json, requirements.txt, Cargo.toml, pom.xml 等)
2. 提取核心直接依赖,按优先级筛选最多 **10 个关键库**
- P0 框架核心(web 框架、ORM)→ P1 安全相关 → P2 高频 import → P3 其余
3. 对每个库调用 `resolve-library-id``get-library-docs`(每库 ≤ 5000 tokens)
4. 构建 **C7 知识缓存 JSON**,传递给所有子 Agent
5. **降级**:Context7 不可用时跳过,报告标注 "未经官方文档验证"
### Phase 1 — Worktree Creation
```bash
CURRENT_COMMIT=$(git rev-parse HEAD)
for dim in security architecture performance reliability quality; do
git worktree add "${WORKTREE_BASE}/${dim}" "${CURRENT_COMMIT}" --detach
done
```
### Phase 2 — Parallel Sub-Agent Dispatch (反幻觉第二重)
**在一条消息中发出所有 Task 调用**`subagent_type: general-purpose`),**必须设置**
- `run_in_background: true` — 子 Agent 后台运行,结果写入 output_file,避免回填主 Agent 上下文
- `max_turns` — 按文件数量设置(见 Context Budget Control)
- `model: "sonnet"` — 子 Agent 使用 sonnet 模型降低延迟和 token 消耗
Agent 数量根据文件规模自动调整(见 Context Budget Control 大仓库降级规则)。
每个 Agent 收到:
| 参数 | 内容 |
|------|------|
| worktree 路径 | `${WORKTREE_BASE}/{dimension}` |
| 文件列表 | 该维度**独占分配**的文件(不重叠) |
| 检查清单 | 该维度对应的精简清单(~30 项,非全量 170 项) |
| C7 缓存 | 仅该维度相关的库文档摘要 |
| 输出格式 | JSON Lines(见下方) |
| 文件读取限制 | 单文件最多 300 行,使用 Read 的 limit 参数 |
每个发现输出一行 JSON:
```json
{
"dimension": "security",
"severity": "critical|high|medium|low|info",
"file": "path/to/file.go",
"line": 42,
"rule": "SEC-001",
"title": "SQL Injection",
"description": "详细描述",
"suggestion": "修复建议(含代码片段)",
"confidence": "high|medium|low",
"c7_verified": true,
"verification_method": "c7_cache|c7_realtime|model_knowledge",
"references": ["CWE-89"]
}
```
**关键规则**
- 涉及第三方库 API 的发现,未经 Context7 验证时 `confidence` 不得为 `high`
- `verification_method == "model_knowledge"` 的发现自动降一级置信度
- 每个子 Agent 最多消耗分配的 Context7 查询预算
- 完成后输出:`{"status":"complete","dimension":"...","files_reviewed":N,"issues_found":N,"c7_queries_used":N}`
### Phase 3 — Aggregation + Cross-Validation (反幻觉第三重)
1. 等待所有子 Agent 完成
2. 合并 findings,按 severity 排序
3. **Context7 交叉验证**
- 筛选 `c7_verified==false` 且 severity 为 critical/high 的 API 相关发现
- 主 Agent 独立调用 Context7 验证
- 验证通过 → 保留 | 验证失败 → 降级或删除(标记 `c7_invalidated`
4. 去重(同一 file:line 合并)
5. 生成报告到 `code-review-report.md`(模板见 [references/report-template.md](references/report-template.md)
### Phase 4 — Cleanup (必须执行)
```bash
for dim in security architecture performance reliability quality; do
git worktree remove "${WORKTREE_BASE}/${dim}" --force 2>/dev/null
done
git worktree prune
rm -rf "${WORKTREE_BASE}"
```
> 即使前面步骤失败也**必须执行**此清理。
## Severity Classification
| 等级 | 标签 | 定义 |
|------|------|------|
| P0 | `critical` | 已存在的安全漏洞或必然导致数据丢失/崩溃 |
| P1 | `high` | 高概率触发的严重问题或重大性能缺陷 |
| P2 | `medium` | 可能触发的问题或明显设计缺陷 |
| P3 | `low` | 代码质量问题,不直接影响运行 |
| P4 | `info` | 优化建议或最佳实践提醒 |
置信度:`high` / `medium` / `low`,低置信度须说明原因。
## Five Review Dimensions
每个维度对应一个子 Agent,详细检查清单见 [references/checklists.md](references/checklists.md)
1. **Security & Compliance** — 注入漏洞(10 类)、认证授权、密钥泄露、密码学、依赖安全、隐私保护
2. **Architecture & Design** — SOLID 原则、架构模式、API 设计、错误策略、模块边界
3. **Performance & Resource** — 算法复杂度、数据库性能、内存管理、并发性能、I/O、缓存、资源泄漏
4. **Reliability & Data Integrity** — 错误处理、空值安全、并发安全、事务一致性、超时重试、边界条件、优雅关闭
5. **Code Quality & Observability** — 复杂度、重复、命名、死代码、测试质量、日志、可观测性、构建部署
## Context7 Anti-Hallucination Overview
> 详细集成文档见 [references/context7-integration.md](references/context7-integration.md)
三重验证防御 5 类 LLM 幻觉:
| 幻觉类型 | 说明 | 防御层 |
|----------|------|--------|
| API 幻觉 | 错误断言函数签名 | 第一重 + 第二重 |
| 废弃幻觉 | 错误标记仍在用的 API 为 deprecated | 第二重 + 第三重 |
| 不存在幻觉 | 声称新增 API 不存在 | 第一重 + 第二重 |
| 参数幻觉 | 错误描述参数类型/默认值 | 第二重实时查 |
| 版本混淆 | 混淆不同版本 API 行为 | 第一重版本锚定 |
验证覆盖度评级:`FULL` (100% API 发现已验证) > `PARTIAL` (50%+) > `LIMITED` (<50%) > `NONE`
## Error Handling
- 某个子 Agent 失败:继续汇总其他结果,报告标注不完整维度
- git worktree 创建失败:`git worktree prune` 重试 → 仍失败则回退串行模式
- Context7 不可用:跳过验证阶段,报告标注 "未经官方文档验证"
- 所有情况下 **Phase 4 清理必须执行**
## Resources
- **[references/checklists.md](references/checklists.md)** — 5 个子 Agent 的完整检查清单 (~170 项)
- **[references/context7-integration.md](references/context7-integration.md)** — Context7 MCP 集成详细流程、缓存格式、查询规范
- **[references/report-template.md](references/report-template.md)** — 审核报告 Markdown 模板
# Sub-Agent Review Checklists
5 个子 Agent 的完整检查清单。每个子 Agent 在独立 git worktree 中工作。
---
## Agent 1: Security & Compliance (安全与合规)
### 1.1 Injection (注入漏洞)
- SQL 注入:字符串拼接 SQL、未使用参数化查询
- 命令注入:exec/system/os.Command/subprocess 拼接用户输入
- XSS:未转义的用户输入写入 HTML/DOM
- XXE:XML 解析器未禁用外部实体
- SSRF:用户可控 URL 用于服务端请求,缺少白名单
- LDAP 注入:LDAP 查询拼接用户输入
- SSTI:用户输入直接传入模板引擎
- 路径穿越:文件操作中未校验 `../`
- Header 注入:HTTP 响应头拼接用户输入 (CRLF)
- Log 注入:日志中拼接未净化的用户输入
### 1.2 Authentication & Authorization
- 缺少认证:敏感 API 端点未要求身份验证
- 越权访问:缺少资源归属校验(水平越权)
- 权限提升:普通用户可执行管理员操作(垂直越权)
- 会话管理:Session fixation、不安全 cookie、缺少超时
- JWT:弱签名算法 (none/HS256)、未验证签名、token 泄露
- OAuth:开放重定向、state 缺失、token 存储不安全
- 默认凭证:代码中预设的用户名密码
### 1.3 Secrets & Sensitive Data
- 硬编码密钥:API key、密码、token、连接字符串写在源码
- 密钥泄露:.env 提交版本控制、明文密码
- 日志泄露:敏感数据出现在日志/错误信息中
- API 响应泄露:接口返回超出必要范围的用户数据
- 错误信息泄露:堆栈、内部路径、数据库结构暴露
### 1.4 Cryptography
- 弱哈希:MD5/SHA1 用于密码或安全场景
- 不安全随机数:math/rand 替代 CSPRNG
- ECB 模式:AES-ECB 等不安全加密模式
- 硬编码 IV/Salt
- 缺少完整性校验:加密但未做 HMAC/AEAD
### 1.5 Dependency Security
- 已知漏洞:依赖清单中的 CVE
- 过时依赖:已停止维护的库
- 依赖来源:非官方源、typosquatting
- 许可证合规:GPL 等传染性许可证混入商业项目
### 1.6 Privacy & Data Protection
- PII 未加密存储或传输
- 缺少数据过期/删除机制
- 跨境传输未考虑地域合规
---
## Agent 2: Architecture & Design (架构与设计)
### 2.1 Design Principles
- SRP:类/函数/模块承担过多职责
- OCP:修改核心逻辑而非通过扩展点添加
- LSP:子类/实现违反父类/接口契约
- ISP:接口过大,强迫实现不需要的方法
- DIP:高层模块直接依赖低层实现
### 2.2 Architectural Patterns
- 分层违规:跨层直接调用
- 循环依赖:包/模块间循环引用
- 上帝对象:单类承载过多数据和行为
- 过度抽象:不必要的工厂/策略/装饰器
- 模式误用:强行套用不适合的设计模式
- 配置管理:硬编码环境相关值
### 2.3 API Design
- 一致性:同系统 API 风格不一致
- 向后兼容:破坏性变更未版本控制
- 幂等性:写操作缺少幂等保证
- 批量操作:逐条处理导致 N+1 网络请求
- 分页:大列表缺少分页/游标
- 错误响应:格式不统一、缺少错误码
### 2.4 Error Handling Strategy
- 错误传播:底层错误未包装丢失上下文
- 错误类型:字符串替代结构化错误
- 恢复策略:缺少重试/降级/断路器
- 边界处理:系统边界缺少防御性检查
### 2.5 Module Boundaries
- 接口定义:模块间通过实现而非接口通信
- 数据共享:模块间共享可变数据结构
- 事件/消息:同步调用链过长
- 领域模型:贫血模型、逻辑散落 Service 层
---
## Agent 3: Performance & Resource (性能与资源)
### 3.1 Algorithm & Data Structure
- 热路径上 O(n^2) 或更高复杂度
- 不当数据结构:线性查找替代哈希
- 循环内重复计算
- 不必要的排序/遍历
### 3.2 Database Performance
- N+1 查询:循环内逐条查询
- 缺少索引:WHERE/JOIN 字段未建索引
- 全表扫描
- 大事务持锁过久
- 连接池未配置或配置不当
- SELECT * 替代指定字段
### 3.3 Memory Management
- 内存泄漏:未释放引用、全局缓存无上限
- 循环内创建大对象/切片
- 未使用缓冲 I/O、一次性读取大文件
- 循环内字符串拼接
- 高频对象未使用池化
### 3.4 Concurrency Performance
- 全局锁替代细粒度锁
- 热点资源锁竞争
- 无限制创建 goroutine/线程
- 对只读数据加锁
- 无缓冲通道导致阻塞
### 3.5 I/O Performance
- 异步上下文中阻塞调用
- HTTP 客户端未复用连接
- 大响应未压缩
- 大数据一次性加载替代流式
### 3.6 Caching
- 频繁重复计算/查询未缓存
- 缓存穿透:不存在 key 反复查 DB
- 缓存雪崩:大量 key 同时过期
- 更新后未失效缓存
- 无界缓存导致 OOM
### 3.7 Resource Leaks
- 文件句柄:打开未关闭
- HTTP response body 未关闭
- 数据库查询结果集未关闭
- Timer/Ticker/订阅未取消
- Goroutine/线程启动后永不退出
---
## Agent 4: Reliability & Data Integrity (可靠性与数据完整性)
### 4.1 Error Handling
- 静默吞错:空 catch、忽略返回 error
- 泛型 catch:catch(Exception e)
- 错误消息缺少上下文 (who/what/why)
- 库代码中 panic/os.Exit
- 关键路径缺少 recover/降级
### 4.2 Null Safety
- 空指针解引用:未检查 nil/null
- Optional/Maybe 未正确解包
- 空集合直接取下标
- 长链式调用中环节返回 null
### 4.3 Concurrency Safety
- 数据竞争:无保护读写共享变量
- 死锁:多锁嵌套、不一致加锁顺序
- check-then-act 未加锁
- 非线程安全 Map 并发使用
- 向已关闭 channel 发送数据
### 4.4 Transaction & Consistency
- 多步数据库操作未包裹事务
- 不恰当的事务隔离级别
- 跨服务缺少补偿/Saga
- 异步处理缺少确认/重试
- 重试产生重复数据
### 4.5 Timeout & Retry
- HTTP/DB/RPC 调用未设超时
- 无限重试或缺少退避
- 调用链超时未传递/收缩
- 缺少断路器保护
### 4.6 Boundary Conditions
- 整数溢出:大数、类型截断
- 浮点精度:金额用浮点数
- 时区未明确
- UTF-8 多字节未处理
- 空集合边界
- 并发 first/last、空队列竞态
### 4.7 Graceful Shutdown
- 缺少 SIGTERM/SIGINT 处理
- 关闭时未等待进行中请求
- 未释放 DB 连接、文件句柄
- 内存中待写数据丢失
---
## Agent 5: Code Quality & Observability (代码质量与可观测性)
### 5.1 Complexity
- 函数圈复杂度 > 15
- 深层嵌套 > 4 层
- 函数超过 100 行
- 参数超过 5 个
- 单文件超过 500 行
### 5.2 Duplication
- 大段相似代码 > 10 行
- 相同业务逻辑多处独立实现
- 魔法数字/字符串多处出现
### 5.3 Naming & Readability
- 不符合语言惯例的命名
- 含义模糊:data/info/temp/result
- 同一概念不同命名
- 布尔命名不是 is/has/can/should
- 不通用缩写降低可读性
### 5.4 Dead Code & Tech Debt
- 未调用的函数、未使用的变量/导入
- 被注释的代码块
- TODO/FIXME/HACK 遗留
- 使用 deprecated API
### 5.5 Test Quality
- 关键业务路径缺少测试
- 断言仅检查"不报错"
- 缺少边界和异常路径测试
- 测试间隐式依赖
- 过度 mock
- 依赖时间/网络等外部状态
### 5.6 Logging
- 关键决策点缺少日志
- ERROR 级别用于非错误场景
- 字符串拼接而非结构化日志
- 日志含密码/token/PII
- 热路径过度日志
### 5.7 Observability
- 缺少业务指标(请求量、延迟、错误率)
- 跨服务缺少 trace ID
- 缺少 liveness/readiness 探针
- 关键故障路径缺少告警
### 5.8 Build & Deploy
- 构建结果依赖环境状态
- 缺少 lock 文件
- 开发/生产配置差异未文档化
- 迁移脚本缺少回滚方案
- 大功能上线缺少 feature flag
# Context7 MCP Anti-Hallucination Integration
## Overview
Context7 MCP 提供两个工具,用于拉取第三方库的最新官方文档,消除 LLM 训练数据时效性导致的代码审核幻觉。
## Tools
### resolve-library-id
```
输入: libraryName (如 "gin", "gorm", "react", "express")
输出: Context7 兼容的 library ID (如 "/gin-gonic/gin")
```
- 必须在 `get-library-docs` 之前调用
- 用户已提供 `/org/project` 格式 ID 时可跳过
- 解析失败则记录到 `c7_failures`,跳过该库
### get-library-docs
```
输入:
- context7CompatibleLibraryID: 从 resolve-library-id 获取
- topic (可选): 聚焦主题 (如 "middleware", "hooks", "query")
- tokens (可选): 最大返回 token 数 (默认 5000)
```
- 每个库每次审核最多调用 **3 次**
- 优先用 `topic` 缩小范围
- 缓存首次查询结果,后续复用
## Three-Layer Verification
### Layer 1: Pre-Review Warm-up (Phase 0.5)
在审核开始前预热文档缓存:
1. **扫描依赖清单**
```bash
for f in go.mod package.json requirements.txt Pipfile pyproject.toml \
Cargo.toml Gemfile pom.xml build.gradle composer.json mix.exs \
pubspec.yaml *.csproj; do
[ -f "$f" ] && echo "FOUND: $f"
done
```
2. **提取直接依赖**(按语言):
- Go: `go.mod` require 块(排除 `// indirect`
- Node: `package.json``dependencies`
- Python: `requirements.txt``pyproject.toml``[project.dependencies]`
- Rust: `Cargo.toml``[dependencies]`
- Java: `pom.xml``build.gradle` 的 implementation 依赖
3. **优先级筛选**(最多 10 个库):
- P0 框架核心:Web 框架、ORM、核心运行时
- P1 安全相关:认证库、加密库、JWT 库
- P2 高频使用:import 次数最多的库
- P3 其余依赖
4. **批量查询 Context7**
```
对每个库:
id = resolve-library-id(libraryName)
如果失败 → 记录到 c7_failures, 跳过
docs = get-library-docs(id, topic="核心 API 概览", tokens=5000)
缓存到 C7 知识缓存
queries_remaining[库名] = 2
```
5. **构建缓存 JSON**
```json
{
"session_id": "cr-20260207-143000-a1b2c3d4",
"libraries": {
"gin": {
"context7_id": "/gin-gonic/gin",
"docs_summary": "...(API 摘要)...",
"key_apis": ["gin.Context", "gin.Engine"],
"tokens_used": 5000
}
},
"queries_remaining": { "gin": 2 },
"c7_failures": []
}
```
> 多个 `resolve-library-id` 可并行调用。
### Layer 2: In-Review Realtime Verification (Phase 2)
子 Agent 审核代码时的实时验证规则:
**必须验证的场景**
1. 认为某个 API 调用方式错误 → 查 C7 确认当前版本签名
2. 认为某个 API 已废弃 → 查 C7 确认 deprecated 状态
3. 认为代码缺少某库提供的安全/性能特性 → 查 C7 确认该特性存在
4. 认为代码写法不兼容某版本 → 查 C7 拉取对应版本文档
**查询优先级**
1. 先查 C7 知识缓存(Phase 0.5 预热结果)
2. 缓存未命中 → 调用 `get-library-docs(id, topic="{具体 API 名}")`
3. 遵守每库 3 次查询上限
**标注字段**
```json
{
"c7_verified": true,
"c7_source": "gin.Context.JSON() accepts int status code and any interface{}",
"verification_method": "c7_cache"
}
```
`verification_method` 取值:
- `c7_cache` — 从预热缓存验证
- `c7_realtime` — 实时调用 Context7 验证
- `model_knowledge` — 未使用 Context7(置信度自动降一级)
### Layer 3: Post-Review Cross-Validation (Phase 3)
主 Agent 汇总时的最终验证:
```
对于每个 finding:
如果 c7_verified == false 且 severity in [critical, high]:
如果涉及第三方库 API:
docs = get-library-docs(libraryID, topic="{相关 API}")
如果文档支持 Agent 判断 → c7_verified = true, 保留
如果文档与 Agent 矛盾 → 降级为 info 或删除, 标记 c7_invalidated
如果 Context7 无数据 → 保留, 标注 unverifiable
否则 (纯逻辑问题):
跳过 C7 验证, 保持原判断
```
**强制规则**`verification_method == "model_knowledge"` 的 critical/high API 相关发现,未完成交叉验证则自动降级为 medium。
## Degradation Strategy
| 场景 | 行为 |
|------|------|
| Context7 MCP 未配置 | 跳过所有 C7 阶段,报告标注 NONE 覆盖度 |
| 网络超时 | 重试 1 次,仍失败则跳过该库 |
| `resolve-library-id` 失败 | 记录到 `c7_failures`,跳过该库 |
| 查询配额耗尽 | 使用已缓存的最佳信息 |
| 子 Agent 中 C7 调用失败 | 标注 `verification_method: "model_knowledge"`,降低置信度 |
## Report Section: Verification Statistics
审核报告中包含的 Context7 统计节:
| 指标 | 说明 |
|------|------|
| 检测到的依赖库总数 | 项目直接依赖数 |
| C7 成功解析的库 | resolve-library-id 成功数 |
| C7 解析失败的库 | 失败列表 |
| Pre-Review 查询次数 | Phase 0.5 的 get-library-docs 调用数 |
| In-Review 查询次数 | Phase 2 子 Agent 的实时查询总数 |
| Post-Review 查询次数 | Phase 3 交叉验证查询数 |
| C7 验证通过的发现数 | c7_verified == true |
| C7 纠正的误判数 | c7_invalidated 标记数 |
| 验证覆盖度评级 | FULL / PARTIAL / LIMITED / NONE |
## Anti-Hallucination Corrections Table
报告中记录被 Context7 纠正的误判:
| # | Agent | 原 Severity | 原 Title | 纠正原因 | C7 Source |
|---|-------|------------|---------|---------|-----------|
| 1 | Security | high | API deprecated | C7 文档显示该 API 在 v2.x 中仍为 stable | /lib/docs... |
# Code Review Report Template
审核报告保存到项目根目录的 `code-review-report.md`,使用以下模板:
---
```markdown
# Code Review Report
**Project:** {PROJECT_NAME}
**Branch:** {BRANCH}
**Commit:** {COMMIT_SHA}
**Date:** {DATE}
**Scope:** {SCOPE_DESCRIPTION}
**Files Reviewed:** {TOTAL_FILES}
---
## Executive Summary
| 等级 | 数量 | 占比 |
|------|------|------|
| Critical (P0) | {N} | {%} |
| High (P1) | {N} | {%} |
| Medium (P2) | {N} | {%} |
| Low (P3) | {N} | {%} |
| Info (P4) | {N} | {%} |
| **Total** | **{N}** | **100%** |
**Overall Risk:** {HIGH/MEDIUM/LOW} — {一句话总结}
**C7 Verification:** {FULL/PARTIAL/LIMITED/NONE}
---
## Critical Issues (P0) — Immediate Action Required
### [{RULE}] {TITLE}
- **File:** `{FILE}:{LINE}`
- **Dimension:** {DIMENSION}
- **Confidence:** {CONFIDENCE} | **C7 Verified:** {YES/NO}
- **Description:** {DESCRIPTION}
- **Suggestion:**
```{lang}
{CODE_SUGGESTION}
```
- **References:** {REFERENCES}
---
## High Issues (P1) — Fix Before Next Release
{同上格式}
---
## Medium Issues (P2) — Plan to Fix
{同上格式}
---
## Low Issues (P3) — Nice to Fix
| # | Rule | File:Line | Title | Confidence |
|---|------|-----------|-------|------------|
| 1 | {RULE} | `{FILE}:{LINE}` | {TITLE} | {CONF} |
---
## Info (P4) — Suggestions
| # | File:Line | Suggestion |
|---|-----------|------------|
| 1 | `{FILE}:{LINE}` | {SUGGESTION} |
---
## Hotspot Analysis
| Rank | File | Issues | Critical | High | Medium |
|------|------|--------|----------|------|--------|
| 1 | {FILE} | {N} | {N} | {N} | {N} |
---
## Dimension Summary
| 维度 | 文件数 | 问题数 | Critical | High |
|------|--------|--------|----------|------|
| Security & Compliance | {N} | {N} | {N} | {N} |
| Architecture & Design | {N} | {N} | {N} | {N} |
| Performance & Resource | {N} | {N} | {N} | {N} |
| Reliability & Data | {N} | {N} | {N} | {N} |
| Quality & Observability | {N} | {N} | {N} | {N} |
---
## Context7 Verification Statistics
| 指标 | 数值 |
|------|------|
| 依赖库总数 | {N} |
| C7 成功解析 | {N} |
| C7 解析失败 | {N} ({FAILED_LIBS}) |
| Pre-Review 查询 | {N} |
| In-Review 查询 | {N} |
| Post-Review 查询 | {N} |
| C7 验证通过 | {N} ({%}) |
| C7 纠正误判 | {N} |
| 覆盖度评级 | {FULL/PARTIAL/LIMITED/NONE} |
### Anti-Hallucination Corrections
| # | Agent | 原 Severity | Title | 纠正原因 | C7 Source |
|---|-------|------------|-------|---------|-----------|
| 1 | {AGENT} | {SEV} | {TITLE} | {REASON} | {SOURCE} |
---
## Recommendations
### Immediate Actions (This Sprint)
1. {P0/P1 对应行动项}
### Short-term (Next 2-3 Sprints)
1. {P2 对应行动项}
### Long-term
1. {架构级改进}
---
## Methodology
- **Type:** Multi-agent parallel review + Context7 anti-hallucination
- **Agents:** Security, Architecture, Performance, Reliability, Quality
- **Isolation:** Independent git worktrees per agent
- **Verification:** Context7 three-layer (warm-up → realtime → cross-validation)
- **Policy:** API findings ≥ high require C7 verification; unverified auto-downgraded
---
*Generated by Code Review Expert — Universal Multi-Agent Code Review System with Context7 Anti-Hallucination*
```
#!/usr/bin/env python3
import argparse
import json
import sys
from datetime import date
HIGH_SEVERITIES = {"high", "critical"}
REQUIRED_FIELDS = {"package", "advisory", "severity", "mitigation", "expires_on"}
def split_kv(line: str) -> tuple[str, str]:
# 解析 "key: value" 形式的简单 YAML 行,并去除引号。
key, value = line.split(":", 1)
value = value.strip()
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
return key.strip(), value
def parse_exceptions(path: str) -> list[dict]:
# 轻量解析异常清单,避免引入额外依赖。
exceptions = []
current = None
with open(path, "r", encoding="utf-8") as handle:
for raw in handle:
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("version:") or line.startswith("exceptions:"):
continue
if line.startswith("- "):
if current:
exceptions.append(current)
current = {}
line = line[2:].strip()
if line:
key, value = split_kv(line)
current[key] = value
continue
if current is not None and ":" in line:
key, value = split_kv(line)
current[key] = value
if current:
exceptions.append(current)
return exceptions
def pick_advisory_id(advisory: dict) -> str | None:
# 优先使用可稳定匹配的标识(GHSA/URL/CVE),避免误匹配到其他同名漏洞。
return (
advisory.get("github_advisory_id")
or advisory.get("url")
or (advisory.get("cves") or [None])[0]
or (str(advisory.get("id")) if advisory.get("id") is not None else None)
or advisory.get("title")
or advisory.get("advisory")
or advisory.get("overview")
)
def iter_vulns(data: dict):
# 兼容 pnpm audit 的不同输出结构(advisories / vulnerabilities),并提取 advisory 标识。
advisories = data.get("advisories")
if isinstance(advisories, dict):
for advisory in advisories.values():
name = advisory.get("module_name") or advisory.get("name")
severity = advisory.get("severity")
advisory_id = pick_advisory_id(advisory)
title = (
advisory.get("title")
or advisory.get("advisory")
or advisory.get("overview")
or advisory.get("url")
)
yield name, severity, advisory_id, title
vulnerabilities = data.get("vulnerabilities")
if isinstance(vulnerabilities, dict):
for name, vuln in vulnerabilities.items():
severity = vuln.get("severity")
via = vuln.get("via", [])
titles = []
advisories = []
if isinstance(via, list):
for item in via:
if isinstance(item, dict):
advisories.append(
item.get("github_advisory_id")
or item.get("url")
or item.get("source")
or item.get("title")
or item.get("name")
)
titles.append(
item.get("title")
or item.get("url")
or item.get("advisory")
or item.get("source")
)
elif isinstance(item, str):
advisories.append(item)
titles.append(item)
elif isinstance(via, str):
advisories.append(via)
titles.append(via)
title = "; ".join([t for t in titles if t])
for advisory_id in [a for a in advisories if a]:
yield name, severity, advisory_id, title
def normalize_severity(severity: str) -> str:
# 统一大小写,避免比较失败。
return (severity or "").strip().lower()
def normalize_package(name: str) -> str:
# 包名只去掉首尾空白,保留原始大小写,同时兼容非字符串输入。
if name is None:
return ""
return str(name).strip()
def normalize_advisory(advisory: str) -> str:
# advisory 统一为小写匹配,避免 GHSA/URL 因大小写差异导致漏匹配。
# pnpm 的 source 字段可能是数字,这里统一转为字符串以保证可比较。
if advisory is None:
return ""
return str(advisory).strip().lower()
def parse_date(value: str) -> date | None:
# 仅接受 ISO8601 日期格式,非法值视为无效。
try:
return date.fromisoformat(value)
except ValueError:
return None
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--audit", required=True)
parser.add_argument("--exceptions", required=True)
args = parser.parse_args()
with open(args.audit, "r", encoding="utf-8") as handle:
audit = json.load(handle)
# 读取异常清单并建立索引,便于快速匹配包名 + advisory。
exceptions = parse_exceptions(args.exceptions)
exception_index = {}
errors = []
for exc in exceptions:
missing = [field for field in REQUIRED_FIELDS if not exc.get(field)]
if missing:
errors.append(
f"Exception missing required fields {missing}: {exc.get('package', '<unknown>')}"
)
continue
exc_severity = normalize_severity(exc.get("severity"))
exc_package = normalize_package(exc.get("package"))
exc_advisory = normalize_advisory(exc.get("advisory"))
exc_date = parse_date(exc.get("expires_on"))
if exc_date is None:
errors.append(
f"Exception has invalid expires_on date: {exc.get('package', '<unknown>')}"
)
continue
if not exc_package or not exc_advisory:
errors.append("Exception missing package or advisory value")
continue
key = (exc_package, exc_advisory)
if key in exception_index:
errors.append(
f"Duplicate exception for {exc_package} advisory {exc.get('advisory')}"
)
continue
exception_index[key] = {
"raw": exc,
"severity": exc_severity,
"expires_on": exc_date,
}
today = date.today()
missing_exceptions = []
expired_exceptions = []
# 去重处理:同一包名 + advisory 可能在不同字段重复出现。
seen = set()
for name, severity, advisory_id, title in iter_vulns(audit):
sev = normalize_severity(severity)
if sev not in HIGH_SEVERITIES or not name:
continue
advisory_key = normalize_advisory(advisory_id)
if not advisory_key:
errors.append(
f"High/Critical vulnerability missing advisory id: {name} ({sev})"
)
continue
key = (normalize_package(name), advisory_key)
if key in seen:
continue
seen.add(key)
exc = exception_index.get(key)
if exc is None:
missing_exceptions.append((name, sev, advisory_id, title))
continue
if exc["severity"] and exc["severity"] != sev:
errors.append(
"Exception severity mismatch: "
f"{name} ({advisory_id}) expected {sev}, got {exc['severity']}"
)
if exc["expires_on"] and exc["expires_on"] < today:
expired_exceptions.append(
(name, sev, advisory_id, exc["expires_on"].isoformat())
)
if missing_exceptions:
errors.append("High/Critical vulnerabilities missing exceptions:")
for name, sev, advisory_id, title in missing_exceptions:
label = f"{name} ({sev})"
if advisory_id:
label = f"{label} [{advisory_id}]"
if title:
label = f"{label}: {title}"
errors.append(f"- {label}")
if expired_exceptions:
errors.append("Exceptions expired:")
for name, sev, advisory_id, expires_on in expired_exceptions:
errors.append(
f"- {name} ({sev}) [{advisory_id}] expired on {expires_on}"
)
if errors:
sys.stderr.write("\n".join(errors) + "\n")
return 1
print("Audit exceptions validated.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""OpenAI OAuth 灰度发布演练脚本(本地模拟)。
该脚本会启动本地 mock Ops API,调用 openai_oauth_gray_guard.py,
验证以下场景:
1) A/B/C/D 四个灰度批次均通过
2) 注入异常场景触发阈值告警并返回退出码 2(模拟自动回滚触发)
"""
from __future__ import annotations
import json
import subprocess
import threading
from dataclasses import dataclass
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from typing import Dict, Tuple
from urllib.parse import parse_qs, urlparse
ROOT = Path(__file__).resolve().parents[2]
GUARD_SCRIPT = ROOT / "tools" / "perf" / "openai_oauth_gray_guard.py"
REPORT_PATH = ROOT / "docs" / "perf" / "openai-oauth-gray-drill-report.md"
THRESHOLDS = {
"sla_percent_min": 99.5,
"ttft_p99_ms_max": 900,
"request_error_rate_percent_max": 2.0,
"upstream_error_rate_percent_max": 2.0,
}
STAGE_SNAPSHOTS: Dict[str, Dict[str, float]] = {
"A": {"sla": 99.78, "ttft": 780, "error_rate": 1.20, "upstream_error_rate": 1.05},
"B": {"sla": 99.82, "ttft": 730, "error_rate": 1.05, "upstream_error_rate": 0.92},
"C": {"sla": 99.86, "ttft": 680, "error_rate": 0.88, "upstream_error_rate": 0.80},
"D": {"sla": 99.89, "ttft": 640, "error_rate": 0.72, "upstream_error_rate": 0.67},
"rollback": {"sla": 97.10, "ttft": 1550, "error_rate": 6.30, "upstream_error_rate": 5.60},
}
class _MockHandler(BaseHTTPRequestHandler):
def _write_json(self, payload: dict) -> None:
raw = json.dumps(payload, ensure_ascii=False).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def log_message(self, format: str, *args): # noqa: A003
return
def do_GET(self): # noqa: N802
parsed = urlparse(self.path)
if parsed.path.endswith("/api/v1/admin/ops/settings/metric-thresholds"):
self._write_json({"code": 0, "message": "success", "data": THRESHOLDS})
return
if parsed.path.endswith("/api/v1/admin/ops/dashboard/overview"):
q = parse_qs(parsed.query)
stage = (q.get("group_id") or ["A"])[0]
snapshot = STAGE_SNAPSHOTS.get(stage, STAGE_SNAPSHOTS["A"])
self._write_json(
{
"code": 0,
"message": "success",
"data": {
"sla": snapshot["sla"],
"error_rate": snapshot["error_rate"],
"upstream_error_rate": snapshot["upstream_error_rate"],
"ttft": {"p99_ms": snapshot["ttft"]},
},
}
)
return
self.send_response(404)
self.end_headers()
def run_guard(base_url: str, stage: str) -> Tuple[int, str]:
cmd = [
"python",
str(GUARD_SCRIPT),
"--base-url",
base_url,
"--platform",
"openai",
"--time-range",
"30m",
"--group-id",
stage,
]
proc = subprocess.run(cmd, cwd=str(ROOT), capture_output=True, text=True)
output = (proc.stdout + "\n" + proc.stderr).strip()
return proc.returncode, output
def main() -> int:
server = HTTPServer(("127.0.0.1", 0), _MockHandler)
host, port = server.server_address
base_url = f"http://{host}:{port}"
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
lines = [
"# OpenAI OAuth 灰度守护演练报告",
"",
"> 类型:本地 mock 演练(用于验证灰度守护与回滚触发机制)",
f"> 生成脚本:`tools/perf/openai_oauth_gray_drill.py`",
"",
"## 1. 灰度批次结果(6.1)",
"",
"| 批次 | 流量比例 | 守护脚本退出码 | 结果 |",
"|---|---:|---:|---|",
]
batch_plan = [("A", "5%"), ("B", "20%"), ("C", "50%"), ("D", "100%")]
all_pass = True
for stage, ratio in batch_plan:
code, _ = run_guard(base_url, stage)
ok = code == 0
all_pass = all_pass and ok
lines.append(f"| {stage} | {ratio} | {code} | {'通过' if ok else '失败'} |")
lines.extend([
"",
"## 2. 回滚触发演练(6.2)",
"",
])
rollback_code, rollback_output = run_guard(base_url, "rollback")
rollback_triggered = rollback_code == 2
lines.append(f"- 注入异常场景退出码:`{rollback_code}`")
lines.append(f"- 是否触发回滚条件:`{'是' if rollback_triggered else '否'}`")
lines.append("- 关键信息摘录:")
excerpt = "\n".join(rollback_output.splitlines()[:8])
lines.append("```text")
lines.append(excerpt)
lines.append("```")
lines.extend([
"",
"## 3. 验收结论(6.3)",
"",
f"- 批次灰度结果:`{'通过' if all_pass else '不通过'}`",
f"- 回滚触发机制:`{'通过' if rollback_triggered else '不通过'}`",
f"- 结论:`{'通过(可进入真实环境灰度)' if all_pass and rollback_triggered else '不通过(需修复后复测)'}`",
])
REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
REPORT_PATH.write_text("\n".join(lines) + "\n", encoding="utf-8")
server.shutdown()
server.server_close()
print(f"drill report generated: {REPORT_PATH}")
return 0 if all_pass and rollback_triggered else 1
if __name__ == "__main__":
raise SystemExit(main())
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment