22 · 工程开发规格 · 数据 / API

数据模型与 API 规格

这是开发可直接照着建表、写接口的规格:D1 表结构与索引、OpenAI 兼容数据面 API、管理端 API 的请求/响应、鉴权、错误码与限流头。所有 schema 用 Zod 在运行时校验。

🗄️ 可执行 D1 DDL 🔌 OpenAI 兼容 + 管理端 Zod 校验

1数据模型概览

核心实体:product(内部产品)→ 持有多个 api_keychannel(上游渠道)提供多个 model(真实模型);logical_model(对外逻辑模型名)经 route 映射到候选 channel+model;每次调用写一行 request(记账);quota 控制每 key 额度;config_audit 记录管理变更。

product 1───* api_key ───* request *───1 logical_model │ │ * * (route 候选) quota channel 1───* model config_audit (所有管理变更) (provider 白名单)

2D1 DDL(SQLite · 可执行)

-- 0001_init.sql

CREATE TABLE products (
  id          TEXT PRIMARY KEY,            -- prod_xxx
  name        TEXT NOT NULL,
  status      TEXT NOT NULL DEFAULT 'active', -- active|suspended
  created_at  INTEGER NOT NULL              -- epoch ms
);

CREATE TABLE api_keys (
  id          TEXT PRIMARY KEY,            -- key_xxx
  product_id  TEXT NOT NULL REFERENCES products(id),
  key_hash    TEXT NOT NULL,               -- sha256(明文),明文仅创建时返回一次
  key_prefix  TEXT NOT NULL,               -- sk-aigw-xxxx(前 8 位,便于识别)
  scopes      TEXT NOT NULL DEFAULT 'chat,embeddings',
  status      TEXT NOT NULL DEFAULT 'active', -- active|revoked
  rpm_limit   INTEGER NOT NULL DEFAULT 60,
  concurrency INTEGER NOT NULL DEFAULT 10,
  created_at  INTEGER NOT NULL,
  revoked_at  INTEGER
);
CREATE UNIQUE INDEX idx_api_keys_hash ON api_keys(key_hash);
CREATE INDEX idx_api_keys_product ON api_keys(product_id);

CREATE TABLE channels (
  id          TEXT PRIMARY KEY,            -- ch_openrouter / ch_deepseek ...
  provider    TEXT NOT NULL,               -- openrouter|deepseek|groq|workers_ai|...(白名单)
  base_url    TEXT NOT NULL,
  secret_ref  TEXT NOT NULL,               -- 指向 Secret 名,不存明文 key
  enabled     INTEGER NOT NULL DEFAULT 1,
  weight      INTEGER NOT NULL DEFAULT 100,
  created_at  INTEGER NOT NULL
);

CREATE TABLE models (
  id            TEXT PRIMARY KEY,          -- m_xxx
  channel_id    TEXT NOT NULL REFERENCES channels(id),
  provider_model TEXT NOT NULL,            -- 厂商真实模型名,如 deepseek/deepseek-v3.2
  modality      TEXT NOT NULL DEFAULT 'chat', -- chat|embedding
  in_price      REAL NOT NULL,             -- USD / 1M input tokens
  out_price     REAL NOT NULL,             -- USD / 1M output tokens
  context_len   INTEGER,
  supports_stream INTEGER NOT NULL DEFAULT 1,
  enabled       INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX idx_models_channel ON models(channel_id);

CREATE TABLE logical_models (
  id          TEXT PRIMARY KEY,            -- 对外名: cheap-default / smart / embed-default
  tier        TEXT NOT NULL,               -- free|cheap|premium|flagship|embedding
  multiplier  REAL NOT NULL DEFAULT 1.0,   -- 计费倍率(折算额度单位)
  cache_ttl   INTEGER NOT NULL DEFAULT 0,  -- 响应缓存 TTL 秒,0=不缓存
  enabled     INTEGER NOT NULL DEFAULT 1
);

CREATE TABLE routes (
  id              TEXT PRIMARY KEY,
  logical_model_id TEXT NOT NULL REFERENCES logical_models(id),
  model_id        TEXT NOT NULL REFERENCES models(id),
  priority        INTEGER NOT NULL,        -- 小=优先;同优先按 weight 分流
  weight          INTEGER NOT NULL DEFAULT 100,
  enabled         INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX idx_routes_lm ON routes(logical_model_id, priority);

CREATE TABLE requests (
  id            TEXT PRIMARY KEY,          -- req_xxx (=x-request-id)
  ts            INTEGER NOT NULL,          -- epoch ms
  product_id    TEXT NOT NULL,
  api_key_id    TEXT NOT NULL,
  logical_model TEXT NOT NULL,
  channel_id    TEXT,                      -- 实际命中渠道
  provider_model TEXT,
  in_tokens     INTEGER NOT NULL DEFAULT 0,
  out_tokens    INTEGER NOT NULL DEFAULT 0,
  cost_usd      REAL NOT NULL DEFAULT 0,   -- 实际上游成本
  billed_units  REAL NOT NULL DEFAULT 0,   -- cost×倍率,扣配额用
  cache_hit     INTEGER NOT NULL DEFAULT 0,
  fallback      INTEGER NOT NULL DEFAULT 0,
  status        INTEGER NOT NULL,          -- HTTP 状态
  latency_ms    INTEGER NOT NULL DEFAULT 0,
  error_code    TEXT
);
CREATE INDEX idx_requests_ts ON requests(ts);
CREATE INDEX idx_requests_product_ts ON requests(product_id, ts);

CREATE TABLE quotas (
  api_key_id    TEXT NOT NULL REFERENCES api_keys(id),
  period        TEXT NOT NULL,             -- 'day' | 'month'
  limit_units   REAL NOT NULL,             -- 额度单位上限
  used_units    REAL NOT NULL DEFAULT 0,
  reset_at      INTEGER NOT NULL,
  PRIMARY KEY (api_key_id, period)
);

CREATE TABLE config_audit (
  id          TEXT PRIMARY KEY,
  ts          INTEGER NOT NULL,
  actor       TEXT NOT NULL,               -- 管理员标识
  action      TEXT NOT NULL,               -- create_channel|update_route|revoke_key...
  entity      TEXT NOT NULL,
  detail      TEXT                         -- JSON
);

KV:响应缓存 cache:<hash>→响应体;配置热缓存 cfg:*。Durable Object:每 key 一个限流实例。详见 23

3鉴权

  • 数据面Authorization: Bearer sk-aigw-...。引擎对明文 sha256 后查 api_keys.key_hash(KV 缓存校验结果,短 TTL)。status≠active 或吊销 → 401。
  • 管理端:独立 X-Admin-Token(Secret)或 Cloudflare Access(JWT);与数据面分离;所有写操作记 config_audit
  • 明文 key 仅在创建时返回一次,库内只存哈希与前缀。

4数据面 API(OpenAI 兼容)

POST /v1/chat/completions

// 请求(OpenAI 兼容子集,Zod)
{
  "model": "cheap-default",          // 逻辑模型名(必填)
  "messages": [{"role":"user","content":"..."}],
  "stream": false,                   // true→SSE
  "temperature": 0.7,
  "max_tokens": 1024,
  "metadata": { "feature": "dream-interpret" }  // 可选,记账维度
}

// 响应(非流式,OpenAI 兼容)
{
  "id": "chatcmpl_...",
  "object": "chat.completion",
  "model": "cheap-default",          // 回显逻辑模型
  "choices": [{ "index":0, "message":{"role":"assistant","content":"..."}, "finish_reason":"stop" }],
  "usage": { "prompt_tokens":12, "completion_tokens":34, "total_tokens":46 }
}
// 响应头:x-request-id, x-gw-channel: ch_deepseek, x-gw-model: deepseek/deepseek-v3.2,
//          x-gw-cache: hit|miss, x-gw-fallback: 0|1, x-gw-cost-usd: 0.0000

流式:text/event-stream,逐 chunk 透传 chat.completion.chunk,末尾 [DONE];usage 在最后一帧或由引擎按 token 计估。

POST /v1/embeddings

{ "model":"embed-default", "input":["text a","text b"] }
→ { "object":"list", "data":[{"object":"embedding","index":0,"embedding":[...]}], "usage":{...} }

GET /v1/models

→ { "object":"list", "data":[ {"id":"cheap-default","object":"model","tier":"cheap"}, ... ] }
// 仅返回 enabled 的逻辑模型

5管理端 API(/admin/*,需 Admin 鉴权)

方法 + 路径作用
POST /admin/products创建产品
POST /admin/keys为产品签发 key(响应含明文一次);body: product_id, scopes, rpm_limit, quota
DELETE /admin/keys/:id吊销 key
POST /admin/channels / PATCH /admin/channels/:id增改渠道(provider 必须在白名单,否则 422)
POST /admin/models登记真实模型 + 定价
PUT /admin/logical-models/:id/routes设置逻辑模型的候选路由(优先级+权重)
GET /admin/usage?group=product|model&from=&to=用量/成本聚合(读 requests)
GET /admin/keys/:id/quota查 key 当前配额与已用
// POST /admin/channels 示例(白名单校验)
{ "provider":"deepseek", "base_url":"https://api.deepseek.com",
  "secret_ref":"DEEPSEEK_API_KEY", "weight":100 }
// provider ∈ {openrouter, deepseek, groq, workers_ai, openai, anthropic, google, mistral, together, deepinfra}
// 非白名单(如逆向/养号端点)→ 422 channel_provider_not_allowed(记 config_audit)

6错误码与限流头

HTTPerror code含义
401invalid_api_key / key_revoked鉴权失败
403scope_forbiddenkey 无该端点权限
404unknown_logical_model逻辑模型不存在/未启用
422channel_provider_not_allowed渠道非白名单(红线护栏)
429rate_limited / quota_exceededRPM 超限 / 额度用尽(带 Retry-After)
502upstream_error所有候选渠道均失败(已尝试回退)
503no_available_channel逻辑模型无可用渠道

错误体(OpenAI 风格):{ "error": { "code":"quota_exceeded", "message":"...", "type":"gateway" } }。限流响应带 Retry-AfterX-RateLimit-Remaining