AI API 中转平台 — 完整开发流程

2026年4月9日

技术栈与部署方案

  • 前端:Next.js(App Router)+ TailwindCSS + shadcn/ui
  • 后端:NestJS + Prisma + PostgreSQL
  • 部署:前端 Vercel / 后端美国服务器(Docker)

一、项目结构

整个项目使用 pnpm workspace 管理 monorepo,分为 web(前端)、server(后端)和可选的 shared(共享类型)三个包。

ai-relay/
├── pnpm-workspace.yaml
├── packages/
│   ├── web/                  # Next.js 前端(App Router)
│   │   └── src/
│   │       ├── app/
│   │       │   ├── (auth)/           # 登录/注册
│   │       │   ├── (dashboard)/      # 控制面板(需登录)
│   │       │   │   ├── overview/     # 总览/统计
│   │       │   │   ├── keys/         # API Key 管理
│   │       │   │   ├── logs/         # 调用日志
│   │       │   │   ├── channels/     # 渠道管理(Admin)
│   │       │   │   ├── playground/   # API 测试
│   │       │   │   └── settings/     # 个人设置
│   │       │   └── page.tsx          # 首页/Landing
│   │       ├── components/
│   │       │   ├── ui/               # shadcn/ui 组件
│   │       │   ├── layout/           # Sidebar, Header
│   │       │   └── dashboard/        # 业务组件
│   │       ├── lib/                  # API 封装、Token 管理
│   │       └── hooks/
│   │
│   └── server/               # NestJS 后端
│       ├── src/
│       │   ├── modules/
│       │   │   ├── auth/         # 认证(JWT)
│       │   │   ├── user/         # 用户管理
│       │   │   ├── api-key/      # API Key CRUD
│       │   │   ├── proxy/        # 核心:代理转发
│       │   │   ├── channel/      # 上游渠道管理
│       │   │   ├── log/          # 请求日志
│       │   │   └── stats/        # 数据统计
│       │   ├── common/           # Guards、Interceptors、Filters
│       │   └── prisma/           # Prisma Service
│       ├── prisma/
│       │   └── schema.prisma
│       └── Dockerfile
│
└── packages/shared/          # 共享类型(可选)
text

二、数据库设计

使用 Prisma + PostgreSQL,核心有四张表:User、ApiKey、RequestLog、Channel。

用户表

-- User 模型
-- id:        String   主键 cuid
-- email:     String   唯一
-- password:  String   bcrypt 哈希
-- name:      String?  可选
-- role:      Role     默认 USER (USER / ADMIN)
-- balance:   Float    默认 0,余额(可选计费)
-- isActive:  Boolean  默认 true
-- createdAt: DateTime 自动
-- updatedAt: DateTime 自动
-- 关联: apiKeys[], logs[]
sql

API Key 表

-- ApiKey 模型
-- id:        String    主键 cuid
-- key:       String    唯一,格式 sk-xxxxxxxxxxxxxxxx
-- name:      String    备注名
-- userId:    String    外键 -> User
-- rateLimit: Int       默认 60,每分钟最大请求数
-- enabled:   Boolean   默认 true
-- models:    String[]  默认空,允许的模型(空=不限)
-- expiresAt: DateTime? 过期时间
-- totalUsed: Int       默认 0,累计请求数
-- createdAt: DateTime  自动
-- 索引: key, userId
-- 关联: logs[]
sql

请求日志表

-- RequestLog 模型
-- id:           String   主键 cuid
-- apiKeyId:     String   外键 -> ApiKey
-- userId:       String   外键 -> User
-- model:        String   请求的模型名
-- provider:     String   openai / anthropic / ...
-- endpoint:     String   /v1/chat/completions
-- promptTokens: Int      默认 0
-- outputTokens: Int      默认 0
-- totalTokens:  Int      默认 0
-- duration:     Int      默认 0,响应耗时(ms)
-- status:       Int      HTTP 状态码
-- ip:           String?  可选
-- error:        String?  错误信息
-- createdAt:    DateTime 自动
-- 索引: [userId, createdAt], [apiKeyId, createdAt], [createdAt]
sql

上游渠道表

-- Channel 模型
-- id:        String        主键 cuid
-- name:      String        渠道名称
-- provider:  String        openai / anthropic / custom
-- baseUrl:   String        上游 API 基础 URL
-- apiKey:    String        上游 API Key(加密存储)
-- models:    String[]      支持的模型列表
-- priority:  Int           默认 0,优先级(越高越优先)
-- weight:    Int           默认 1,权重(同优先级加权轮询)
-- enabled:   Boolean       默认 true
-- maxRetry:  Int           默认 2
-- timeout:   Int           默认 60000,超时时间(ms)
-- status:    ChannelStatus 默认 ACTIVE (ACTIVE / DEGRADED / DISABLED)
-- createdAt: DateTime      自动
-- updatedAt: DateTime      自动
sql

三、后端 API 接口设计

3.1 认证接口

  • POST /api/auth/register — 注册
  • POST /api/auth/login — 登录,返回 JWT
  • GET /api/auth/profile — 获取当前用户信息
  • POST /api/auth/refresh — 刷新 Token

3.2 API Key 管理

  • GET /api/keys — 获取我的 Key 列表
  • POST /api/keys — 创建新 Key
  • PATCH /api/keys/:id — 更新 Key
  • DELETE /api/keys/:id — 删除 Key

3.3 AI 代理接口(核心)

  • POST /v1/chat/completions — 兼容 OpenAI Chat 格式
  • POST /v1/completions — 兼容 OpenAI Completions
  • POST /v1/embeddings — 嵌入接口
  • GET /v1/models — 返回可用模型列表

鉴权方式:Authorization: Bearer sk-xxxxxxxx(使用平台生成的 API Key)

3.4 渠道管理(Admin)

  • GET /api/channels — 渠道列表
  • POST /api/channels — 添加渠道
  • PATCH /api/channels/:id — 更新渠道
  • DELETE /api/channels/:id — 删除渠道
  • POST /api/channels/:id/test — 测试渠道连通性

3.5 统计与日志接口

  • GET /api/stats/overview — 总览(今日请求数/Token/费用)
  • GET /api/stats/daily — 每日趋势(近 30 天)
  • GET /api/stats/models — 按模型统计
  • GET /api/logs — 调用日志(分页+筛选)

四、核心模块 — Proxy 代理转发

这是整个平台最核心的模块,负责接收客户端请求并转发到上游 AI 服务。

4.1 请求处理流程

客户端请求
    |
    v
[API Key 鉴权] --- 无效 ---> 401 Unauthorized
    | 有效
    v
[限流检查] --- 超限 ---> 429 Too Many Requests
    | 通过
    v
[模型权限检查] --- 无权 ---> 403 Forbidden
    | 通过
    v
[选择上游 Channel]
    |  加权轮询 + 优先级
    v
[构建上游请求]
    |  模型映射、格式转换
    v
[发送请求到上游]
    |
    |-- 非流式 ---> 等待响应 ---> 解析 token 用量 ---> 返回
    |
    +-- 流式 SSE ---> 逐块透传 ---> 累计 token ---> 结束后记录
    |
    v
[记录日志] -- 异步写入数据库
text

4.2 Proxy Controller

// proxy.controller.ts
// @Controller('v1')
class ProxyController {
  // POST /v1/chat/completions
  // @UseGuards(ApiKeyAuthGuard)
  async chatCompletions(req, res) {
    const isStream = req.body.stream === true;

    if (isStream) {
      // SSE 流式透传
      res.setHeader('Content-Type', 'text/event-stream');
      res.setHeader('Cache-Control', 'no-cache');
      res.setHeader('Connection', 'keep-alive');
      await this.proxyService.streamProxy(req, res);
    } else {
      const result = await this.proxyService.proxy(req);
      res.json(result);
    }
  }

  // GET /v1/models
  // @UseGuards(ApiKeyAuthGuard)
  async listModels() {
    return this.proxyService.listModels();
  }
}
javascript

4.3 流式代理核心逻辑

// proxy.service.ts
class ProxyService {
  // 选择上游渠道(加权轮询)
  async selectChannel(model) {
    const channels = await this.getActiveChannels(model);
    return this.weightedRoundRobin(channels);
  }

  // 流式代理
  async streamProxy(req, res) {
    const channel = await this.selectChannel(req.body.model);
    const startTime = Date.now();

    const upstream = await fetch(
      channel.baseUrl + '/v1/chat/completions',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + decrypt(channel.apiKey),
        },
        body: JSON.stringify(req.body),
      },
    );

    // 逐块透传 SSE
    const reader = upstream.body.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const result = await reader.read();
      if (result.done) break;

      const chunk = decoder.decode(result.value, { stream: true });
      res.write(chunk);
    }

    res.end();

    // 异步记录日志
    this.logService.create({ /* ... */ });
  }
}
javascript

4.4 API Key 鉴权 Guard

// api-key-auth.guard.ts
// @Injectable()
class ApiKeyAuthGuard {
  async canActivate(context) {
    const request = context.switchToHttp().getRequest();
    const authHeader = request.headers['authorization'];

    if (!authHeader || !authHeader.startsWith('Bearer sk-')) {
      throw new UnauthorizedException('Invalid API Key');
    }

    const key = authHeader.replace('Bearer ', '');
    const apiKey = await this.apiKeyService.validate(key);

    if (!apiKey || !apiKey.enabled) {
      throw new UnauthorizedException('API Key disabled or not found');
    }

    if (apiKey.expiresAt && apiKey.expiresAt < new Date()) {
      throw new UnauthorizedException('API Key expired');
    }

    // 挂载到 request 上供后续使用
    request.apiKey = apiKey;
    request.user = apiKey.user;

    return true;
  }
}
javascript

五、前端页面设计

5.1 页面路由

  • / — Landing 首页(公开)
  • /login — 登录(公开)
  • /register — 注册(公开)
  • /overview — 数据总览(需登录)
  • /keys — API Key 管理(需登录)
  • /logs — 调用日志(需登录)
  • /playground — API 测试(需登录)
  • /channels — 渠道管理(Admin)
  • /settings — 个人设置(需登录)

5.2 Dashboard 布局

整个后台使用经典的侧边栏 + 主内容区布局,移动端侧边栏收起为 Sheet 抽屉。

5.3 Overview 总览页

总览页包含以下组件:

  • 统计卡片:今日请求数、今日 Token 用量、活跃 Key 数、可用渠道数
  • 趋势折线图:近 30 天请求量变化(Recharts LineChart)
  • 模型用量饼图:各模型调用占比(Recharts PieChart)
  • 最近请求列表:实时展示最近调用记录

5.4 API Keys 管理页

列表展示所有 Key,支持创建、复制、启停和删除操作。Key 只在创建时展示完整值,之后仅显示 sk-***...后4位。

5.5 Playground 测试页

左侧为参数配置面板(模型选择、Temperature、Max Tokens、Key 选择),右侧为对话界面,支持流式输出。

5.6 API 请求封装

// lib/api.ts
const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

async function fetchAPI(path, options) {
  const token = getToken();
  const headers = {
    'Content-Type': 'application/json',
  };
  if (token) {
    headers['Authorization'] = 'Bearer ' + token;
  }
  if (options && options.headers) {
    Object.assign(headers, options.headers);
  }

  const res = await fetch(BASE_URL + path, {
    ...options,
    headers,
  });

  if (!res.ok) {
    const error = await res.json().catch(function() { return {}; });
    throw new ApiError(res.status, error.message);
  }

  return res.json();
}
javascript

六、分阶段开发步骤

Phase 1:项目初始化(Day 1)

# 创建项目 & 初始化 monorepo
mkdir ai-relay && cd ai-relay
pnpm init

# 创建 Next.js 前端
cd packages
pnpm create next-app@latest web -- \
  --typescript --tailwind --eslint --app --src-dir

# 安装 shadcn/ui
cd web
pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button card input label \
  table dialog dropdown-menu toast tabs badge

# 安装前端依赖
pnpm add swr recharts lucide-react js-cookie

# 创建 NestJS 后端
cd ../
npx @nestjs/cli new server --package-manager pnpm --skip-git

# 安装后端依赖
cd server
pnpm add @nestjs/passport passport passport-jwt @nestjs/jwt
pnpm add @nestjs/config @nestjs/throttler
pnpm add @prisma/client bcryptjs class-validator class-transformer
pnpm add nanoid

# 初始化 Prisma
npx prisma init
bash

Phase 2:后端核心开发(Day 2-4)

按顺序开发:Prisma Schema -> Auth -> User -> ApiKey -> Channel -> Proxy -> Log -> Stats

  1. Prisma Schema — 编写数据模型,执行 prisma migrate dev --name init
  2. Auth 模块 — 注册/登录,JWT 签发与校验
  3. API Key 模块 — 生成 sk- + nanoid(48),CRUD + 启停
  4. Channel 模块 — 上游渠道管理,API Key 使用 AES 加密存储
  5. Proxy 模块(核心)— API Key 鉴权、模型路由、加权轮询、流式透传、失败重试
  6. Log + Stats 模块 — Interceptor 自动采集日志,SQL 聚合统计

Phase 3:前端开发(Day 5-7)

按顺序开发:Layout -> Auth -> Overview -> Keys -> Logs -> Playground -> Settings

Phase 4:部署配置(Day 8)

后端使用 Docker Compose 部署,前端部署到 Vercel。


七、部署方案

后端 Dockerfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN npx prisma generate
RUN pnpm build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/package.json ./
EXPOSE 3001
CMD ["node", "dist/main.js"]
dockerfile

Docker Compose

version: '3.8'
services:
  api:
    build: ./packages/server
    ports:
      - '3001:3001'
    environment:
      DATABASE_URL: postgresql://relay:password@db:5432/relay
      JWT_SECRET: your-jwt-secret-here
      ENCRYPTION_KEY: your-aes-key-here
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: relay
      POSTGRES_PASSWORD: password
      POSTGRES_DB: relay
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

volumes:
  pgdata:
yaml

Nginx 配置(SSE 支持)

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # SSE 关键配置
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 300s;
    }
}
nginx

部署命令

# 在服务器上
git clone your-repo && cd your-repo

# 启动后端
docker compose up -d

# 执行数据库迁移
docker compose exec api npx prisma migrate deploy

# 配置 SSL
sudo certbot --nginx -d api.yourdomain.com
bash

前端连接 Git 仓库后在 Vercel 部署,设置 Root Directory 为 packages/web,环境变量 NEXT_PUBLIC_API_URL 指向后端地址。


八、环境变量

后端 .env

# 数据库
DATABASE_URL="postgresql://relay:password@localhost:5432/relay"

# JWT
JWT_SECRET="your-super-secret-jwt-key"
JWT_EXPIRES_IN="7d"
JWT_REFRESH_EXPIRES_IN="30d"

# 加密(上游 API Key 加密存储)
ENCRYPTION_KEY="32-byte-hex-string-for-aes-256"

# Redis
REDIS_URL="redis://localhost:6379"

# 服务
PORT=3001
NODE_ENV=production
bash

前端 .env.local

NEXT_PUBLIC_API_URL=https://api.yourdomain.com
bash

九、后续优化路线

  1. Redis 缓存 — API Key 验证缓存,减少数据库查询
  2. 模型映射 — Claude 格式与 OpenAI 格式自动转换
  3. 计费系统 — 按 token 消耗扣费/充值
  4. 渠道健康检查 — 定时测试,自动禁用故障渠道
  5. Admin 后台 — 用户管理、全局统计、系统配置
  6. 多租户 — 支持多用户独立管理自己的渠道
  7. Webhook 告警 — 余额不足/异常通知
  8. 速率限制升级 — 滑动窗口 + 令牌桶