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

April 9, 2026

技术栈与部署方案

  • 前端: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,核心有四张表:UserApiKeyRequestLogChannel

用户表

-- 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. 速率限制升级 — 滑动窗口 + 令牌桶