好的,以下是完整的最终设计方案。

---

# LLM Gateway 完整设计方案

## 一、项目概述

LLM Gateway 是一个基于 Next.js 的 OpenAI 兼容 API 网关,用于统一代理多个 vLLM 后端,提供用户管理、模型授权、用量限额和使用统计功能。

### 核心目标

- 用户通过统一的 `(base_url, model_alias, api_key)` 访问多个 vLLM 后端
- 管理员通过 Web Console 管理用户、模型、授权和限额
- 支持 SSE 流式响应和 tools/function calling 透传
- 按模型粒度控制用户的 token 用量、请求次数和使用时间段

### 技术栈

| 类别 | 选型 |
|------|------|
| 框架 | Next.js 15(App Router) |
| 语言 | TypeScript |
| 数据库 | PostgreSQL |
| ORM | Drizzle ORM |
| UI 组件库 | shadcn/ui(Tailwind CSS + Radix UI) |
| 图表 | Recharts |
| 部署方式 | `next start`(standalone 单进程) |
| 认证 | JWT(httpOnly cookie) |

### 约束与规模

- 模型数量:10 个以下
- 并发:100 以下
- 内网环境,API Key 明文存储
- 不涉及计费

---

## 二、系统架构

```
用户 (OpenAI SDK / curl / 任意 HTTP 客户端)
        │
        │  POST /api/v1/chat/completions
        │  POST /api/v1/completions
        │  GET  /api/v1/models
        │  GET  /api/v1/models/{model}
        │  Authorization: Bearer sk-xxxxx
        ▼
┌──────────────────────────────────────────┐
│         LLM Gateway (Next.js 15)         │
│                                          │
│  ┌────────────────────────────────────┐  │
│  │       Admin Console (Web UI)       │  │
│  │  /admin/login                      │  │
│  │  /admin/dashboard                  │  │
│  │  /admin/users                      │  │
│  │  /admin/models                     │  │
│  │  /admin/usage                      │  │
│  └────────────────────────────────────┘  │
│                                          │
│  ┌────────────────────────────────────┐  │
│  │    OpenAI-Compatible Proxy Layer   │  │
│  │  /api/v1/*                         │  │
│  │                                    │  │
│  │  1. 认证 (api_key → user)          │  │
│  │  2. 模型解析 (alias → backend)     │  │
│  │  3. 权限检查 (user_models)         │  │
│  │  4. 限额检查 (quotas + usage)      │  │
│  │  5. 请求转发 (rewrite + proxy)     │  │
│  │  6. 响应透传 (SSE / JSON)          │  │
│  │  7. 用量记录 (async)               │  │
│  └────────────────────────────────────┘  │
│                                          │
│  ┌────────────────────────────────────┐  │
│  │    Admin API Layer                 │  │
│  │  /api/admin/*                      │  │
│  │  JWT 认证保护                       │  │
│  └────────────────────────────────────┘  │
│                                          │
│  ┌────────────────────────────────────┐  │
│  │    内存限流器                       │  │
│  │  Map<userId:modelId, timestamps[]> │  │
│  └────────────────────────────────────┘  │
│                                          │
│  ┌────────────────────────────────────┐  │
│  │         PostgreSQL                 │  ���
│  └────────────────────────────────────┘  │
└──────────────────────────────────────────┘
        │
        │  按 model alias 路由到对应后端
        ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ vLLM: qwen3.5│  │ vLLM: qwen3  │  │ vLLM: ...    │
│ ip1:port1    │  │ ip2:port2    │  │ ipN:portN    │
└──────────────┘  └──────────────┘  └──────────────┘
```

---

## 三、数据模型

### 3.1 ER 关系图

```
users ──1:N── user_models ──N:1── models
  │                                  │
  │──1:N── user_model_quotas ──N:1───│
  │
  │──1:N── usage_logs ──N:1── models
  │
  │──1:N── daily_usage ──N:1── models
```

### 3.2 表结构

```sql
-- ============================================
-- 用户表
-- ============================================
CREATE TABLE users (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name            VARCHAR(100) NOT NULL,
    email           VARCHAR(255) UNIQUE NOT NULL,
    password_hash   VARCHAR(255),               -- 仅 admin 用户需要,普通用户为 NULL
    api_key         VARCHAR(64) UNIQUE NOT NULL, -- 明文存储,格式:sk-<32位随机hex>
    is_active       BOOLEAN DEFAULT true,
    is_admin        BOOLEAN DEFAULT false,
    created_at      TIMESTAMPTZ DEFAULT now(),
    updated_at      TIMESTAMPTZ DEFAULT now()
);

-- ============================================
-- 模型注册表
-- ============================================
CREATE TABLE models (
    id                              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    alias                           VARCHAR(100) UNIQUE NOT NULL,   -- 用户可见的模型名
    backend_url                     VARCHAR(500) NOT NULL,          -- 如 http://ip1:port1/v1
    backend_model                   VARCHAR(200) NOT NULL,          -- 真实模型名
    backend_api_key                 VARCHAR(200),                   -- 后端 API Key(可选)
    is_active                       BOOLEAN DEFAULT true,

    -- 默认限额模板(新用户授权时自动继承)
    default_max_tokens_per_day      BIGINT,       -- NULL = 不限
    default_max_requests_per_day    INT,          -- NULL = 不限
    default_max_requests_per_min    INT,          -- NULL = 不限
    default_allowed_time_start      TIME,         -- NULL = 不限
    default_allowed_time_end        TIME,         -- NULL = 不限

    created_at                      TIMESTAMPTZ DEFAULT now()
);

-- ============================================
-- 用户-模型授权(多对多)
-- ============================================
CREATE TABLE user_models (
    user_id     UUID REFERENCES users(id) ON DELETE CASCADE,
    model_id    UUID REFERENCES models(id) ON DELETE CASCADE,
    created_at  TIMESTAMPTZ DEFAULT now(),
    PRIMARY KEY (user_id, model_id)
);

-- ============================================
-- 用户-模型限额配置
-- ============================================
CREATE TABLE user_model_quotas (
    id                      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id                 UUID REFERENCES users(id) ON DELETE CASCADE,
    model_id                UUID REFERENCES models(id) ON DELETE CASCADE,
    max_tokens_per_day      BIGINT,       -- NULL = 不限
    max_requests_per_day    INT,          -- NULL = 不限
    max_requests_per_min    INT,          -- NULL = 不限
    allowed_time_start      TIME,         -- NULL = 不限
    allowed_time_end        TIME,         -- NULL = 不限
    created_at              TIMESTAMPTZ DEFAULT now(),
    updated_at              TIMESTAMPTZ DEFAULT now(),
    UNIQUE(user_id, model_id)
);

-- ============================================
-- 使用日志(详细记录每次请求)
-- ============================================
CREATE TABLE usage_logs (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id             UUID REFERENCES users(id),
    model_id            UUID REFERENCES models(id),
    request_type        VARCHAR(50) NOT NULL,     -- "chat.completions" | "completions"
    prompt_tokens       INT DEFAULT 0,
    completion_tokens   INT DEFAULT 0,
    total_tokens        INT DEFAULT 0,
    is_stream           BOOLEAN DEFAULT false,
    duration_ms         INT,
    status              VARCHAR(20),              -- "success" | "error"
    created_at          TIMESTAMPTZ DEFAULT now()
);

-- ============================================
-- 每日聚合(按用户+模型+日期,加速限额检查)
-- ============================================
CREATE TABLE daily_usage (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id         UUID REFERENCES users(id),
    model_id        UUID REFERENCES models(id),
    date            DATE NOT NULL,
    total_tokens    BIGINT DEFAULT 0,
    request_count   INT DEFAULT 0,
    UNIQUE(user_id, model_id, date)
);

-- ============================================
-- 索引
-- ============================================
CREATE INDEX idx_users_api_key ON users(api_key);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_models_alias ON models(alias);
CREATE INDEX idx_usage_logs_user_created ON usage_logs(user_id, created_at);
CREATE INDEX idx_usage_logs_model_created ON usage_logs(model_id, created_at);
CREATE INDEX idx_daily_usage_user_model_date ON daily_usage(user_id, model_id, date);
```

### 3.3 限额检查优先级

```
user_model_quotas 有记录?
    ├── 是 → 使用 user_model_quotas 中的值
    └── 否 → 使用 models 表中的 default_* 值(即模板)
              └── 也为 NULL → 不限制
```

### 3.4 初始管理员 Seed

首次启动时,从环境变量创建管理员:

```
ADMIN_NAME=Admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your-secure-password
```

启动时检查:若 `users` 表中无 `is_admin=true` 的用户,则自动创建。

---

## 四、API 设计

### 4.1 OpenAI 兼容代理接口(`/api/v1/*`)

所有请求通过 `Authorization: Bearer sk-xxxxx` 认证。

| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/api/v1/chat/completions` | Chat Completions(支持 stream、tools) |
| `POST` | `/api/v1/completions` | Completions(支持 stream) |
| `GET` | `/api/v1/models` | 返回当前用户被授权的模型列表 |
| `GET` | `/api/v1/models/{model}` | 返回单个模型详情 |
| `GET` | `/api/health` | 健康检查(无需认证) |

#### 请求转发规则

用户发送:
```json
{
  "model": "my-qwen3",
  "messages": [{"role": "user", "content": "hello"}],
  "stream": true,
  "tools": [...]
}
```

Gateway 转发到后端时改写:
```
URL:   http://ip2:port2/v1/chat/completions
Auth:  Bearer <backend_api_key>
Body:  { "model": "qwen3", ... }   ← alias 替换为 backend_model
```

响应原样透传给用户(model 字段保持后端返回的值,不做回写)。

#### 错误响应格式

所有错误统一使用 OpenAI 格式,保证客户端 SDK 兼容:
```json
{
  "error": {
    "message": "具体错误信息",
    "type": "error_type",
    "code": "error_code"
  }
}
```

| 场景 | HTTP 状态码 | type | code |
|------|------------|------|------|
| API Key 无效或缺失 | 401 | `authentication_error` | `invalid_api_key` |
| 用户已禁用 | 403 | `permission_error` | `user_disabled` |
| 无权访问该模型 | 403 | `permission_error` | `model_not_allowed` |
| 模型不存在 | 404 | `not_found_error` | `model_not_found` |
| 每日 token 超限 | 429 | `rate_limit_error` | `daily_token_limit` |
| 每日请求次数超限 | 429 | `rate_limit_error` | `daily_request_limit` |
| 每分钟请求超限 | 429 | `rate_limit_error` | `rate_limit_exceeded` |
| 不在允许时间段内 | 403 | `permission_error` | `time_restricted` |
| 后端不可用 | 502 | `server_error` | `backend_unavailable` |
| 后端超时 | 504 | `server_error` | `backend_timeout` |

### 4.2 管理后台 API(`/api/admin/*`)

所有请求通过 JWT cookie 认证,仅 `is_admin=true` 的用户可访问。

#### 认证

| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/api/admin/auth/login` | 登录(email + password → JWT cookie) |
| `POST` | `/api/admin/auth/logout` | 登出(清除 cookie) |
| `GET` | `/api/admin/auth/me` | 获取当前管理员信息 |

#### 用户管理

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/admin/users` | 用户列表(支持分页、搜索) |
| `POST` | `/api/admin/users` | 创建用户(自动生成 api_key) |
| `GET` | `/api/admin/users/{id}` | 用户详情 |
| `PUT` | `/api/admin/users/{id}` | 更新用户信息 |
| `DELETE` | `/api/admin/users/{id}` | 删除用户 |
| `POST` | `/api/admin/users/{id}/regenerate-key` | 重新生成 API Key |

#### 模型管理

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/admin/models` | 模型列表 |
| `POST` | `/api/admin/models` | 注册模型 |
| `GET` | `/api/admin/models/{id}` | 模型详情 |
| `PUT` | `/api/admin/models/{id}` | 更新模型 |
| `DELETE` | `/api/admin/models/{id}` | 删除模型 |
| `POST` | `/api/admin/models/{id}/test` | 测试后端连通性 |

#### 用户-模型授权与限额

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/admin/users/{id}/models` | 获取用户已授权的模型及限额 |
| `POST` | `/api/admin/users/{id}/models` | 为用户授权模型(自动继承默认限额模板) |
| `DELETE` | `/api/admin/users/{id}/models/{modelId}` | 取消授权 |
| `PUT` | `/api/admin/users/{id}/models/{modelId}/quota` | 设置/更新用户对该模型的限额 |

#### 用量统计

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/admin/usage/overview` | 总览统计(今日/近7天/近30天) |
| `GET` | `/api/admin/usage/by-user` | 按用户统计 |
| `GET` | `/api/admin/usage/by-model` | 按模型统计 |
| `GET` | `/api/admin/usage/by-user/{id}` | 单个用户的详细用量 |
| `GET` | `/api/admin/usage/logs` | 请求日志列表(分页) |

---

## 五、请求代理核心流程

### 5.1 完整请求生命周期

```
请求到达 /api/v1/chat/completions 或 /api/v1/completions
│
├─ 1. 解析 Authorization Header
│     提取 Bearer token → api_key
│     失败 → 401 invalid_api_key
│
├─ 2. 用户认证
│     SELECT * FROM users WHERE api_key = $1
│     未找到 → 401 invalid_api_key
│     is_active = false → 403 user_disabled
│
├─ 3. 模型解析
│     从 request body 提取 model 字段
│     SELECT * FROM models WHERE alias = $1 AND is_active = true
│     未找到 → 404 model_not_found
│
├─ 4. 权限检查
│     SELECT 1 FROM user_models WHERE user_id = $1 AND model_id = $2
│     未找到 → 403 model_not_allowed
│
├─ 5. 限额检查(按顺序)
│     a. 时间窗口检查
│        获取限额配置(优先 user_model_quotas,fallback models.default_*)
│        当前时间不在 [allowed_time_start, allowed_time_end] 内
│        → 403 time_restricted
│
│     b. 每分钟请求限流(内存滑动窗口)
│        检查最近 60s 内该 user+model 的请求数
│        超过 max_requests_per_min → 429 rate_limit_exceeded
│
│     c. 每日请求次数
│        SELECT request_count FROM daily_usage
│          WHERE user_id=$1 AND model_id=$2 AND date=today
│        超过 max_requests_per_day → 429 daily_request_limit
│
│     d. 每日 token 用量
│        SELECT total_tokens FROM daily_usage
│          WHERE user_id=$1 AND model_id=$2 AND date=today
│        超过 max_tokens_per_day → 429 daily_token_limit
│
├─ 6. 构造后端请求
│     - URL: backend_url + /chat/completions 或 /completions
│     - Headers: Authorization: Bearer <backend_api_key>
│     - Body: 替换 model 字段为 backend_model,其余原样透传
│       (包括 messages, stream, tools, tool_choice,
│         temperature, max_tokens 等所有参数)
│
├─ 7. 发送请求到后端并处理响应
│     ├─ 后端无响应 → 502 backend_unavailable
│     ├─ 后端超时 → 504 backend_timeout
│     ├─ 非流式响应:
│     │    等待完整 JSON → 提取 usage 字段 → 返回给用户
│     └─ 流式响应 (SSE):
│          使用 TransformStream 逐 chunk 透传
│          解析最后一个包含 usage 的 chunk
│          (若后端未返回 usage,则 tokens 记为 0)
│
└─ 8. 异步记录用量(不阻塞响应)
      - INSERT INTO usage_logs (...)
      - INSERT INTO daily_usage (...) ON CONFLICT (user_id, model_id, date)
        DO UPDATE SET
          total_tokens = daily_usage.total_tokens + $tokens,
          request_count = daily_usage.request_count + 1
```

### 5.2 SSE 流式透传实现要点

```typescript
// 核心伪代码
async function handleStreamResponse(
  backendResponse: Response,
  userId: string,
  modelId: string
): Promise<Response> {
  let usageData = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };

  const transformStream = new TransformStream({
    transform(chunk, controller) {
      // 原样透传每个 SSE chunk 给客户端
      controller.enqueue(chunk);

      // 尝试从 chunk 中解析 usage(通常在最后一个 chunk)
      // vLLM 在 stream_options.include_usage=true 时会在最后返回 usage
      const text = new TextDecoder().decode(chunk);
      const lines = text.split('\n').filter(l => l.startsWith('data: '));
      for (const line of lines) {
        const jsonStr = line.slice(6); // 去掉 "data: "
        if (jsonStr === '[DONE]') continue;
        try {
          const parsed = JSON.parse(jsonStr);
          if (parsed.usage) {
            usageData = parsed.usage;
          }
        } catch { /* 忽略解析失败 */ }
      }
    },
    flush() {
      // 流结束后异步记录用量
      recordUsage(userId, modelId, usageData).catch(console.error);
    }
  });

  backendResponse.body!.pipeThrough(transformStream);

  return new Response(transformStream.readable, {
    status: 200,
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}
```

### 5.3 内存限流器

```typescript
// 基于滑动窗口的简单限流,standalone 单进程下可靠
class RateLimiter {
  // key: "userId:modelId", value: 请求时间戳数组
  private windows: Map<string, number[]> = new Map();

  check(userId: string, modelId: string, maxPerMin: number): boolean {
    const key = `${userId}:${modelId}`;
    const now = Date.now();
    const windowStart = now - 60_000;

    let timestamps = this.windows.get(key) || [];
    timestamps = timestamps.filter(t => t > windowStart); // 清理过期

    if (timestamps.length >= maxPerMin) return false;

    timestamps.push(now);
    this.windows.set(key, timestamps);
    return true;
  }

  // 定时清理(每 5 分钟清一次长期不活跃的 key)
  cleanup() { /* ... */ }
}

// 全局单例
export const rateLimiter = new RateLimiter();
```

### 5.4 超时配置

| 场景 | 超时时间 |
|------|---------|
| 非流式请求 | 300 秒 |
| 流式请求 | 600 秒 |
| 后端连通性测试 | 10 秒 |

通过环境变量可配置:
```
PROXY_TIMEOUT_NON_STREAM=300000
PROXY_TIMEOUT_STREAM=600000
```

---

## 六、Admin Console 页面设计

### 6.1 页面结构

```
/admin/login                    ← 登录页
/admin                          ← 重定向到 /admin/dashboard
/admin/dashboard                ← 总览仪表盘
/admin/users                    ← 用户列表
/admin/users/new                ← 创建用户
/admin/users/{id}               ← 用户详情(含授权模型 + 限额配置)
/admin/models                   ← 模型列表
/admin/models/new               ← 注册模型(含默认限额模板)
/admin/models/{id}              ← 模型详情/编辑
/admin/usage                    ← 用量统计
```

### 6.2 各页面功能详述

#### Dashboard(`/admin/dashboard`)

```
┌──────────────────────────────────────────────────┐
│  Dashboard                                        │
│                                                    │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────┐ │
│  │ 总用户数  │ │ 活跃模型 │ │ 今日请求 │ │今日   │ │
│  │   12     │ │    8     │ │  3,456   │ │Token  │ │
│  │          │ │          │ │          │ │1.2M   │ │
│  └──────────┘ └──────────┘ └──────────┘ └──────┘ │
│                                                    │
│  ┌─────────────────────────────────────────────┐  │
│  │  近 7 天请求量趋势(折线图)                    │  │
│  │  📈                                          │  │
│  └─────────────────────────────────────────────┘  │
│                                                    │
│  ┌─────────────────────────────────────────────┐  │
│  │  近 7 天 Token 消耗趋势(折线图)              │  │
│  │  📈                                          │  │
│  └─────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘
```

#### 用户管理(`/admin/users`)

- **列表页**:表格展示(名称、邮箱、API Key、状态、已授权模型数、今日用量、操作)
- **创建用户**:填写名称、邮箱 → 自���生成 API Key → 创建后展示完整 Key
- **用户详情页**:
  - 基本信息编辑(名称、邮箱、启用/禁用)
  - API Key 展示 + 重新生成按钮
  - 已授权模型列表 + 每个模型的限额配置(内联编辑)
  - 添加模型授权(下拉选择,自动继承模型默认限额)
  - 该用户的用量统计摘要

#### 模型管理(`/admin/models`)

- **列表页**:表格展示(别名、后端地址、后端模型名、状态、授权用户数、操作)
- **注册模型**:
  - 填写:别名(限制 `a-z0-9-`)、后端 URL、后端模型名、后端 API Key
  - 设置默认限额模板(可选)
  - 「测试连接」按钮
- **模型详情页**:编辑模型信息 + 默认限额模板

#### 用量统计(`/admin/usage`)

- 日期范围选择器
- **按用户统计 Tab**:表格(用户名、总请求数、总 Token、按模型明细展开)
- **按模型统计 Tab**:表格(模型别名、总请求数、总 Token、按用户明细展开)
- **请求日志 Tab**:分页表格(时间、用户、模型、类型、Token 数、耗时、状态)

---

## 七、环境变量

```bash
# 数据库
DATABASE_URL=postgresql://user:password@localhost:5432/llm_gateway

# 初始管理员(首次启动时 seed)
ADMIN_NAME=Admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your-secure-password

# JWT
JWT_SECRET=your-jwt-secret-key
JWT_EXPIRES_IN=24h

# 时区(用于时间窗口限额检查)
TZ=Asia/Shanghai

# 代理超时(毫秒)
PROXY_TIMEOUT_NON_STREAM=300000
PROXY_TIMEOUT_STREAM=600000
```

---

## 八、项目目录结构

```
llm-gateway/
├── src/
│   ├── app/
│   │   ├── api/
│   │   │   ├── v1/
│   │   │   │   ├── chat/
│   │   │   │   │   └── completions/
│   │   │   │   │       └── route.ts          # POST: Chat Completions 代理
│   │   │   │   ├── completions/
│   │   │   │   │   └── route.ts              # POST: Completions 代理
│   │   │   │   └── models/
│   │   │   │       ├── route.ts              # GET: 模型列表
│   │   │   │       └── [model]/
│   │   │   │           └── route.ts          # GET: 单个模型详情
│   │   │   ├── admin/
│   │   │   │   ├── auth/
│   │   │   │   │   ├── login/route.ts
│   │   │   │   │   ├── logout/route.ts
│   │   │   │   │   └── me/route.ts
│   │   │   │   ├── users/
│   │   │   │   │   ├── route.ts              # GET: 列表, POST: 创建
│   │   │   │   │   └── [id]/
│   │   │   │   │       ├── route.ts          # GET, PUT, DELETE
│   │   │   │   │       ├── regenerate-key/route.ts
│   │   │   │   │       └── models/
│   │   │   │   │           ├── route.ts      # GET: 用户模型列表, POST: 授权
│   │   │   │   │           └── [modelId]/
│   │   │   │   │               ├── route.ts  # DELETE: 取消授权
│   │   │   │   │               └── quota/route.ts  # PUT: 设置限额
│   │   │   │   ├── models/
│   │   │   │   │   ├── route.ts
│   │   │   │   │   └── [id]/
│   │   │   │   │       ├── route.ts
│   │   │   │   │       └── test/route.ts     # POST: 测试连通性
│   │   │   │   └── usage/
│   │   │   │       ├── overview/route.ts
│   │   │   │       ├── by-user/
│   │   │   │       │   ├── route.ts
│   │   │   │       │   └── [id]/route.ts
│   │   │   │       ├── by-model/route.ts
│   │   │   │       └── logs/route.ts
│   │   │   └── health/
│   │   │       └── route.ts                  # 健康检查
│   │   ├── admin/
│   │   │   ├── layout.tsx                    # Admin 布局(侧边栏 + JWT 检��)
│   │   │   ├── login/page.tsx
│   │   │   ├── dashboard/page.tsx
│   │   │   ├── users/
│   │   │   │   ├── page.tsx
│   │   │   │   ├── new/page.tsx
│   │   │   │   └── [id]/page.tsx
│   │   │   ├── models/
│   │   │   │   ├── page.tsx
│   │   │   │   ├── new/page.tsx
│   │   │   │   └── [id]/page.tsx
│   │   │   └── usage/page.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx                          # 根页面,重定向到 /admin
│   ├── lib/
│   │   ├── db/
│   │   │   ├── index.ts                      # Drizzle 连接
│   │   │   ├── schema.ts                     # Drizzle schema 定义
│   │   │   └── seed.ts                       # 初始管理员 seed
│   │   ├── auth/
│   │   │   └── jwt.ts                        # JWT 签发/验证
│   │   ├── proxy/
│   │   │   ├── handler.ts                    # 请求代理核心逻辑
│   │   │   ├── stream.ts                     # SSE 流式透传
│   │   │   └── errors.ts                     # OpenAI 格式错误构造
│   │   ├── quota/
│   │   │   ├── checker.ts                    # 限额检查逻辑
│   │   │   └── rate-limiter.ts               # 内存滑动窗口限流
│   │   ├── usage/
│   │   │   └── recorder.ts                   # 用量记录(usage_logs + daily_usage)
│   │   └── utils/
│   │       ├── api-key.ts                    # API Key 生成
│   │       └── validators.ts                 # 输入校验(模型别名格式等)
│   ├── middleware.ts                         # Next.js 中间件(admin 路由 JWT 检查)
│   └── components/                           # shadcn/ui 组件
│       └── ...
├── drizzle/
│   └── migrations/                           # 数据库迁移文件
├── drizzle.config.ts
├── next.config.ts
├── tailwind.config.ts
├── package.json
├── tsconfig.json
├── .env.example
��── README.md
```

---

## 九、关键实现备注

### 9.1 `GET /api/v1/models` 响应格式

严格遵循 OpenAI API 格式:

```json
{
  "object": "list",
  "data": [
    {
      "id": "my-qwen3",
      "object": "model",
      "created": 1700000000,
      "owned_by": "llm-gateway"
    },
    {
      "id": "my-qwen3.5",
      "object": "model",
      "created": 1700000000,
      "owned_by": "llm-gateway"
    }
  ]
}
```

### 9.2 模型别名校验规则

```
/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/
```

- 只允许小写字母、数字、短横线
- 不能以短横线开头或结尾
- 长度 1-100

### 9.3 API Key 格式

```
sk-<32位随机十六进制字符>
```

示例:`sk-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6`

### 9.4 健康检查响应

```json
// GET /api/health
{
  "status": "ok",
  "timestamp": "2026-03-17T10:30:00.000Z",
  "database": "connected"
}
```

### 9.5 模型连通性测试

`POST /api/admin/models/{id}/test` 向后端发送 `GET <backend_url>/models`,10 秒超时:

```json
// 成功
{ "status": "ok", "latency_ms": 42 }

// 失败
{ "status": "error", "message": "Connection refused" }
```

---

以上就是完整设计方案,请 review!如果没有问题,我们就按此方案开始实现。