Files
llm-intelligence/TECHNICAL_DESIGN.md
2026-05-13 14:42:45 +08:00

76 KiB
Raw Blame History

LLM Intelligence Hub — 技术设计文档 v1.1

文档版本v1.1 日期2026-05-09 负责人宰相AI 辅助) 状态Phase 1 执行中,技术栈已修正为 Go+PostgreSQL


一、系统架构概览

1.1 整体架构

┌──────────────────────────────────────────────────────────────────────┐
│                        LLM Intelligence Hub                         │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────┐   ┌─────────────┐   ┌─────────────────────────┐  │
│  │      报告    │   │   Web UI    │   │  AI Agent / MCP Client  │  │
│  │   Phase 2才推送│   │  (Explorer+报告)  │   │     (REST API / MCP)    │  │
│  └──────┬──────┘   └──────┬──────┘   └────────────┬────────────┘  │
│         │                 │                        │                │
│  ┌──────▼──────────────────▼────────────────────────▼────────────┐  │
│  │                      Service Layer (Go)                         │  │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌──────────┐  │  │
│  │  │  Report    │  │    API     │  │  Scheduler │  │ Notifier │  │  │
│  │  │  Generator │  │   Server   │  │  (cron)    │  │  (告警)  │  │  │
│  │  └────────────┘  └────────────┘  └────────────┘  └──────────┘  │  │
│  └───────────────────────────┬────────────────────────────────────┘  │
│                              │                                        │
│  ┌───────────────────────────▼────────────────────────────────────┐  │
│  │                    Data Access Layer (database/sql + pq)          │  │
│  └───────────────────────────┬────────────────────────────────────┘  │
│                              │                                        │
│  ┌───────────────────────────▼────────────────────────────────────┐  │
│  │              Storage Layer (PostgreSQL)                         │  │
│  └────────────────────────────────────────────────────────────────┘  │
│                                                                      │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │                    Data Collection Layer                        │  │
│  │  ┌─────────────┐  ┌──────────────┐  ┌──────────────────────┐   │  │
│  │  │ OpenRouter  │  │ Phase 2才扩充厂商/中转平台 │  │  中转平台采集器       │   │  │
│  │  │  Collector  │  │ (10家厂商)    │  │ (硅基流动等)         │   │  │
│  │  └─────────────┘  └──────────────┘  └──────────────────────┘   │  │
│  └────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────┘

1.2 各层职责

| 层级 | 职责 | 技术选型 | | 数据采集层 | 从 OpenRouter 抓取模型元数据、定价 | Go + net/http + encoding/json | | 存储层 | 结构化数据持久化 | PostgreSQL与立交桥技术栈统一 | | 服务层 | 报告生成、API 服务、调度 | Go 1.22.2(采集器+报告生成 CLIAPI Server Phase 2 评估 | | 前端层 | 静态 Web 页面展示Explorer / 报告) | React/ViteTypeScript或纯静态 HTML |

1.3 技术架构决策(与立交桥技术栈统一)

核心约束与立交桥技术栈保持一致Phase 1 直接使用 PostgreSQL。

| 决策 | 选型 | 理由 | | 数据库 | PostgreSQL | 与立交桥统一;支持 JSONB/数组类型 | | 采集语言 | Go | 与立交桥统一;静态编译、单二进制部署、并发性能优 | | API 框架 | Phase 2 评估 | Phase 1 暂无 API 服务需求(直写 DB + 静态页面) | | 前端 | React/Vite 或纯静态 HTML | 视部署方式定Phase 1 最小可用 | | HTTP 客户端 | net/http(标准库) | Go 原生,无需第三方依赖 | | 数据库驱动 | github.com/lib/pq | 最成熟的 PostgreSQL Go 驱动 | | 调度 | 系统 cron + Go binary | go build 产出单二进制cron 直接调用 | | 日志/监控 | 标准库 log + 文件输出 | Go 标准库够用Phase 2 再接入结构化日志 | | 告警 | Phase 2 | 钉钉/飞书 Webhook 推送 |

Phase 1 单机部署拓扑

Phase 1 单机部署
├── PostgreSQL DB
├── fetch_openrouterGo binarycron 触发,直写 DB
├── generate_daily_reportGo binaryMarkdown 输出到 reports/daily/
└── frontend/静态页面CDN 或本地 nginx 托管)

二、技术选型详解

2.1 语言与运行时

| 组件 | 选型 | 版本 | 依据 |

| 主力语言 | Go | 1.22.2 | 与立交桥统一;静态编译单二进制部署;并发采集性能优;标准库完备 | | 前端 | Vanilla JS / React | — | Phase 1 最小可用React 用于 Explorer 复杂交互 |

2.2 框架与工具库

| 用途 | 库/工具 | 用途说明 |

| HTTP 请求 | net/http(标准库) | 数据采集主库,无需第三方依赖 | | JSON 解析 | encoding/json(标准库) | OpenRouter API 响应反序列化 | | 数据库 | database/sql + github.com/lib/pq | PostgreSQL 连接与查询 | | 报告生成 | Go text/template / html/template | Markdown/HTML 报告模板渲染 | | 图表 | ECharts(前端) | 浏览器端可视化(价格趋势/排行榜) | | 调度 | 系统 cron | Go binary 直接由 cron 调用 | | 日志 | log 标准库 | 简单文件输出Phase 2 评估 zap/slog | | 日期处理 | time 标准库 | Go 原生日期解析与格式化 | | 货币换算 | Phase 2 | USD/CNY/EUR 汇率获取(当前仅记录原始币种) | | 测试 | testing + net/http/httptest | Go 标准库测试框架 |

2.3 数据库

Phase 1PostgreSQL

  • 与立交桥技术栈统一
  • 利用 PostgreSQL JSONB 存储灵活字段(如 capabilities 数组)
  • Schema 设计直接以 PostgreSQL 语法编写(见 db/migrations/
  • Phase 2 评估是否需要数据库内任务队列(当前用 cron 直接触发 binary

2.4 为什么不用这些

| 未选方案 | 原因 |

| SQLite | 不符合与立交桥技术栈统一的要求 | | FastAPI | 技术栈已统一为 GoPython 框架需要运行时和依赖管理,部署复杂度高于 Go 单二进制 | | Scrapy | 重量级框架Phase 1 采集规模不需要分布式 | | Celery + RabbitMQ | 增加运维复杂度,当前用 cron + Go binary 替代 | | React/Vue | Phase 1 使用静态页面或最小 React 构建;部署复杂度可控 | | Deno/Bun | 生态不如 Go 成熟,与立交桥技术栈不一致 |


三、数据库设计DDL

以下 DDL 以 PostgreSQL 语法编写,与立交桥技术栈统一。

3.1 model_provider模型商

CREATE TABLE model_provider (
    id              BIGSERIAL PRIMARY KEY,
    name            TEXT NOT NULL UNIQUE,           -- "OpenAI", "百度", "DeepSeek"
    name_cn         TEXT,                            -- 中文名:"百度智能云"
    country         TEXT NOT NULL,                   -- "US" / "CN" / "EU"
    website         TEXT,                            -- 官网 URL
    founded_year    INTEGER,                         -- 成立年份
    description     TEXT,                            -- 简介
    logo_url        TEXT,                            -- 厂商 Logo
    status          TEXT NOT NULL DEFAULT 'active', -- active / deprecated
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by      TEXT DEFAULT 'system',            -- 创建者审计
    updated_by      TEXT DEFAULT 'system',            -- 更新者审计
    deleted_at      TIMESTAMP                         -- 软删除标记NULL=未删除)
);

CREATE INDEX idx_provider_country ON model_provider(country);
CREATE INDEX idx_provider_status ON model_provider(status);
CREATE INDEX idx_provider_deleted ON model_provider(deleted_at);

3.2 model模型

CREATE TABLE model (
    id                  BIGSERIAL PRIMARY KEY,
    provider_id        INTEGER NOT NULL,
    name                TEXT NOT NULL,               -- "GPT-4o", "ERNIE-4.0"
    version             TEXT,                         -- "2025-12", "V3.2"
    modality            TEXT NOT NULL,               -- text / vision / audio / video / code
    context_length      INTEGER NOT NULL DEFAULT 0,  -- 上下文窗口0=未知
    capabilities        TEXT,                        -- JSON数组: ["function_calling","vision"]
    release_date        DATE,                        -- 发布日期
    status              TEXT NOT NULL DEFAULT 'active', -- active / deprecated / discontinued
    parent_model_id     INTEGER,                     -- 父模型ID区分 Turbo/Lite 变体)
    elo_score           REAL,                        -- ELO 分数OpenRouter
    benchmark_scores    TEXT,                        -- JSON: {"mmlu": 88.5, "humaneval": 90.2}
    source_url          TEXT,                        -- 来源 URL
    data_confidence     TEXT DEFAULT 'official',    -- official / inferred / unverified / stale
    retrieved_at        TIMESTAMP,                   -- 数据抓取时间(数据新鲜度)
    batch_id            TEXT,                        -- 采集批次号(血缘追踪)
    collector_version   TEXT DEFAULT 'v1.0',        -- 采集器版本
    created_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by          TEXT DEFAULT 'system',
    updated_by          TEXT DEFAULT 'system',
    deleted_at          TIMESTAMP,
    FOREIGN KEY (provider_id) REFERENCES model_provider(id) ON DELETE CASCADE,
    UNIQUE(provider_id, name, version)
);

CREATE INDEX idx_model_provider ON model(provider_id);
CREATE INDEX idx_model_modality ON model(modality);
CREATE INDEX idx_model_status ON model(status);
CREATE INDEX idx_model_name ON model(name);
CREATE INDEX idx_model_deleted ON model(deleted_at);
CREATE INDEX idx_model_retrieved ON model(retrieved_at);
CREATE INDEX idx_model_confidence ON model(data_confidence);

3.3 operator运营商/云平台)

CREATE TABLE operator (
    id              BIGSERIAL PRIMARY KEY,
    name            TEXT NOT NULL UNIQUE,           -- "AWS Bedrock", "硅基流动"
    name_cn         TEXT,                            -- 中文名
    type            TEXT NOT NULL,                  -- cloud / reseller / official
    country         TEXT NOT NULL,                  -- 运营主体国籍
    website         TEXT,                           -- 控制台地址
    api_endpoint    TEXT,                           -- API 基础 URL
    auth_type       TEXT NOT NULL,                  -- api_key / oauth / sts
    is_cn_accessible BOOLEAN DEFAULT 1,              -- 国内是否可访问
    stability_grade TEXT DEFAULT 'B',               -- A/B/C/D 稳定性评级
    status          TEXT NOT NULL DEFAULT 'active', -- active / deprecated
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by      TEXT DEFAULT 'system',
    updated_by      TEXT DEFAULT 'system',
    deleted_at      TIMESTAMP
);

CREATE INDEX idx_operator_type ON operator(type);
CREATE INDEX idx_operator_country ON operator(country);
CREATE INDEX idx_operator_deleted ON operator(deleted_at);

3.4 region_pricing区域定价

CREATE TABLE region_pricing (
    id                      BIGSERIAL PRIMARY KEY,
    operator_id             INTEGER NOT NULL,
    model_id                INTEGER NOT NULL,
    region                  TEXT NOT NULL DEFAULT 'GLOBAL', -- CN / US / EU / GLOBAL
    currency                TEXT NOT NULL,                    -- CNY / USD / EUR
    input_price_per_mtok    REAL NOT NULL,                   -- 元/百万Token
    output_price_per_mtok   REAL NOT NULL,
    unit                    TEXT DEFAULT 'per_mtok',         -- per_mtok / per_1k / per_token
    free_tier_id            INTEGER,                          -- 关联 free_tier 表
    rate_limit              TEXT,                             -- JSON: {"rpm": 60, "tpm": 100000}
    free_limitations        TEXT,                             -- JSON数组: ["仅限国内IP","新用户专享"]
    last_updated            DATE NOT NULL,
    source_url              TEXT,
    data_confidence         TEXT DEFAULT 'official',         -- official / inferred / expired
    retrieved_at            TIMESTAMP,                        -- 数据抓取时间
    batch_id                TEXT,                             -- 采集批次号
    collector_version       TEXT DEFAULT 'v1.0',
    created_at              TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at              TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by              TEXT DEFAULT 'system',
    updated_by              TEXT DEFAULT 'system',
    deleted_at              TIMESTAMP,
    FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE CASCADE,
    FOREIGN KEY (model_id) REFERENCES model(id) ON DELETE CASCADE,
    UNIQUE(operator_id, model_id, region, currency)
);

CREATE INDEX idx_pricing_operator ON region_pricing(operator_id);
CREATE INDEX idx_pricing_model ON region_pricing(model_id);
CREATE INDEX idx_pricing_region ON region_pricing(region);
CREATE INDEX idx_pricing_currency ON region_pricing(currency);
CREATE INDEX idx_pricing_input_cost ON region_pricing(input_price_per_mtok);
CREATE INDEX idx_pricing_deleted ON region_pricing(deleted_at);
CREATE INDEX idx_pricing_retrieved ON region_pricing(retrieved_at);

3.5 pricing_history价格历史

CREATE TABLE pricing_history (
    id                      BIGSERIAL PRIMARY KEY,
    region_pricing_id       INTEGER NOT NULL,
    model_id                INTEGER NOT NULL,
    operator_id             INTEGER NOT NULL,
    region                  TEXT NOT NULL,
    currency                TEXT NOT NULL,
    old_input_price         REAL,
    new_input_price         REAL NOT NULL,
    old_output_price        REAL,
    new_output_price        REAL NOT NULL,
    change_pct              REAL,                            -- 变动百分比(自动计算)
    change_type             TEXT NOT NULL,                   -- increase / decrease / new_model / discontinued
    recorded_at             DATE NOT NULL,                   -- 记录日期
    source_url              TEXT,
    batch_id                TEXT,                             -- 采集批次号
    collector_version       TEXT DEFAULT 'v1.0',
    created_at              TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (region_pricing_id) REFERENCES region_pricing(id) ON DELETE CASCADE,
    FOREIGN KEY (model_id) REFERENCES model(id) ON DELETE CASCADE,
    FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE CASCADE
);

CREATE INDEX idx_history_model ON pricing_history(model_id);
CREATE INDEX idx_history_operator ON pricing_history(operator_id);
CREATE INDEX idx_history_recorded ON pricing_history(recorded_at);
CREATE INDEX idx_history_change_type ON pricing_history(change_type);

-- Phase 2: 按 recorded_at 分区,提升历史查询性能
-- CREATE TABLE pricing_history_partitioned (...) PARTITION BY RANGE (recorded_at);

3.6 free_tier免费政策

CREATE TABLE free_tier (
    id                  BIGSERIAL PRIMARY KEY,
    operator_id         INTEGER NOT NULL,
    model_id            INTEGER,                          -- NULL表示该平台全部免费额度
    free_model_name     TEXT,                              -- 免费模型名称(展示用)
    quota_type          TEXT NOT NULL,                     -- daily / monthly / one_time / unlimited
    quota_amount        REAL,                              -- 配额数量
    quota_unit          TEXT,                              -- requests / tokens / minutes
    tpm_limit           INTEGER,                           -- tokens per minute 限制
    rpm_limit           INTEGER,                           -- requests per minute 限制
    daily_req_limit     INTEGER,                           -- 每日请求上限
    monthly_req_limit   INTEGER,                           -- 每月请求上限
    token_limit_per_req INTEGER,                           -- 单次请求Token上限
    requires_credit_card BOOLEAN DEFAULT 0,                -- 是否需要绑定信用卡
    requires_verification BOOLEAN DEFAULT 0,              -- 是否需要实名认证
    region_restrictions TEXT,                              -- JSON: ["仅限部分地区"]
    applicable_scenarios TEXT,                             -- JSON: ["仅限新用户"]
    special_notes       TEXT,                              -- 特殊说明
    effective_from       DATE,
    effective_until     DATE,                              -- NULL表示长期有效
    last_updated        DATE,
    source_url          TEXT,
    created_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by          TEXT DEFAULT 'system',
    updated_by          TEXT DEFAULT 'system',
    deleted_at          TIMESTAMP,
    FOREIGN KEY (operator_id) REFERENCES operator(id) ON DELETE CASCADE,
    FOREIGN KEY (model_id) REFERENCES model(id) ON DELETE SET NULL
);

CREATE INDEX idx_free_operator ON free_tier(operator_id);
CREATE INDEX idx_free_model ON free_tier(model_id);
CREATE INDEX idx_free_quota_type ON free_tier(quota_type);
CREATE INDEX idx_free_deleted ON free_tier(deleted_at);

3.7 daily_report每日报告

CREATE TABLE daily_report (
    id              BIGSERIAL PRIMARY KEY,
    report_date     DATE NOT NULL UNIQUE,
    new_models      TEXT,                                 -- JSON数组新上线模型
    price_changes   TEXT,                                 -- JSON数组价格变动
    free_changes    TEXT,                                 -- JSON数组免费政策变更
    top_recommendations TEXT,                            -- JSON对象场景推荐
    cost_alerts     TEXT,                                 -- JSON数组成本告警
    html_content    TEXT,                                 -- 完整HTML报告内容
    summary_md      TEXT,                                 -- Markdown摘要
    status          TEXT NOT NULL DEFAULT 'generated',    -- generated / failed / partial
    generated_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    error_message   TEXT,
    created_by      TEXT DEFAULT 'system',
    updated_by      TEXT DEFAULT 'system'
);

CREATE INDEX idx_report_date ON daily_report(report_date);
CREATE INDEX idx_report_status ON daily_report(status);

3.8 user_subscription用户订阅

CREATE TABLE user_subscription (
    id                  BIGSERIAL PRIMARY KEY,
    user_id             TEXT NOT NULL,                    -- 统一用户ID
    email               TEXT,
    phone               TEXT,
    subscription_tier   TEXT NOT NULL DEFAULT 'free',     -- free / pro / team / enterprise
    subscription_start  DATE,
    subscription_end    DATE,
    notify_channels     TEXT,                             -- JSON: ["feishu","email","dingtalk"]
    feishu_webhook      TEXT,
    dingtalk_webhook    TEXT,
    email_webhook       TEXT,
    model_watchlist     TEXT,                             -- JSON数组关注模型
    operator_watchlist  TEXT,                             -- JSON数组关注平台
    price_alert_threshold REAL DEFAULT 10.0,             -- 告警阈值(%
    monthly_token_limit INTEGER,                          -- 月度Token限制
    monthly_token_used  INTEGER DEFAULT 0,
    stripe_customer_id  TEXT,
    created_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at          TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by          TEXT DEFAULT 'system',
    updated_by          TEXT DEFAULT 'system',
    deleted_at          TIMESTAMP,
    UNIQUE(email)
);

CREATE INDEX idx_sub_user ON user_subscription(user_id);
CREATE INDEX idx_sub_tier ON user_subscription(subscription_tier);
CREATE INDEX idx_sub_deleted ON user_subscription(deleted_at);

四、API 设计

4.1 内部采集 APICollector → Server

POST /api/v1/collect/push

采集器推送采集结果Phase 2 分布式采集节点使用)

Request:

{
  "batch": [
    {
      "provider_name": "OpenAI",
      "model_name": "GPT-4o",
      "version": "2025-01",
      "operator_name": "OpenRouter",
      "region": "GLOBAL",
      "currency": "USD",
      "input_price": 2.50,
      "output_price": 10.0,
      "context_length": 128000,
      "capabilities": ["vision", "function_calling", "json_mode"],
      "free_tier": null,
      "source_url": "https://openrouter.ai/api/v1/models"
    }
  ],
  "collected_at": "2026-05-04T08:00:00+08:00"
}

Response:

{
  "status": "ok",
  "inserted": 365,
  "updated": 12,
  "errors": 0
}

4.1.5 API 安全与生产规范

认证与鉴权

场景 机制 说明
内部采集器 → DB 无 API 认证 采集器直连 PostgreSQL通过 DB 用户权限隔离
Phase 2 对外 API API KeyX-API-Key Header 按订阅等级分配不同 KeyDB 存储 bcrypt 哈希
Admin 运维接口 JWT + RBAC 仅内部使用Token 有效期 1hRefresh Token 7d

限流策略

订阅等级 QPS 日调用上限 并发连接
Free 2 100 1
Pro 10 5,000 3
Team 50 50,000 10
Enterprise 协商 无限 协商
  • 限流实现Token Bucket内存+ Redis分布式Phase 2
  • 超限响应:429 Too Many RequestsRetry-After 头部

错误码规范

HTTP Status 业务码 含义 示例
200 成功
400 BAD_REQUEST 参数非法 page_size > 100
401 UNAUTHORIZED 认证失败 API Key 无效/过期
403 FORBIDDEN 权限不足 Free 用户访问 Team 功能
404 NOT_FOUND 资源不存在 模型 ID 不存在
429 RATE_LIMITED 限流触发 超出 QPS/日配额
500 INTERNAL_ERROR 服务端错误 DB 连接失败
503 SERVICE_UNAVAILABLE 维护模式 系统升级中

错误响应格式:

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Quota exceeded: 100/100 daily calls used",
    "retry_after": 3600,
    "request_id": "req_abc123"
  }
}

CORS 策略

Access-Control-Allow-Origin: https://llm-hub.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-API-Key
Access-Control-Max-Age: 86400
  • 生产环境:只允许特定域名
  • 开发环境:*(仅本地)

请求/响应约束

约束 说明
请求体大小 ≤ 1MB 防止超大 JSON 攻击
响应体大小 ≤ 5MB 分页控制
超时 30s API 请求硬超时
参数校验 严格 未知参数返回 400

4.2 对外 REST API

GET /api/v1/models

查询模型列表

Query Parameters: | 参数 | 类型 | 默认值 | 说明 |

| provider | string | — | 模型商名称过滤 | | modality | string | — | text/vision/audio/video/code | | min_context | int | — | 最小上下文长度 | | max_input_price | float | — | 最大输入价格(/MTok | | has_free | bool | false | 仅显示有免费额的模型 | | search | string | — | 关键词搜索(模型名/capabilities | | sort | string | input_price | 排序字段 | | order | string | asc | asc/desc | | page | int | 1 | 页码 | | page_size | int | 20 | 每页数量max 100 |

Response:

{
  "total": 523,
  "page": 1,
  "page_size": 20,
  "models": [
    {
      "id": 42,
      "name": "DeepSeek V4-Flash",
      "provider": "DeepSeek",
      "provider_cn": "深度求索",
      "modality": "text",
      "context_length": 1048576,
      "capabilities": ["function_calling", "json_mode"],
      "status": "active",
      "lowest_price": {
        "operator": "硅基流动",
        "currency": "CNY",
        "input": 0.14,
        "output": 0.028,
        "region": "CN"
      }
    }
  ]
}

GET /api/v1/models/{id}

查询单个模型详情

Response:

{
  "id": 42,
  "name": "DeepSeek V4-Flash",
  "provider": {
    "id": 5,
    "name": "DeepSeek",
    "country": "CN"
  },
  "version": "V4-Flash",
  "modality": "text",
  "context_length": 1048576,
  "capabilities": ["function_calling", "json_mode"],
  "release_date": "2026-04-15",
  "status": "active",
  "elo_score": 1382.5,
  "pricing": [
    {
      "operator": "硅基流动",
      "region": "CN",
      "currency": "CNY",
      "input": 0.14,
      "output": 0.028,
      "source_url": "https://siliconflow.cn"
    },
    {
      "operator": "OpenRouter",
      "region": "GLOBAL",
      "currency": "USD",
      "input": 0.02,
      "output": 0.004
    }
  ],
  "free_tier": {
    "quota_type": "monthly",
    "quota_amount": 5000000,
    "quota_unit": "tokens",
    "requires_credit_card": false
  }
}

GET /api/v1/cost

成本计算器

Query Parameters: | 参数 | 类型 | 必填 | 说明 |

| input_tokens | int | 是 | 输入 Token 数 | | output_tokens | int | 否 | 输出 Token 数(默认=input_tokens×0.3 | | modality | string | 否 | 模态过滤 | | region | string | 否 | 区域CN/US/GLOBAL | | currency | string | CNY | 显示货币 | | top_n | int | 10 | 返回前N个最低价 |

Response:

{
  "input_tokens": 1000000,
  "output_tokens": 300000,
  "currency": "CNY",
  "results": [
    {
      "rank": 1,
      "model": "DeepSeek V4-Flash",
      "provider": "DeepSeek",
      "operator": "硅基流动",
      "input_cost": 0.14,
      "output_cost": 0.0084,
      "total_cost": 0.1484,
      "total_cost_usd": 0.020
    },
    {
      "rank": 2,
      "model": "Kimi K2.5",
      "provider": "Moonshot",
      "operator": "硅基流动",
      "input_cost": 0.23,
      "output_cost": 0.021,
      "total_cost": 0.251,
      "total_cost_usd": 0.034
    }
  ]
}

GET /api/v1/recommend

模型推荐

Query Parameters: | 参数 | 类型 | 必填 | 说明 |

| use_case | string | 是 | 场景coding/writing/reasoning/free/vision | | min_context | int | — | 最小上下文需求 | | budget | float | — | 预算上限(/MTok input | | region | string | CN | 区域偏好 | | limit | int | 5 | 返回数量 |

Response:

{
  "use_case": "coding",
  "recommendations": [
    {
      "rank": 1,
      "model": "Kimi K2.6",
      "provider": "Moonshot",
      "reason": "SWE-Bench Pro 超越 GPT-5.4,编码能力最强",
      "input_price": 0.95,
      "currency": "CNY",
      "free_option": null
    },
    {
      "rank": 2,
      "model": "GLM-5.1",
      "provider": "智谱",
      "reason": "编码能力接近 Opus 4.6,性价比高",
      "input_price": 1.40,
      "currency": "CNY",
      "free_option": null
    }
  ]
}

GET /api/v1/reports

每日报告列表

Query Parameters: | 参数 | 类型 | 默认值 | 说明 |

| from | date | 30天前 | 开始日期 | | to | date | 今天 | 结束日期 | | page | int | 1 | 页码 |

Response:

{
  "total": 30,
  "reports": [
    {
      "id": 30,
      "report_date": "2026-05-04",
      "status": "generated",
      "summary": "新上线3个模型价格变动2项免费政策更新1项",
      "generated_at": "2026-05-04T08:00:45+08:00"
    }
  ]
}

GET /api/v1/reports/{date}

获取指定日期报告内容

Response:

{
  "id": 30,
  "report_date": "2026-05-04",
  "html_content": "<html>...</html>",
  "new_models": [
    {"name": "xAI Grok 4.1 Fast", "provider": "xAI", "input_price": 0.20, "currency": "USD"}
  ],
  "price_changes": [
    {
      "model": "Claude Opus 4.6",
      "operator": "Anthropic",
      "old_price": 15.0,
      "new_price": 5.0,
      "change_pct": -66.7,
      "currency": "USD"
    }
  ],
  "free_changes": [
    {
      "model": "Gemini 2.5 Pro",
      "operator": "Google",
      "change": "免费层下线,需付费使用"
    }
  ],
  "top_recommendations": {
    "coding": {"model": "Kimi K2.6", "provider": "Moonshot"},
    "writing": {"model": "GLM-5.1", "provider": "智谱"},
    "free": {"model": "DeepSeek R1", "provider": "DeepSeek"},
    "cheapest": {"model": "Step 3.5 Flash", "provider": "字节"}
  }
}

GET /api/v1/health

健康检查

Response:

{
  "status": "ok",
  "version": "1.0.0",
  "db_record_count": {
    "models": 523,
    "providers": 22,
    "operators": 31,
    "pricing_records": 1847
  },
  "last_collect_time": "2026-05-04T08:00:12+08:00",
  "last_report_time": "2026-05-04T08:00:45+08:00"
}

五、数据采集 Pipeline

5.1 OpenRouter 采集流程

┌─────────────────┐
│  每日 08:00     │
│  cron 触发      │
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│  GET https://openrouter.ai/api/v1/models        │
│  Headers: Authorization: Bearer <OPENROUTER_KEY>│
└────────┬────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│  解析响应 JSON                                   │
│  字段映射:                                       │
│    id → model.name (如 "anthropic/claude-3.5-sonnet")│
│    name → display_name                         │
│    pricing.input * 1e6 → input_price_per_mtok  │
│    pricing.output * 1e6 → output_price_per_mtok│
│    context_length → context_length             │
│    supported_parameters → capabilities          │
│    opensource → modality (text/vision/etc)     │
└────────┬────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│  识别 provider_name (从 id 前缀提取)             │
│  示例: "anthropic/claude-3.5-sonnet" →         │
│        provider="Anthropic", model="Claude 3.5 Sonnet"│
└────────┬────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│  Upsert:                                        │
│  INSERT OR REPLACE INTO model_provider (...)   │
│  INSERT OR REPLACE INTO model (...)             │
│  INSERT OR REPLACE INTO region_pricing (...)    │
└────────┬────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│  检测价格变动:                                   │
│  SELECT old_price FROM pricing_history          │
│  WHERE model_id = x AND operator_id = y         │
│  IF new_price != old_price:                     │
│    INSERT INTO pricing_history (...)            │
│    IF abs(change_pct) > 5%: 标记为高亮变动      │
└─────────────────────────────────────────────────┘

5.2 国内厂商采集流程 — Phase 2

每个国内厂商独立采集器(collectors/ 目录统一接口输出Go

// collectors/collector.go
package collectors

import "time"

// Collector 所有采集器必须实现的接口
type Collector interface {
    Name() string
    Collect() ([]CollectedRecord, error)
    Schedule() string        // cron 表达式,如 "0 8 * * *"
    Timeout() time.Duration
    RetryCount() int
}

采集器清单Phase 1

统一字段映射

每个采集器输出标准化 CollectedRecordGo struct

type CollectedRecord struct {
    ProviderName       string    `json:"provider_name"`
    ProviderNameCN     string    `json:"provider_name_cn"`
    ModelName          string    `json:"model_name"`
    ModelVersion       string    `json:"model_version"`
    Modality           string    `json:"modality"`
    ContextLength      int       `json:"context_length"`
    Capabilities       []string  `json:"capabilities"`
    OperatorName       string    `json:"operator_name"`
    OperatorType       string    `json:"operator_type"`
    Region             string    `json:"region"`
    Currency           string    `json:"currency"`
    InputPricePerMTok  float64   `json:"input_price_per_mtok"`
    OutputPricePerMTok float64   `json:"output_price_per_mtok"`
    FreeTier           *FreeTierRecord `json:"free_tier,omitempty"`
    SourceURL          string    `json:"source_url"`
    CollectedAt        time.Time `json:"collected_at"`
}

统一 ProviderMapperGo map

var ProviderNameMap = map[string]struct {
    Provider string
    Model    string
    Version  string
}{
    // DeepSeek
    "deepseek-ai/DeepSeek-V3": {Provider: "DeepSeek", Model: "V3.2", Version: "2026-03"},
    "deepseek-ai/DeepSeek-V4": {Provider: "DeepSeek", Model: "V4", Version: "2026-04"},
    "deepseek-ai/DeepSeek-R1": {Provider: "DeepSeek", Model: "R1", Version: "2026-01"},
    // 阿里
    "qwen/Qwen3-VL-32B": {Provider: "阿里云", Model: "Qwen3-VL-32B", Version: "2026-03"},
    "qwen/Qwen3-VL-8B":  {Provider: "阿里云", Model: "Qwen3-VL-8B", Version: "2026-03"},
    // Moonshot
    "moonshotai/Kimi-K2.6": {Provider: "Moonshot", Model: "K2.6", Version: "2026-04"},
    "moonshotai/Kimi-K2.5": {Provider: "Moonshot", Model: "K2.5", Version: "2026-03"},
    // 智谱
    "zhipuai/GLM-5.1": {Provider: "智谱", Model: "GLM-5.1", Version: "2026-03"},
    "zhipuai/GLM-4.7": {Provider: "智谱", Model: "GLM-4.7", Version: "2025-12"},
    // ... 其他厂商
}

5.3 每日调度设计

调度策略:系统 cron 统一调度,无外部消息队列依赖。

# /etc/crontab

# 每日 08:00 触发全量采集 + 报告生成
0 8 * * * root /opt/llm-hub/scripts/run_daily.sh >> /var/log/llm-hub/daily.log 2>&1

# 每日 09:00 触发数据备份
0 9 * * * root /opt/llm-hub/scripts/backup.sh >> /var/log/llm-hub/backup.log 2>&1
#!/bin/bash
# run_daily.sh

set -e

LOG_FILE="/var/log/llm-hub/daily.log"
HUB_DIR="/opt/llm-hub"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

log "开始每日采集任务"

# 1. 采集 OpenRouter海外模型优先级最高
cd $HUB_DIR
./scripts/fetch_openrouter >> $LOG_FILE 2>&1
log "OpenRouter 采集完成"

# 2. Phase 2 才并行采集国内厂商DeepSeek/阿里/Kimi/智谱等)

# 3. 生成每日报告
./scripts/generate_daily_report >> $LOG_FILE 2>&1
log "日报生成完成"

# 4. Phase 2 才检测价格变动并告警
log "每日任务完成"

5.4 失败重试 + 告警机制Go 实现)

// pkg/retry/retry.go
package retry

import (
	"fmt"
	"log"
	"time"
)

// Do 指数退避重试
func Do(maxAttempts int, delay time.Duration, backoff float64, fn func() error) error {
	var err error
	for attempt := 1; attempt <= maxAttempts; attempt++ {
		if err = fn(); err == nil {
			return nil
		}
		if attempt == maxAttempts {
			return fmt.Errorf("all %d attempts failed: %w", maxAttempts, err)
		}
		wait := time.Duration(float64(delay) * pow(backoff, float64(attempt-1)))
		log.Printf("Attempt %d/%d failed: %v. Retrying in %v...", attempt, maxAttempts, err, wait)
		time.Sleep(wait)
	}
	return err
}

func pow(x, y float64) float64 {
	result := 1.0
	for i := 0; i < int(y); i++ {
		result *= x
	}
	return result
}

采集器调用示例Go

func collectWithRetry(collector collectors.Collector) error {
	return retry.Do(3, 10*time.Second, 2.0, func() error {
		records, err := collector.Collect()
		if err != nil {
			return err
		}
		return saveToDB(records)
	})
}

告警触发逻辑Go

func checkAndAlertPriceChange(modelID, operatorID int, newPrice float64) {
	oldPrice := getLastPrice(modelID, operatorID)
	if oldPrice == 0 {
		return // 首次录入,不告警
	}

	changePct := (newPrice - oldPrice) / oldPrice * 100

	if math.Abs(changePct) > 10 {
		alertMsg := fmt.Sprintf(
			"⚠️ 价格变动告警\n模型: %s\n平台: %s\n原价: %.4f\n新价: %.4f\n变动: %+.1f%%",
			getModelName(modelID), getOperatorName(operatorID), oldPrice, newPrice, changePct,
		)
		sendFeishuAlert(alertMsg)
		log.Printf("[ALERT] %s", alertMsg)
	}
}

告警规则:

| 条件 | 动作 |

| 单个采集器失败 | 记录日志,保留旧数据,发送低优先级告警 | | 连续 3 天同一采集器失败 | 发送高优先级告警(钉钉/飞书) | | 价格变动 > 10% | 立即触发告警 | | 价格变动 > 20% | 立即触发告警 + 暂停该平台数据(人工确认) | | 报告生成失败 | 发送告警,保留前一天报告 | | 数据库写入失败 | 立即告警,回滚事务 |


六、前端架构

6.1 技术栈

| 组件 | 选型 | 理由 |

| 页面框架 | React 18 + Vite + TypeScript | 组件化、类型安全、构建优化 | | 图表库 | ECharts 5 + echarts-for-react | 功能全面、中文支持好 | | 图标 | Lucide React | 现代化图标、Tree-shaking | | 搜索 | 前端 Fuse.js | 轻量模糊搜索、< 100KB | | 布局 | Tailwind CSS | 原子化 CSS、响应式、定制灵活 | | 构建 | Vite | 快速 HMR、Rollup 打包、生产优化 |

6.2 页面清单

| 页面 | 路径 | 功能说明 |

| 首页 / 报告列表 | / | 展示最新每日报告入口,显示近期报告摘要 | | 报告详情 | /reports/{date}.html | 单日报告完整内容(新模型/价格变动/推荐) | | 模型浏览器 | /explorer.html | 组合筛选 + 卡片/表格视图 + 搜索 | | 模型详情 | /model/{id}.html | 模型完整信息 + 全平台定价对比 | Phase 2|Phase 2 Phase 2Phase 2Phase 2Phase 2Phase 2成Phase 2本Phase 2计Phase 2算Phase 2器Phase 2Phase 2Phase 2Phase 2Phase 2 Phase 2|Phase 2 Phase 2Phase 2/Phase 2cPhase 2aPhase 2lPhase 2cPhase 2uPhase 2lPhase 2aPhase 2tPhase 2oPhase 2rPhase 2.Phase 2hPhase 2tPhase 2mPhase 2lPhase 2Phase 2 Phase 2|Phase 2 Phase 2TPhase 2oPhase 2kPhase 2ePhase 2nPhase 2 Phase 2用Phase 2量Phase 2 Phase 2→Phase 2 Phase 2多Phase 2平Phase 2台Phase 2成Phase 2本Phase 2对Phase 2比Phase 2排Phase 2行Phase 2 Phase 2|Phase 2 Phase 2Phase 2|Phase 2 Phase 2Phase 2Phase 2Phase 2Phase 2趋Phase 2势Phase 2图Phase 2Phase 2Phase 2Phase 2Phase 2 Phase 2|Phase 2 Phase 2Phase 2/Phase 2tPhase 2rPhase 2ePhase 2nPhase 2dPhase 2sPhase 2.Phase 2hPhase 2tPhase 2mPhase 2lPhase 2Phase 2 Phase 2|Phase 2 Phase 2价Phase 2格Phase 2/Phase 2模Phase 2型Phase 2能Phase 2力Phase 2历Phase 2史Phase 2趋Phase 2势Phase 2Phase 2EPhase 2CPhase 2hPhase 2aPhase 2rPhase 2tPhase 2sPhase 2Phase 2 Phase 2|Phase 2 Phase 2| 关于我们 | /about.html | 项目介绍、数据来源说明 |

6.3 与后端的数据交互

模式:纯前端 SPASingle Page Application通过 Fetch API 调用后端 REST API。

前端静态文件Phase 2才 Nginx 托管)
    │
    ├── GET /api/v1/models          → Go API 返回 JSON
    ├── GET /api/v1/models/{id}    → 模型详情 JSON
    ├── GET /api/v1/cost           → 成本计算 JSON
    ├── GET /api/v1/recommend      → 推荐结果 JSON
    └── GET /api/v1/reports/{date} → 报告 JSON

前端数据层dataService.js

// 统一 API 调用封装
const API_BASE = '/api/v1';

async function apiGet(endpoint, params = {}) {
    const url = new URL(`${API_BASE}${endpoint}`, window.location.origin);
    Object.entries(params).forEach(([k, v]) => v != null && url.searchParams.set(k, v));
    const resp = await fetch(url);
    if (!resp.ok) throw new Error(`API error: ${resp.status}`);
    return resp.json();
}

// 主要接口封装
const api = {
    models: {
        list: (params) => apiGet('/models', params),
        detail: (id) => apiGet(`/models/${id}`)
    },
    cost: {
        calculate: (params) => apiGet('/cost', params)
    },
    recommend: (params) => apiGet('/recommend', params),
    reports: {
        list: (params) => apiGet('/reports', params),
        get: (date) => apiGet(`/reports/${date}`)
    }
};

6.4 模型浏览器页面结构

<!-- explorer.html -->

<!-- 筛选栏 -->
<div class="row mb-3">
    <div class="col-md-2">
        <select id="filter-provider" class="form-select">
            <option value="">全部厂商</option>
            <option value="DeepSeek">DeepSeek</option>
            <option value="阿里云">阿里云</option>
            <!-- ... -->
        </select>
    </div>
    <div class="col-md-2">
        <select id="filter-modality" class="form-select">
            <option value="">全部模态</option>
            <option value="text">文字</option>
            <option value="vision">视觉</option>
            <option value="code">代码</option>
        </select>
    </div>
    <div class="col-md-2">
        <input type="number" id="filter-max-price" class="form-control"
               placeholder="最大输入价(¥/MT)">
    </div>
    <div class="col-md-3">
        <input type="text" id="search-keyword" class="form-control"
               placeholder="搜索模型名称...">
    </div>
    <div class="col-md-3">
        <div class="btn-group" role="group">
            <button class="btn btn-outline-primary active" data-view="card">卡片</button>
            <button class="btn btn-outline-primary" data-view="table">表格</button>
        </div>
    </div>
</div>

<!-- 结果区域 -->
<div id="results" class="row">
    <!-- 动态渲染卡片或表格 -->
</div>

<!-- 分页 -->
<nav><ul class="pagination" id="pagination"></ul></nav>

十二、快速部署参考(历史版本)

本节保留早期简洁部署方案,生产环境请参考「八、部署与运维架构」。

7.1 Docker 配置

# docker-compose.yml
version: '3.8'

services:
  # --- Phase 1 核心服务 ---

  collector:
    build:
      context: .
      dockerfile: Dockerfile.collector
    volumes:
      - ./data:/opt/llm-hub/data        # PostgreSQL 数据持久化
      - ./logs:/var/log/llm-hub        # 日志持久化
      - ./reports:/opt/llm-hub/reports # 报告输出
    env_file:
      - .env
    restart: unless-stopped
    networks:
      - llm-hub-net

  api:
    build:
      context: .
      dockerfile: Dockerfile.api
    ports:
      - "5000:5000"
    volumes:
      - ./data:/opt/llm-hub/data
      - ./reports:/opt/llm-hub/reports
    env_file:
      - .env
    restart: unless-stopped
    depends_on:
      - collector
    networks:
      - llm-hub-net

  # --- Phase 2 才引入 Nginx内网访问 + 静态文件服务)---

networks:
  llm-hub-net:
    driver: bridge

7.2 内网部署要求

部署前提

  • 一台可访问外网的服务器(境外更好,便于访问 OpenRouter
  • 域名(可选,用于 HTTPS + 钉钉/飞书 Webhook 回调)
  • Docker + Docker Compose

网络访问需求

| 目的地 | 用途 | 协议 |

| openrouter.ai | 采集海外模型数据 | HTTPS | | api.deepseek.com | 采集 DeepSeek 定价 | HTTPS | | dashscope.aliyuncs.com | 采集阿里云定价 | HTTPS | | api.moonshot.cn | 采集 Kimi 定价 | HTTPS | | open.bigmodel.cn | 采集智谱定价 | HTTPS | | api.siliconflow.cn | 采集硅基流动定价 | HTTPS | | oapi.dingtalk.com | Phase 2 钉钉告警 | HTTPS | | open.feishu.cn | Phase 2 飞书告警 | HTTPS | | 无需访问 | 国内云厂商定价页(如阿里云控制台) | — |

7.3 环境变量清单

# .env 文件Phase 1 最小配置)

# === 数据库 ===
DATABASE_URL=postgresql://user:pass@localhost:5432/llmhub

# === OpenRouter ===
OPENROUTER_API_KEY=sk-or-v1-xxxxx

# === 国内厂商 API Keys ===
DEEPSEEK_API_KEY=sk-xxxxx
DASHSCOPE_API_KEY=sk-xxxxx
MOONSHOT_API_KEY=sk-xxxxx
ZHIPU_API_KEY=xxxxx
MINIMAX_API_KEY=xxxxx
VOLCENGINE_API_KEY=xxxxx
VOLCENGINE_SECRET_KEY=xxxxx
TENCENT_SECRET_ID=xxxxx
TENCENT_SECRET_KEY=xxxxx
BAIDU_QIANFAN_API_KEY=xxxxx
BAIDU_QIANFAN_SECRET_KEY=xxxxx
SILICONFLOW_API_KEY=sk-xxxxx

# === 告警配置Phase 2 才启用)===
# DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxxxx
# FEISHU_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxxxx
ALERT_THRESHOLD_PCT=10
ALERT_THRESHOLD_CRITICAL_PCT=20

# === 邮件配置(可选)===
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_PASS=xxxxx

# === 备份配置 ===
BACKUP_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
BACKUP_OSS_BUCKET=llm-hub-backup
BACKUP_OSS_KEY=xxxxx
BACKUP_OSS_SECRET=xxxxx

# === 系统 ===
LOG_LEVEL=INFO
TZ=Asia/Shanghai

7.4 Nginx 配置

# nginx.conf
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    keepalive_timeout 65;

    # --- 静态文件服务(前端)---
    server {
        listen 80;
        server_name _;

        root /usr/share/nginx/html;
        index index.html;

        # 前端静态页面
        location / {
            try_files $uri $uri/ /index.html;
        }

        # 每日报告 HTML
        location /reports/ {
            alias /usr/share/nginx/html/reports/;
            expires 7d;
            add_header Cache-Control "public, immutable";
        }

        # --- API 反向代理 ---
        location /api/ {
            proxy_pass http://api:5000/api/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_read_timeout 60s;
        }

        # 健康检查(无需认证)
        location /health {
            proxy_pass http://api:5000/api/v1/health;
            proxy_set_header Host $host;
        }
    }
}

十三、Phase 1 技术路线3个月

8.1 Sprint 划分

| Sprint | 周期 | 目标 | 交付物 |

| Sprint 0 | Week 1 | 技术方案确认 + 环境搭建 | TECHNICAL_DESIGN.md 终稿;开发环境就绪 | | Sprint 1 | Week 2-3 | OpenRouter 采集器 + 数据库 Schema | 371 海外模型入库;数据库 DDL 可执行 | | Sprint 2 | Week 4-5 | PostgreSQL migration + 日报生成器 | 三张表落地Markdown 报告输出到 reports/daily/ | | Sprint 2 | Week 4-5 | 每日报告生成 + Explorer 页面 | Markdown 报告生成Explorer 页面上线Markdown 报告可输出到 reports/daily/ | | Sprint 4 | Week 8-9 | 模型浏览器 + 搜索筛选 | /explorer.html 上线;卡片/表格视图 | | Sprint 5 | Week 10-11 | Explorer 页面完善 + Dashboard 占位图 | 表格/筛选排序;价格趋势占位图 | | Sprint 6 | Week 12 | 收尾 + 部署 + 验证脚本 | Docker Compose 部署文档;验证脚本;备份策略 |

8.2 Sprint 1 详细任务OpenRouter 采集器)

Sprint 1 目标:从 OpenRouter API 采集 371 模型,建立基础数据库

任务分解:
├── T1.1 数据库 Schema 部署
│   ├── [ ] 创建所有 DDL 表model_provider/model/operator/region_pricing/...
│   ├── [ ] 编写 PostgreSQL Schema 部署脚本deploy.sh
│   └── [ ] 验证:查询所有表,返回空表,数量正确
│
├── T1.2 OpenRouter 采集器实现
│   ├── [ ] 实现 `scripts/fetch_openrouter.go`Go
│   │   ├── 调用 GET https://openrouter.ai/api/v1/models
│   │   ├── 解析 id/name/pricing/context_length/capabilities
│   │   ├── 从 id 前缀提取 provideranthropic/claude-3.5-sonnet → Anthropic
│   │   ├── 处理免费模型id 包含 :free 后缀)
│   │   └── 错误处理401/429/500
│   ├── [ ] 实现 base collector 抽象类
│   ├── [ ] 实现数据清洗逻辑(去除异常价格、统一单位)
│   └── [ ] 验证371 模型全部入库,无重复,数据正确
│
├── T1.3 数据映射 + Provider 标准化
│   ├── [ ] 建立 PROVIDER_NAME_MAPOpenRouter id → 标准厂商名)
│   ├── [ ] 验证:所有 provider 名称统一(无别名)
│   └── [ ] 补充 provider logo_url / description
│
├── T1.4 初始数据导入
│   ├── [ ] 运行 OpenRouter 采集器,导入 371 模型
│   ├── [ ] 质量检查:随机抽 10 条数据,验证价格/上下文长度
│   └── [ ] 导出数据字典文档
│
└── T1.5 采集脚本 + cron 配置
    ├── [ ] 编写 scripts/run_openrouter_collect.sh
    ├── [ ] 配置 crontab08:00 每日执行)
    ├── [ ] 编写失败重试逻辑
    └── [ ] 验证:手动运行脚本成功,数据入库

8.3 Phase 1 关键技术决策记录

| 决策 | 选型 | 记录时间 | 理由 |

| Phase 1 数据库用 PostgreSQL | 确认 | Sprint 0 | 与立交桥技术栈统一;支持 JSONB/数组类型;数据库内队列 | | 数据采集用 Go net/http | 确认 | Sprint 0 | 标准库,无需第三方依赖,静态编译 | | 报告生成用 Go html/template | 确认 | Sprint 0 | 标准库模板,无需第三方依赖 | | 告警用 Webhook 直推 | 确认 | Sprint 0 | 无需消息队列,降低复杂度 | | OpenRouter ELO 数据暂不采集 | ⚠️ 延期 | Sprint 1 | ELO API 可能收费Phase 1 跳过 | | 国内厂商优先级DeepSeek > 阿里 > Kimi > 智谱 > MiniMax > 火山 > 腾讯 > 百度 | 确认 | Sprint 2 | 按市场热度排序 |

8.4 质量检查清单Phase 1 上线前)

功能验证

  • OpenRouter 371 模型全部入库,覆盖率 100%

(Phase 2 才采集国内厂商)

  • 每日 08:00 cron 触发采集,报告自动生成
  • 报告内容包含:新模型、价格变动(>5% 高亮)、场景推荐
  • /explorer.html 搜索响应 < 500ms

(Phase 2 才实现告警推送)

数据质量验证

  • 每条数据有 source_url 来源标注
  • 置信度分级标注official / inferred / expired
  • 价格单位统一为 ¥/MTok 或 $/MTok
  • 同模型多源价格差异 > 20% 时标注"待核实"
  • 采集失败写入日志,保留旧数据

部署验证

  • docker-compose up 可正常启动所有服务
  • PostgreSQL 数据库持久化到 data/ 目录
  • 报告 HTML 生成到 reports/ 目录

Phase 2 才引入 Nginx

  • API /api/v1/health 返回 200
  • 备份脚本每日推送至 OSS 成功

性能验证

  • 371 模型采集完成 < 5 分钟
  • 报告生成 < 30 秒
  • API 查询响应 < 500ms/models, 20 条)
  • 并发 10 个采集器同时运行,内存 < 2GB

附录:目录结构

llm-intelligence/
├── TECHNICAL_DESIGN.md          # 本文档
├── PRD.md                        # 产品需求文档
├── FEATURE_LIST.md               # 功能清单
├── BUSINESS_MODEL.md             # 商业模式
├── MARKET_ANALYSIS.md            # 市场调研
│
├── Dockerfile.collector          # 采集器镜像
├── Dockerfile.api                # API 服务镜像
├── docker-compose.yml            # 容器编排
├── .env.example                  # 环境变量模板
├── nginx.conf                     # Nginx 配置
│
├── collectors/                    # 数据采集器
│   ├── collector.go             # 采集器接口定义
│   ├── openrouter.go            # OpenRouter 采集器Go
│   └── [deepseek.go]            # Phase 2: DeepSeek 采集器
│
├── services/                     # 服务层
│   ├── db.go                    # 数据库连接池封装Go database/sql + pq
│   ├── queries.go               # 预编译 SQL 查询
│   └── models.go                # Go struct 模型定义(对应 DB schema
│
├── api/                          # REST API
│   ├── main.go                  # HTTP 服务入口Go net/http 或 Phase 2 框架)
│   ├── routes.go                # 路由注册
│   └── middleware.go              # 认证/限流/日志中间件
│
├── static/                       # 前端静态文件
│   ├── index.html                # 首页/报告列表
│   ├── explorer.html              # 模型浏览器
│   ├── calculator.html            # 成本计算器
│   ├── trends.html                # 趋势分析
│   ├── css/
│   │   └── style.css
│   └── js/
│       ├── dataService.js         # API 调用封装
│       ├── explorer.js            # 模型浏览器逻辑
│       ├── calculator.js           # 计算器逻辑
│       └── charts.js               # ECharts 封装
│
├── templates/                     # Go html/template 模板
│   └── report.html               # 每日报告 HTML 模板
│
├── reports/                       # 生成的报告 HTML 输出
│   └── 2026-05-04.html
│
├── scripts/                       # 运维脚本
│   ├── run_daily.sh               # 每日采集 + 报告脚本
│   ├── backup.sh                  # 数据库备份脚本
│   ├── migrate.sh                 # PostgreSQL Schema 部署脚本
│   └── init_db.sql                # 数据库初始化(权限/扩展)
│
├── internal/                      # Go 内部包
│   ├── collectors/                # 采集器接口 + 实现
│   │   ├── collector.go           # Collector 接口定义
│   │   └── openrouter.go          # OpenRouter 采集器
│   ├── db/                        # 数据库连接 + 查询封装
│   │   ├── db.go                  # 连接池管理
│   │   └── queries.go             # 预编译 SQL
│   ├── retry/                     # 重试工具包
│   │   └── retry.go               # 指数退避重试
│   └── report/                    # 报告生成
│       └── generator.go           # Markdown/HTML 生成
│
├── db/migrations/                 # PostgreSQL 迁移
│   └── 001_phase1_core_tables.sql
│
├── frontend/                      # React 前端
│   ├── src/
│   │   ├── pages/
│   │   │   └── Explorer.tsx       # 模型浏览器
│   │   ├── data/
│   │   │   └── models.json        # 静态数据(开发用)
│   │   └── App.tsx
│   ├── package.json
│   └── vite.config.ts
│
├── reports/daily/                 # Markdown 日报输出
│   └── daily_report_YYYY-MM-DD.md
│
├── tests/                         # 测试
│   ├── integration/               # 集成测试testcontainers-go
│   └── e2e/                       # E2E 测试
│
├── logs/                          # 日志文件运行时生成logrotate
│   ├── collector.log
│   ├── api.log
│   └── backup.log
│
├── Dockerfile                     # 多阶段构建
├── docker-compose.yml             # 生产编排
├── Makefile                       # 常用命令
├── go.mod                         # Go 依赖
└── .env.example                   # 环境变量模板


文档状态: 生产级设计 v1.1 完成

修订内容2026-05-09

  • 技术栈统一为 Go 1.22.2 + PostgreSQL
  • DDL 补充审计字段created_by/updated_by/deleted_at+ 数据血缘字段retrieved_at/batch_id/collector_version
  • API 安全章节:认证/限流/错误码/CORS
  • 新增 5 个生产级章节:安全/部署运维/可观测性/测试策略/容量规划

下一步行动:

  • T-3.2 Dashboard 组件完善Explorer 页面数据对接)
  • T-4.3 cron 每日自动采集 + 日报生成

文档编制宰相AI 辅助) 基于 PRD.mdv0.3、FEATURE_LIST.mdv1.1、BUSINESS_MODEL.mdv1.0、MARKET_ANALYSIS.mdv3.0


七、安全设计

7.1 安全原则

本项目遵循最小权限原则纵深防御策略:

层级 防御措施
网络层 TLS 1.3 全链路加密、防火墙白名单
应用层 输入校验、SQL 参数化查询、CSRF 防护
数据层 敏感字段加密、DB 用户权限最小化
运维层 密钥外部化、日志脱敏、定期轮换

7.2 API 密钥管理

OpenRouter API Key

存储方式 优先级 说明
环境变量 OPENROUTER_API_KEY 推荐 系统级配置,不进入代码仓库
Docker Secrets 容器环境 docker secret 或 K8s secret
.env 文件 ⚠️ 开发环境 必须加入 .gitignore
硬编码 禁止 任何提交含密钥 = 安全事件

Go 读取示例:

apiKey := os.Getenv("OPENROUTER_API_KEY")
if apiKey == "" {
    log.Fatal("OPENROUTER_API_KEY not set")
}

7.3 数据库凭证管理

连接字符串

postgresql://llm_hub:${DB_PASSWORD}@db:5432/llm_intelligence?sslmode=require
环境 密码来源
开发 .env 文件(不提交 git
CI/CD GitHub Actions Secrets
生产 Docker Secret / 云 KMS

.env.example 模板(无真实值):

# 数据库
DB_HOST=localhost
DB_PORT=5432
DB_NAME=llm_intelligence
DB_USER=llm_hub
DB_PASSWORD=                    # 必填,留空示例
DB_SSLMODE=require

# API 密钥
OPENROUTER_API_KEY=           # 必填

# 日志级别
LOG_LEVEL=info

7.4 传输安全

  • DB ↔ Appsslmode=require,禁止明文连接
  • App ↔ 外部 APItls.Config{MinVersion: tls.VersionTLS13}
  • 前端 ↔ 后端HTTPS 强制HSTS max-age=31536000
  • 内部通信:若 Phase 2 引入微服务mTLS 双向认证

7.5 输入验证与防注入

JSON Schema 校验(采集器):

type OpenRouterModel struct {
    ID              string  `json:"id" validate:"required"`
    Name            string  `json:"name"`
    Pricing         Pricing `json:"pricing" validate:"required"`
    ContextLength   int     `json:"context_length" validate:"gte=0,lte=10000000"`
}

SQL 防注入

  • 全部使用 database/sql 参数化查询($1, $2
  • 禁止字符串拼接 SQL
  • ORM/查询 builder Phase 2 评估

7.6 敏感数据保护

user_subscription 表敏感字段

字段 存储方式 说明
email AES-256-GCM 加密 密钥由 KMS 管理
phone AES-256-GCM 加密 仅显示后 4 位
feishu_webhook AES-256-GCM 加密 Webhook URL 含 token
dingtalk_webhook AES-256-GCM 加密 同上

日志脱敏

  • 日志中 email 显示为 l***@example.com
  • webhook URL 中 token 部分替换为 ***
  • DB 密码显示为 [REDACTED]

7.7 安全响应头

// HTTP 中间件
func securityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Security-Policy", "default-src 'self'")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        next.ServeHTTP(w, r)
    })
}

7.8 数据库最小权限

用户 权限 用途
llm_hub_app SELECT, INSERT, UPDATE 应用服务账号
llm_hub_collector SELECT, INSERT, UPDATE 采集器账号
llm_hub_readonly SELECT 只读查询(报表/审计)
llm_hub_admin ALL 迁移/运维(人工使用)

八、部署与运维架构

8.1 CI/CD 流水线

GitHub Actions 工作流.github/workflows/ci.yml

name: CI/CD
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: { go-version: '1.22' }
      - run: go vet ./...
      - run: gofmt -l . | tee /dev/stderr | wc -l | xargs test 0 -eq
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env: { POSTGRES_PASSWORD: test }
    steps:
      - uses: actions/checkout@v4
      - run: go test -race -coverprofile=coverage.out ./...
      - run: go tool cover -func=coverage.out | grep total
  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: go build -o bin/fetch_openrouter ./scripts/fetch_openrouter.go
      - run: go build -o bin/generate_daily_report ./scripts/generate_daily_report.go
      - uses: actions/upload-artifact@v4
        with: { name: binaries, path: bin/ }
  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with: { name: binaries }
      # SSH/SCP 到生产服务器,或 docker push

8.2 容器化

Dockerfile多阶段构建

# 构建阶段
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o fetch_openrouter scripts/fetch_openrouter.go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o generate_daily_report scripts/generate_daily_report.go

# 运行阶段
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /app/fetch_openrouter .
COPY --from=builder /app/generate_daily_report .
COPY --from=builder /app/db/migrations ./migrations
CMD ["./fetch_openrouter"]

docker-compose.yml生产版

version: '3.8'
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: llm_intelligence
      POSTGRES_USER: llm_hub
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./db/migrations:/docker-entrypoint-initdb.d
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U llm_hub"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build: .
    environment:
      DB_CONN: "host=db dbname=llm_intelligence sslmode=require"
      OPENROUTER_API_KEY_FILE: /run/secrets/openrouter_key
    secrets:
      - openrouter_key
      - db_password
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./frontend/dist:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on: [app]

volumes:
  postgres_data:

secrets:
  db_password:
    file: ./secrets/db_password.txt
  openrouter_key:
    file: ./secrets/openrouter_key.txt

8.3 健康检查端点

// GET /api/v1/health
func healthHandler(w http.ResponseWriter, r *http.Request) {
    health := struct {
        Status          string            `json:"status"`
        Version         string            `json:"version"`
        DB              string            `json:"db"`
        DBRecordCount   map[string]int    `json:"db_record_count"`
        LastCollectTime time.Time         `json:"last_collect_time"`
        LastReportTime  time.Time         `json:"last_report_time"`
        Uptime          string            `json:"uptime"`
    }{
        Status:  "ok",
        Version: os.Getenv("APP_VERSION"),
    }

    // DB 连通性检查
    if err := db.Ping(); err != nil {
        health.Status = "degraded"
        health.DB = "unreachable: " + err.Error()
        w.WriteHeader(http.StatusServiceUnavailable)
    } else {
        health.DB = "ok"
        health.DBRecordCount = getTableCounts()
    }

    // 数据新鲜度检查
    if time.Since(lastCollectTime) > 25*time.Hour {
        health.Status = "stale_data"
    }

    json.NewEncoder(w).Encode(health)
}

8.4 配置管理

环境变量清单

变量 必填 默认值 说明
DB_CONN PostgreSQL 连接字符串
OPENROUTER_API_KEY API 密钥
LOG_LEVEL info debug/info/warn/error
APP_VERSION dev 应用版本号
COLLECT_TIMEOUT 60s 采集超时
REPORT_OUTPUT_DIR ./reports/daily 日报输出目录
FRONTEND_DATA_DIR ./frontend/src/data 前端数据目录
ENABLE_METRICS false Prometheus 指标开关

环境区分策略

.env.development   # 本地开发
.env.staging       # 预生产(连接 staging DB
.env.production    # 生产(仅服务器上存在,不提交 git

8.5 滚动发布与回滚

零停机部署

  1. 构建新镜像 → docker build -t llm-hub:v1.1
  2. 蓝绿部署:新容器启动 + 健康检查通过
  3. 切换流量nginx upstream 指向新容器
  4. 保留旧容器 5 分钟(快速回滚)

回滚策略

# 紧急回滚30 秒内)
docker-compose stop app && docker-compose up -d app --no-deps --scale app=1
# 或切换到上一个镜像标签
docker tag llm-hub:v1.0 llm-hub:latest && docker-compose up -d app

九、可观测性体系

9.1 日志体系

结构化日志Go slog

import "log/slog"

var logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level:     parseLogLevel(os.Getenv("LOG_LEVEL")),
    AddSource: true,
}))

// 使用示例
logger.Info("collection completed",
    slog.String("collector", "openrouter"),
    slog.Int("records", 365),
    slog.Duration("duration", 12*time.Second),
)

日志分级

级别 场景 输出目标
DEBUG 开发调试、详细 SQL 本地 stdout生产关闭
INFO 正常流程、采集完成 stdout + 文件
WARN 降级处理、数据延迟 stdout + 文件 + 告警通道
ERROR 失败、异常、DB 断开 stdout + 文件 + 立即告警

日志轮转

# docker-compose 中的 logrotate 或 docker 原生
logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "5"

9.2 MetricsPrometheus

指标设计

指标名 类型 说明
collector_duration_seconds Histogram 采集耗时(按采集器标签)
collector_records_total Counter 采集记录数
collector_errors_total Counter 采集失败次数(按错误类型标签)
api_requests_total Counter API 请求数(按 endpoint/method/status
api_request_duration_seconds Histogram API 响应时间 P50/P95/P99
db_connection_active Gauge 当前活跃 DB 连接数
db_connection_wait_duration Histogram 等待连接池时间
data_freshness_hours Gauge 数据新鲜度(距上次采集小时数)

Go 实现prometheus/client_golang

import "github.com/prometheus/client_golang/prometheus"

var collectorDuration = prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "collector_duration_seconds",
        Help:    "Collector run duration",
        Buckets: prometheus.DefBuckets,
    },
    []string{"collector"},
)

func init() {
    prometheus.MustRegister(collectorDuration)
}

9.3 告警分级

等级 触发条件 通知方式 响应时间
P0 服务不可用、DB 断开、所有采集器失败 飞书/钉钉/短信 + 电话 15 分钟
P1 单个采集器失败 > 24h、API P99 > 2s 飞书/钉钉 1 小时
P2 价格异常变动 > 20%、数据延迟 > 48h 飞书群 4 小时

告警升级策略

  • P0 告警 15 分钟未恢复 → 升级至电话通知
  • P1 告警 1 小时未恢复 → 升级至 P0 通道
  • 同一问题 24h 内重复触发 → 合并为汇总告警(避免轰炸)

9.4 运行看板Grafana

推荐面板

  1. 采集健康:成功率、耗时趋势、各采集器状态
  2. 数据规模:模型数、定价记录数、日增量
  3. API 性能QPS、P99 延迟、错误率
  4. 资源使用CPU、内存、DB 连接池、磁盘
  5. 业务看板:价格变动 Top 10、新模型上线、免费政策变更

9.5 链路追踪Phase 2

评估 OpenTelemetry Go SDK

import "go.opentelemetry.io/otel"

// 采集链路cron → collector → API → parser → DB
// 追踪维度采集批次、模型处理耗时、DB 写入耗时

十、测试策略

10.1 单元测试

覆盖率目标

  • 整体 ≥ 80%
  • 采集器核心逻辑 ≥ 90%
  • 数据库查询 ≥ 85%
  • 错误处理路径 100%

Go 测试示例

// scripts/fetch_openrouter_test.go
func TestParseModelID(t *testing.T) {
    tests := []struct {
        input    string
        wantProvider string
        wantModel    string
    }{
        {"anthropic/claude-3.5-sonnet", "Anthropic", "Claude 3.5 Sonnet"},
        {"deepseek-ai/DeepSeek-V4", "DeepSeek", "V4"},
    }
    for _, tt := range tests {
        t.Run(tt.input, func(t *testing.T) {
            gotProvider, gotModel := parseModelID(tt.input)
            assert.Equal(t, tt.wantProvider, gotProvider)
            assert.Equal(t, tt.wantModel, gotModel)
        })
    }
}

Mock 策略

  • HTTP 外部调用httptest 模拟 OpenRouter API
  • DB 查询sqlmock 模拟数据库响应
  • 时间:手动注入 time.Now() 替代

10.2 集成测试

testcontainers-go + PostgreSQL

import "github.com/testcontainers/testcontainers-go"
import "github.com/testcontainers/testcontainers-go/modules/postgres"

func TestCollectorToDB(t *testing.T) {
    ctx := context.Background()
    pg, err := postgres.Run(ctx, "postgres:16-alpine",
        postgres.WithDatabase("test_db"),
        postgres.WithUsername("test"),
        postgres.WithPassword("test"),
    )
    require.NoError(t, err)
    defer pg.Terminate(ctx)

    connStr, _ := pg.ConnectionString(ctx)
    db := setupTestDB(connStr)

    // 运行采集器
    records, err := collectOpenRouter(ctx, db, mockAPIKey)
    require.NoError(t, err)
    require.Greater(t, len(records), 0)

    // 验证 DB 写入
    var count int
    db.QueryRow("SELECT COUNT(*) FROM model").Scan(&count)
    require.Greater(t, count, 0)
}

10.3 E2E 测试

覆盖范围

  • fetch_openrouter 二进制 → DB 有数据
  • generate_daily_report 二进制 → reports/daily/ 产出 Markdown
  • API 端到端:启动服务 → curl 请求 → 验证响应

前端 E2EPhase 2

  • Cypress/PlaywrightExplorer 页面加载、筛选交互、数据展示

10.4 性能测试

测试项 目标 工具
采集器并发 371 模型 < 60s Go benchmark + pprof
API 响应 P99 < 200ms k6 / vegeta
DB 查询 价格对比查询 < 100ms EXPLAIN ANALYZE

Go Benchmark 示例

func BenchmarkCollectOpenRouter(b *testing.B) {
    for i := 0; i < b.N; i++ {
        collectOpenRouter(ctx, mockClient)
    }
}

十一、容量规划

11.1 数据增长模型

数据源 日增量 年增量 存储/条
OpenRouter 模型 ~371 条 model ~135K ~2KB
定价记录 ~371 条 pricing ~135K ~1KB
价格历史 变动时插入,预估 ~50/天 ~18K ~0.5KB
日报 1 条/天 ~365 ~50KBHTML

1 年总估算(含索引膨胀 ×1.5

  • 模型+定价表:~400MB
  • 价格历史:~20MB
  • 日报:~30MB
  • 日志90 天轮转):~500MB
  • 总计:~1GB/年PostgreSQL 单机轻松承载)

11.2 QPS 与并发规划

阶段 预期并发 QPS 目标 策略
Phase 1 1-5 用户 10 QPS 单机 + 连接池 10
Phase 2 50-100 用户 100 QPS nginx 反代 + 连接池 50
Phase 3 1K+ 用户 500+ QPS 读副本 + CDN + 缓存

11.3 性能基准

指标 目标 测量方式
采集器单次运行 < 60s cron 日志
日报生成 < 30s 命令计时
API 响应 P50 < 50ms Prometheus histogram
API 响应 P99 < 200ms Prometheus histogram
DB 查询(价格对比) < 100ms EXPLAIN ANALYZE
前端首屏加载 < 2s Lighthouse

11.4 扩展策略

Phase 2 扩展点

  1. 读副本PostgreSQL 流复制,分离报表查询和写入
  2. 采集器并行:按厂商拆分 goroutine并发采集
  3. 缓存层Redis 缓存热门查询(模型列表、价格对比)
  4. CDN:前端静态资源 + 日报 HTML 缓存

Phase 3 扩展点

  1. 分库分表pricing_history 按时间分区PostgreSQL 原生分区)
  2. 对象存储:日报 HTML 长期归档至 MinIO/S3
  3. 消息队列采集任务入队Worker 消费(替代 cron 直接触发)