diff --git a/CLAUDE.md b/CLAUDE.md
index f0385a574bc9..d683dbf03d2a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,6 +4,104 @@
This is an AI API gateway/proxy built with Go. It aggregates 40+ upstream AI providers (OpenAI, Claude, Gemini, Azure, AWS Bedrock, etc.) behind a unified API, with user management, billing, rate limiting, and an admin dashboard.
+## Common Development Commands
+
+### Backend (Go)
+
+```bash
+# Run backend development server
+go run main.go
+
+# Run with debug mode
+GIN_MODE=debug DEBUG=true go run main.go
+
+# Run with custom port
+PORT=8080 go run main.go
+
+# Run tests
+go test ./...
+
+# Run specific test
+go test ./relay/channel -v -run TestClaude
+
+# Run specific test file
+go test ./relay/channel/api_request_test.go -v
+```
+
+### Frontend (React + Vite)
+
+```bash
+# Install dependencies (uses Bun)
+cd web && bun install
+
+# Run development server (runs on http://localhost:5173)
+cd web && bun run dev
+
+# Build for production
+cd web && bun run build
+
+# Run ESLint
+cd web && bun run eslint
+
+# Fix ESLint issues
+cd web && bun run eslint:fix
+
+# Format code with Prettier
+cd web && bun run lint:fix
+
+# i18n tools
+cd web && bun run i18n:extract # Extract new translation keys
+cd web && bun run i18n:sync # Sync translations
+cd web && bun run i18n:lint # Lint translation files
+```
+
+### Full Stack Development
+
+```bash
+# Using makefile
+make build-frontend # Build frontend assets
+make start-backend # Start backend server
+make all # Build frontend + start backend (parallel)
+
+# Manually (two terminals)
+# Terminal 1: Frontend dev server
+cd web && bun run dev
+
+# Terminal 2: Backend dev server
+go run main.go
+```
+
+### Docker Development
+
+```bash
+# Start full stack with docker-compose
+docker-compose up -d
+
+# View logs
+docker-compose logs -f
+
+# Stop services
+docker-compose down
+```
+
+### Environment Configuration
+
+Create a `.env` file based on `.env.example`:
+
+```bash
+cp .env.example .env
+# Then edit .env with your settings
+```
+
+Key environment variables:
+- `SQL_DSN` - Database connection string (default: SQLite in `/data`)
+- `REDIS_CONN_STRING` - Redis connection for cache
+- `SESSION_SECRET` - Required for multi-machine deployments
+- `CRYPTO_SECRET` - Required when using Redis
+- `STREAMING_TIMEOUT` - Streaming timeout in seconds (default: 300)
+- `DEBUG` - Enable debug mode
+- `GIN_MODE` - Gin mode (`debug` or `release`)
+
## Tech Stack
- **Backend**: Go 1.22+, Gin web framework, GORM v2 ORM
@@ -24,6 +122,8 @@ service/ — Business logic
model/ — Data models and DB access (GORM)
relay/ — AI API relay/proxy with provider adapters
relay/channel/ — Provider-specific adapters (openai/, claude/, gemini/, aws/, etc.)
+ relay/common/ — Relay utilities and RelayInfo struct
+ relay/helper/ — Stream scanner, billing helpers, etc.
middleware/ — Auth, rate limiting, CORS, logging, distribution
setting/ — Configuration management (ratio, model, operation, system, performance)
common/ — Shared utilities (JSON, crypto, Redis, env, rate-limit, etc.)
@@ -37,6 +137,26 @@ web/ — React frontend
web/src/i18n/ — Frontend internationalization (i18next, zh/en/fr/ru/ja/vi)
```
+### Relay System
+
+The relay system is the core of the gateway, handling requests from clients and forwarding them to upstream AI providers:
+
+- **Adaptor Interface** (`relay/channel/adapter.go`): All channel adapters implement this interface with methods like `Init`, `GetRequestURL`, `SetupRequestHeader`, `ConvertOpenAIRequest`, `DoRequest`, `DoResponse`, etc.
+- **RelayInfo** (`relay/common/relay_info.go`): Contains all context for a single relay request - user/token info, channel metadata, pricing, billing session, request conversion chain.
+- **Request Format Conversion**: Supports multiple relay formats - OpenAI, Claude Messages, Gemini, Responses, Rerank, Embedding, Audio, Image, Realtime, Task (async). Adapters convert between formats as needed.
+- **Stream Handling**: `relay/helper/stream_scanner.go` handles streaming responses with configurable buffer size.
+- **TaskAdaptor Interface**: For async task-based providers (Midjourney, Suno, etc.), implements polling-based task lifecycle management with billing hooks.
+
+### Request Flow
+
+1. Router receives request → Middleware (auth, rate limit, distribution)
+2. Controller parses request → Validates token/channel
+3. Service layer handles business logic (quota, billing)
+4. Relay layer calls appropriate channel adaptor
+5. Adaptor converts request format and forwards to upstream
+6. Response is converted back and sent to client
+7. Billing session settles quota (pre-consume -> adjust delta -> final settle)
+
## Internationalization (i18n)
### Backend (`i18n/`)
diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
new file mode 100644
index 000000000000..63477b6a7938
--- /dev/null
+++ b/docker-compose-dev.yml
@@ -0,0 +1,65 @@
+# new-api Docker Compose for Local Development
+#
+# Quick Start:
+# docker-compose -f docker-compose-dev.yml up -d
+#
+# Purpose:
+# This file contains only external services (database, cache) needed for development.
+# The main new-api application runs on your local machine.
+#
+# Services:
+# - postgres: PostgreSQL 17 database
+# - redis: Redis cache server
+# - redis-commander: Redis web UI (optional, for viewing cache)
+# - pgadmin4: PostgreSQL web UI (optional, for database management)
+#
+version: '3.8'
+name: new-api-dev
+
+services:
+ postgres:
+ image: postgres:17-alpine
+ container_name: new-api-postgres-dev
+ restart: unless-stopped
+ environment:
+ POSTGRES_USER: root
+ POSTGRES_PASSWORD: 123456
+ POSTGRES_DB: new-api
+ POSTGRES_INITDB_ARGS: "-E UTF8"
+ TZ: Asia/Shanghai
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U newapi"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+ # Redis Cache Server
+ redis:
+ image: bitnamilegacy/redis:8.0
+ container_name: new-api-redis-dev
+ restart: unless-stopped
+ environment:
+ - ALLOW_EMPTY_PASSWORD=yes
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis_data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 5s
+ timeout: 3s
+ retries: 5
+
+volumes:
+ postgres_data:
+ driver: local
+ redis_data:
+ driver: local
+
+networks:
+ default:
+ name: new-api-dev-network
diff --git "a/docs/learn_docs/01-\351\241\271\347\233\256\346\246\202\350\277\260.md" "b/docs/learn_docs/01-\351\241\271\347\233\256\346\246\202\350\277\260.md"
new file mode 100644
index 000000000000..7b848c280adf
--- /dev/null
+++ "b/docs/learn_docs/01-\351\241\271\347\233\256\346\246\202\350\277\260.md"
@@ -0,0 +1,140 @@
+# 项目概述
+
+## 什么是 new-api
+
+new-api 是一个基于 Go 语言的下一代大模型网关和 AI 资产管理系统。它将 40+ 个上游 AI 提供商(OpenAI、Claude、Gemini、Azure、AWS Bedrock 等)整合在一个统一的 API 接口下,提供用户管理、计费、限流和管理后台等功能。
+
+## 核心功能
+
+### 1. 多提供商聚合
+```mermaid
+graph TD
+ Client[客户端请求] --> Gateway[new-api 网关]
+ Gateway --> OpenAI[OpenAI]
+ Gateway --> Claude[Claude]
+ Gateway --> Gemini[Gemini]
+ Gateway --> Azure[Azure]
+ Gateway --> AWS[AWS Bedrock]
+ Gateway --> DeepSeek[DeepSeek]
+ Gateway --> Moonshot[Moonshot]
+ Gateway --> MiniMax[MiniMax]
+ Gateway --> Ali[通义千问]
+ Gateway --> Zhipu[智谱]
+ Gateway --> Others[其他30+提供商]
+```
+
+### 2. 多 API 格式支持
+- **OpenAI 兼容格式** - `/v1/chat/completions`
+- **OpenAI Responses 格式** - `/v1/responses`
+- **Claude Messages 格式** - `/v1/messages`
+- **Gemini 格式** - `/v1beta/models/...`
+- **实时对话格式** - `/v1/realtime` (WebSocket)
+
+### 3. 统一计费系统
+支持多种计费模式:
+- 按使用量计费(tokens)
+- 按时间计费(音频/视频)
+- 按图片数量计费
+- 按分辨率/时长组合计费
+- 任务预付费 + 完成结算
+
+### 4. 智能路由策略
+- **通道加权随机** - 按权重分配请求
+- **自动重试** - 失败时自动切换通道
+- **用户级模型限流** - 防止单用户占用过多资源
+- **通道亲和性** - 会话级别的通道粘性
+
+### 5. 异步任务系统
+支持异步任务提供商:
+- Midjourney - 图像生成
+- Suno - 音乐生成
+- Sora/Kling - 视频生成
+- Jimeng - 即梦绘画
+
+## 技术栈
+
+| 层级 | 技术选型 |
+|-------|----------|
+| **后端框架** | Go 1.25.1, Gin Web Framework |
+| **数据库** | SQLite, MySQL >= 5.7.8, PostgreSQL >= 9.6 |
+| **缓存** | Redis (go-redis) + 内存缓存 |
+| **ORM** | GORM v2 |
+| **认证** | JWT, WebAuthn/Passkeys, OAuth (GitHub, Discord, OIDC) |
+| **前端框架** | React 18, Vite |
+| **UI 组件库** | Semi Design (@douyinfe/semi-ui) |
+| **前端包管理器** | Bun |
+| **国际化** | go-i18n (后端), i18next (前端) |
+
+## 项目定位
+
+### 目标用户
+- 企业用户 - 需要 API 聚合和统一计费
+- 开发者 - 需要简单接入多个 AI 模型
+- 分发商 - 需要管理和计费终端用户
+
+### 核心价值
+1. **统一接入** - 一次接入 40+ AI 提供商
+2. **成本可控** - 灵活的计费策略和限流
+3. **高可用** - 自动重试和故障转移
+4. **易管理** - 完整的用户、通道、日志管理后台
+
+## 项目结构概览
+
+```
+new-api/
+├── router/ # HTTP 路由层
+├── controller/ # 请求处理层
+├── service/ # 业务逻辑层
+├── model/ # 数据模型和数据库访问
+├── relay/ # AI API 转发核心
+│ └── channel/ # 各提供商适配器
+├── middleware/ # 中间件(认证、限流等)
+├── setting/ # 配置管理
+├── common/ # 通用工具
+├── dto/ # 数据传输对象
+├── constant/ # 常量定义
+├── types/ # 类型定义
+├── i18n/ # 后端国际化
+├── oauth/ # OAuth 提供商实现
+├── pkg/ # 内部包
+└── web/ # React 前端
+```
+
+## 快速上手
+
+### 1. 环境要求
+- Go 1.25.1+
+- Node.js 18+ (前端开发)
+- Bun (前端包管理)
+- SQLite/MySQL/PostgreSQL
+
+### 2. 启动开发环境
+```bash
+# 后端
+go run main.go
+
+# 前端(在 web/ 目录)
+cd web
+bun install
+bun run dev
+```
+
+### 3. 核心配置
+```bash
+# 数据库连接
+SQL_DSN="user:password@tcp(localhost:3306)/newapi"
+
+# Redis 连接(可选)
+REDIS_CONN_STRING="redis://localhost:6379"
+
+# 会话密钥(多机部署必填)
+SESSION_SECRET="your-secret-key"
+
+# 加密密钥(Redis 必填)
+CRYPTO_SECRET="your-crypto-secret"
+```
+
+## 后续阅读
+
+- [02-架构详解](./02-架构详解.md) - 深入了解系统架构
+- [03-二次开发指南](./03-二次开发指南.md) - 学习如何扩展功能
diff --git "a/docs/learn_docs/02-\346\236\266\346\236\204\350\257\246\350\247\243.md" "b/docs/learn_docs/02-\346\236\266\346\236\204\350\257\246\350\247\243.md"
new file mode 100644
index 000000000000..685466d97e5e
--- /dev/null
+++ "b/docs/learn_docs/02-\346\236\266\346\236\204\350\257\246\350\247\243.md"
@@ -0,0 +1,594 @@
+# 架构详解
+
+## 整体架构
+
+new-api 采用经典的分层架构:**Router → Controller → Service → Model**
+
+```mermaid
+graph TB
+ subgraph "客户端层"
+ Client[Web 客户端]
+ API[API 客户端]
+ end
+
+ subgraph "路由层 (router/)"
+ Router[Gin Router]
+ API_Router[/v1/api]
+ Relay_Router[/v1/relay]
+ Dashboard_Router[/dashboard]
+ Web_Router[前端路由]
+ end
+
+ subgraph "中间件层 (middleware/)"
+ Auth[认证中间件]
+ RateLimit[限流中间件]
+ Distribute[分发中间件]
+ CORS[CORS 中间件]
+ Logger[日志中间件]
+ end
+
+ subgraph "控制器层 (controller/)"
+ RelayCtrl[Relay 控制器]
+ UserCtrl[User 控制器]
+ ChannelCtrl[Channel 控制器]
+ TokenCtrl[Token 控制器]
+ end
+
+ subgraph "业务逻辑层 (service/)"
+ ChannelSelect[通道选择服务]
+ Billing[计费服务]
+ Convert[格式转换服务]
+ BillingSession[计费会话服务]
+ TokenCounter[Token 计数服务]
+ end
+
+ subgraph "数据层 (model/)"
+ User[User 模型]
+ Channel[Channel 模型]
+ Token[Token 模型]
+ Log[Log 模型]
+ Task[Task 模型]
+ end
+
+ subgraph "转发层 (relay/)"
+ Adaptor[适配器接口]
+ OpenAI[OpenAI 适配器]
+ Claude[Claude 适配器]
+ Gemini[Gemini 适配器]
+ TaskAdaptor[任务适配器]
+ end
+
+ subgraph "存储层"
+ DB[(数据库)]
+ Redis[(Redis 缓存)]
+ Disk[磁盘缓存]
+ end
+
+ Client --> Router
+ API --> Router
+
+ Router --> CORS
+ CORS --> Logger
+ Logger --> Auth
+ Auth --> RateLimit
+ RateLimit --> Distribute
+
+ Distribute --> RelayCtrl
+ Distribute --> UserCtrl
+ Distribute --> ChannelCtrl
+ Distribute --> TokenCtrl
+
+ RelayCtrl --> Convert
+ RelayCtrl --> ChannelSelect
+
+ ChannelSelect --> Channel
+ Billing --> User
+ Billing --> Token
+ Billing --> Log
+
+ Convert --> Adaptor
+ Adaptor --> OpenAI
+ Adaptor --> Claude
+ Adaptor --> Gemini
+
+ OpenAI --> DB
+ Claude --> DB
+ Gemini --> DB
+
+ DB --> Redis
+ DB --> Disk
+```
+
+## 请求处理流程
+
+### 1. AI 请求转发流程
+
+```mermaid
+sequenceDiagram
+ participant C as 客户端
+ participant R as Router
+ participant M as Middleware
+ participant Ctrl as Controller
+ participant S as Service
+ participant R as Relay
+ participant P as 提供商
+ participant D as 数据库
+ participant Cache as Redis
+
+ C->>R: POST /v1/chat/completions
+ R->>M: CORS 检查
+ M->>M: 请求 ID 生成
+ M->>Cache: 限流检查
+ M->>D: Token 认证
+ D-->>M: Token 信息
+ M->>Ctrl: Relay(c, format)
+ Ctrl->>S: 通道选择
+ S->>D: 获取可用通道
+ D-->>S: 通道列表
+ S->>S: 负载均衡
+ S->>R: RelayHelper(c, info)
+ R->>R: 格式转换
+ R->>R: 请求构建
+ R->>P: HTTP 请求
+ P-->>R: 响应
+ R->>R: 响应转换
+ R->>S: Token 计数
+ S->>D: 更新配额
+ R-->>C: 响应数据
+```
+
+### 2. 通道选择策略
+
+```mermaid
+graph TD
+ Start[开始选择通道] --> Filter{通道过滤}
+ Filter --> |有指定通道| Direct[直接使用指定通道]
+ Filter --> |无指定通道| CheckCache{缓存检查}
+
+ CheckCache --> |有缓存亲和性| UseCache[使用缓存通道]
+ CheckCache --> |无缓存| GetAvailable[获取可用通道]
+
+ GetAvailable --> CheckWeight{是否有权重配置}
+ CheckWeight --> |有权重| Weighted[加权随机选择]
+ CheckWeight --> |无权重| Random[随机选择]
+
+ Direct --> Verify{通道可用性检查}
+ UseCache --> Verify
+ Weighted --> Verify
+ Random --> Verify
+
+ Verify --> |可用| Selected[选中通道]
+ Verify --> |不可用| Retry{重试次数 < 最大值?}
+ Retry --> |是| GetAvailable
+ Retry --> |否| Error[返回错误]
+
+ Selected --> CacheUpdate[更新缓存亲和性]
+ CacheUpdate --> End[返回通道]
+```
+
+## 中间件架构
+
+中间件按以下顺序执行:
+
+```mermaid
+graph LR
+ Request[请求] --> CORS[CORS]
+ CORS --> Decompress[解压缩]
+ Decompress --> BodyCleanup[请求体清理]
+ BodyCleanup --> Stats[统计]
+ Stats --> RouteTag[路由标签]
+ RouteTag --> SystemPerformance[系统性能检查]
+ SystemPerformance --> Auth[认证]
+ Auth --> ModelRateLimit[模型请求限流]
+ ModelRateLimit --> Distribute[分发中间件]
+
+ Distribute --> Controller[控制器]
+
+ Controller --> Distribute
+ Distribute --> Response[响应]
+```
+
+### 关键中间件说明
+
+| 中间件 | 文件 | 功能 |
+|---------|------|------|
+| **CORS** | `cors.go` | 跨域资源共享配置 |
+| **认证** | `auth.go` | Token 和用户认证 |
+| **分发** | `distributor.go` | 通道选择和负载均衡 |
+| **限流** | `rate-limit.go` | 全局请求限流 |
+| **模型限流** | `model-rate-limit.go` | 用户级模型限流 |
+| **性能检查** | `performance.go` | 系统性能保护 |
+| **请求 ID** | `request-id.go` | 生成唯一请求 ID |
+| **i18n** | `i18n.go` | 国际化上下文设置 |
+| **日志** | `logger.go` | 请求/响应日志记录 |
+
+## Relay 转发系统架构
+
+### 适配器接口设计
+
+```mermaid
+classDiagram
+ class Adaptor {
+ +Init(info)
+ +GetRequestURL(info) string
+ +SetupRequestHeader(c, header, info)
+ +ConvertOpenAIRequest(c, info, req)
+ +ConvertClaudeRequest(c, info, req)
+ +ConvertGeminiRequest(c, info, req)
+ +DoRequest(c, info, body)
+ +DoResponse(c, resp, info)
+ +GetModelList() string[]
+ +GetChannelName() string
+ }
+
+ class OpenAIAdaptor {
+ ChannelType
+ ResponseFormat
+ }
+
+ class ClaudeAdaptor {
+ ChannelType
+ ResponseFormat
+ }
+
+ class GeminiAdaptor {
+ ChannelType
+ ResponseFormat
+ }
+
+ Adaptor <|.. OpenAIAdaptor
+ Adaptor <|.. ClaudeAdaptor
+ Adaptor <|.. GeminiAdaptor
+```
+
+### Relay 模式与适配
+
+```mermaid
+graph TB
+ Request[客户端请求] --> Detect{格式检测}
+
+ Detect --> |x-api-key +
anthropic-version| Claude[Claude 格式]
+ Detect --> |x-goog-api-key| Gemini[Gemini 格式]
+ Detect --> |默认| OpenAI[OpenAI 格式]
+
+ Claude --> ClaudeAdaptor[Claude 适配器]
+ Gemini --> GeminiAdaptor[Gemini 适配器]
+ OpenAI --> OpenAIAdaptor[OpenAI 适配器]
+
+ ClaudeAdaptor --> Convert[Claude -> OpenAI
格式转换]
+ GeminiAdaptor --> Convert
+
+ Convert --> Channel{通道类型}
+
+ Channel --> |OpenAI| OAI_Provider[OpenAI 提供商]
+ Channel --> |Claude| Anthropic[Anthropic 提供商]
+ Channel --> |Gemini| Google[Google 提供商]
+ Channel --> |Azure| Azure[Azure 提供商]
+ Channel --> |AWS| AWS[AWS Bedrock]
+ Channel --> |DeepSeek| DeepSeek[DeepSeek 提供商]
+ Channel --> |...| Others[其他提供商]
+
+ OAI_Provider --> ProviderAPI[提供商 API]
+ Anthropic --> ProviderAPI
+ Google --> ProviderAPI
+ Azure --> ProviderAPI
+ AWS --> ProviderAPI
+ DeepSeek --> ProviderAPI
+ Others --> ProviderAPI
+
+ ProviderAPI --> Response[响应]
+ Response --> BackConvert[格式回转]
+ BackConvert --> Client[客户端]
+```
+
+## 数据库架构
+
+### 主要数据表
+
+```mermaid
+erDiagram
+ USER ||--o{ TOKEN : "拥有"
+ USER ||--o{ CHANNEL : "管理"
+ CHANNEL ||--o{ TOKEN : "可用"
+ USER ||--o{ LOG : "产生"
+ TOKEN ||--o{ LOG : "产生"
+ USER ||--o{ TASK : "提交"
+ CHANNEL ||--o{ TASK : "执行"
+ GROUP ||--o{ USER : "包含"
+ GROUP ||--o{ TOKEN : "限制"
+ CHANNEL ||--o{ CHANNEL_AFFINITY : "亲和性"
+
+ USER {
+ int id PK
+ string username
+ string password
+ string role
+ string status
+ int quota
+ datetime created_at
+ }
+
+ CHANNEL {
+ int id PK
+ int type
+ string key
+ string base_url
+ string models
+ int priority
+ int weight
+ string status
+ }
+
+ TOKEN {
+ int id PK
+ string key
+ int user_id FK
+ string name
+ int group_id
+ int quota
+ string models
+ datetime expire_time
+ }
+
+ LOG {
+ int id PK
+ int user_id FK
+ int token_id FK
+ string model
+ int prompt_tokens
+ int completion_tokens
+ datetime created_at
+ }
+
+ TASK {
+ int id PK
+ int user_id FK
+ int channel_id FK
+ string action
+ string status
+ string result
+ datetime created_at
+ }
+
+ GROUP {
+ int id PK
+ string name
+ string models
+ int max_quota
+ }
+
+ CHANNEL_AFFINITY {
+ int id PK
+ int user_id FK
+ int token_id FK
+ int channel_id FK
+ datetime created_at
+ }
+```
+
+## 缓存架构
+
+### 三级缓存设计
+
+```mermaid
+graph LR
+ Request[请求] --> L1[L1: 内存缓存]
+ L1 --> |未命中| L2[L2: Redis 缓存]
+ L2 --> |未命中| L3[L3: 数据库]
+
+ L3 --> L2
+ L2 --> L1
+
+ L1 --> |命中| Response[返回数据]
+
+ subgraph "缓存内容"
+ Channels[通道配置]
+ Models[模型列表]
+ Ratios[计费比例]
+ Users[用户信息]
+ Tokens[Token 信息]
+ end
+
+ L2 -.缓存.-> Channels
+ L2 -.缓存.-> Models
+ L2 -.缓存.-> Ratios
+ L2 -.缓存.-> Users
+ L2 -.缓存.-> Tokens
+```
+
+## 异步任务系统架构
+
+### 任务生命周期
+
+```mermaid
+stateDiagram-v2
+ [*] --> Submitted: 提交任务
+ Submitted --> Queued: 入队
+
+ Queued --> Processing: 开始处理
+ Processing --> Running: 提供商返回任务ID
+
+ Running --> Polling: 开始轮询
+ Polling --> Running: 继续轮询
+ Polling --> Success: 任务成功
+ Polling --> Failed: 任务失败
+ Polling --> Timeout: 超时
+
+ Success --> Billing: 完成结算
+ Failed --> Billing: 失败结算
+ Timeout --> Billing: 超时结算
+
+ Billing --> [*]
+```
+
+### 任务适配器接口
+
+```mermaid
+classDiagram
+ class TaskAdaptor {
+ +Init(info)
+ +ValidateRequestAndSetAction(c, info)
+ +EstimateBilling(c, info)
+ +BuildRequestURL(info)
+ +BuildRequestHeader(c, req, info)
+ +BuildRequestBody(c, info)
+ +DoRequest(c, info, body)
+ +DoResponse(c, resp, info)
+ +FetchTask(baseUrl, key, body)
+ +ParseTaskResult(respBody)
+ +AdjustBillingOnComplete(task, result)
+ +GetModelList()
+ +GetChannelName()
+ }
+
+ class MidjourneyAdapter {
+ ParseAction(path)
+ BuildFormData(params)
+ ParseTaskResult(body)
+ }
+
+ class SunoAdapter {
+ ParseAction(path)
+ BuildRequestJSON(params)
+ ParseTaskResult(body)
+ }
+
+ class VideoAdapter {
+ ParseAction(path)
+ BuildRequestJSON(params)
+ ParseTaskResult(body)
+ }
+
+ TaskAdaptor <|.. MidjourneyAdapter
+ TaskAdaptor <|.. SunoAdapter
+ TaskAdaptor <|.. VideoAdapter
+```
+
+## 计费系统架构
+
+### 计费流程
+
+```mermaid
+sequenceDiagram
+ participant U as 用户
+ participant C as Controller
+ participant S as BillingService
+ participant D as 数据库
+ participant R as Redis
+ participant A as Adapter
+
+ U->>C: 发送请求
+ C->>S: PreCheckBalance()
+ S->>D: 查询用户配额
+ D-->>S: 当前配额
+ S->>R: 执行请求
+
+ R-->>S: 返回使用信息
+ S->>S: CalculateCost()
+
+ alt 实时计费
+ S->>D: 扣除配额
+ S->>R: 记录日志
+ end
+
+ alt 任务计费
+ S->>D: EstimateBilling()
+ S->>D: 预扣配额
+ Note over S,D: 任务完成后结算差额
+ end
+
+ S-->>C: 处理完成
+ C-->>U: 返回响应
+```
+
+### 计费会话管理
+
+```mermaid
+graph TB
+ Start[请求开始] --> CreateSession{会话存在?}
+ CreateSession --> |否| NewSession[创建计费会话]
+ CreateSession --> |是| GetSession[获取现有会话]
+ NewSession --> Cache[缓存会话]
+ GetSession --> Cache
+
+ Cache --> Process[处理请求]
+ Process --> Accumulate[累积使用量]
+
+ Accumulate --> Stream{是否流式?}
+ Stream --> |是| CacheUpdate[更新缓存]
+ Stream --> |否| Finalize[最终结算]
+
+ CacheUpdate --> CheckDone{请求完成?}
+ CheckDone --> |否| Process
+ CheckDone --> |是| Finalize
+
+ Finalize --> UpdateDB[更新数据库]
+ Finalize --> DeleteCache[删除会话缓存]
+ UpdateDB --> End[结束]
+ DeleteCache --> End
+```
+
+## WebSocket 实时通信
+
+### OpenAI Realtime 处理流程
+
+```mermaid
+sequenceDiagram
+ participant C as 客户端
+ participant R as Router
+ participant W as WebSocket Upgrader
+ participant A as OpenAI Realtime Handler
+ participant P as OpenAI Realtime API
+
+ C->>R: GET /v1/realtime
Sec-WebSocket-Protocol
+ R->>W: Upgrade to WebSocket
+ W-->>C: WebSocket Connection
+
+ C->>A: session.update
+ A->>P: 转发配置
+
+ C->>A: input.audio_buffer
音频数据
+ A->>P: 转发音频
+
+ P-->>A: conversation.item.audio
合成音频
+ A-->>C: conversation.item.audio
播放音频
+
+ P-->>A: conversation.item.created
对话消息
+ A-->>C: conversation.item.created
显示消息
+
+ P-->>A: input_audio_buffer.speech_started
+ A-->>C: input_audio_buffer.speech_started
语音开始
+
+ C->>A: input_audio_buffer.speech_stopped
+ A->>P: 转发语音停止
+```
+
+## 错误处理架构
+
+```mermaid
+graph TB
+ Request[请求] --> Try{尝试执行}
+ Try --> Success{成功?}
+ Success --> |是| Response[返回响应]
+ Success --> |否| ErrorType{错误类型}
+
+ ErrorType --> |系统错误| SystemError[系统内部错误]
+ ErrorType --> |上游错误| UpstreamError[上游 API 错误]
+ ErrorType --> |认证错误| AuthError[认证/授权错误]
+ ErrorType --> |限流错误| RateError[限流错误]
+
+ SystemError --> Log500[记录 500 日志]
+ UpstreamError --> Wrap[包装为统一错误格式]
+ AuthError --> Wrap
+ RateError --> Wrap
+
+ Wrap --> ResponseFormat{响应格式}
+ ResponseFormat --> |OpenAI| OAI_Format[OpenAI 错误格式]
+ ResponseFormat --> |Claude| Claude_Format[Claude 错误格式]
+ ResponseFormat --> |Gemini| Gemini_Format[Gemini 错误格式]
+
+ Log500 --> OAI_Format
+ OAI_Format --> Client[返回客户端]
+ Claude_Format --> Client
+ Gemini_Format --> Client
+```
diff --git "a/docs/learn_docs/03-\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/learn_docs/03-\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md"
new file mode 100644
index 000000000000..4d1703576790
--- /dev/null
+++ "b/docs/learn_docs/03-\344\272\214\346\254\241\345\274\200\345\217\221\346\214\207\345\215\227.md"
@@ -0,0 +1,744 @@
+# 二次开发指南
+
+## 目录
+
+1. [添加新的 AI 提供商通道](#添加新的-ai-提供商通道)
+2. [添加新的异步任务提供商](#添加新的异步任务提供商)
+3. [添加新的 API 路由](#添加新的-api-路由)
+4. [扩展计费系统](#扩展计费系统)
+5. [添加中间件](#添加中间件)
+6. [自定义配置选项](#自定义配置选项)
+7. [测试指南](#测试指南)
+
+---
+
+## 添加新的 AI 提供商通道
+
+### 步骤 1: 创建适配器目录
+
+在 `relay/channel/` 下创建新的提供商目录,例如 `relay/channel/myprovider/`
+
+```bash
+mkdir relay/channel/myprovider
+touch relay/channel/myprovider/adaptor.go
+touch relay/channel/myprovider/constants.go
+touch relay/channel/myprovider/dto.go
+```
+
+### 步骤 2: 定义常量和 DTO
+
+**constants.go**
+```go
+package myprovider
+
+const (
+ ChannelName = "MyProvider"
+ // 添加模型列表
+ ModelGPT4 = "gpt-4"
+ ModelGPT35Turbo = "gpt-3.5-turbo"
+)
+```
+
+**dto.go** - 定义提供商特定的请求/响应结构
+```go
+package myprovider
+
+type MyProviderRequest struct {
+ Model string `json:"model"`
+ Prompt string `json:"prompt"`
+ Stream bool `json:"stream"`
+ // ... 其他提供商特定字段
+}
+
+type MyProviderResponse struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ Created int64 `json:"created"`
+ Model string `json:"model"`
+ Choices []struct {
+ Index int `json:"index"`
+ Message struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+ } `json:"message"`
+ FinishReason string `json:"finish_reason"`
+ } `json:"choices"`
+ Usage struct {
+ PromptTokens int `json:"prompt_tokens"`
+ CompletionTokens int `json:"completion_tokens"`
+ TotalTokens int `json:"total_tokens"`
+ } `json:"usage"`
+}
+```
+
+### 步骤 3: 实现适配器接口
+
+**adaptor.go** - 实现完整的适配器接口
+
+```go
+package myprovider
+
+import (
+ "github.com/QuantumNous/new-api/dto"
+ "github.com/QuantumNous/new-api/relay/channel"
+ relaycommon "github.com/QuantumNous/new-api/relay/common"
+ "github.com/QuantumNous/new-api/types"
+
+ "github.com/gin-gonic/gin"
+ "net/http"
+ "io"
+)
+
+type Adaptor struct {
+ ChannelType int
+ ResponseFormat string
+}
+
+func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
+ a.ChannelType = info.ChannelType
+}
+
+func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
+ return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, "/v1/chat/completions", info.ChannelType), nil
+}
+
+func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *relaycommon.RelayInfo) error {
+ header.Set("Authorization", "Bearer "+info.ApiKey)
+ header.Set("Content-Type", "application/json")
+ return nil
+}
+
+func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
+ // 将 OpenAI 格式转换为本提供商格式
+ myRequest := MyProviderRequest{
+ Model: info.UpstreamModelName,
+ Prompt: request.Messages, // 简化示例
+ Stream: info.IsStream,
+ }
+ return myRequest, nil
+}
+
+func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
+ return channel.DoApiRequest(a, c, info, requestBody)
+}
+
+func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) {
+ // 处理响应、提取使用信息
+ var myResp MyProviderResponse
+ if err := c.ShouldBindJSON(&myResp); err != nil {
+ return nil, common.ErrorWrapper(err)
+ }
+ // 返回使用量供计费
+ return myResp.Usage, nil
+}
+
+// 实现其他必需的接口方法...
+```
+
+### 步骤 4: 注册通道类型
+
+1. 在 `constant/channel_type.go` 添加通道常量:
+```go
+const (
+ ChannelTypeOpenAI = 1
+ // ...
+ ChannelTypeMyProvider = 50 // 使用新的 ID
+)
+```
+
+2. 在 `constant/model.go` 的 `GetChannelTypeStrings()` 添加映射:
+```go
+case ChannelTypeMyProvider:
+ return "myprovider"
+```
+
+3. 在 `relay/adaptor.go` 的 `GetAdaptor()` 函数中注册:
+```go
+case constant.ChannelTypeMyProvider:
+ return &myprovider.Adaptor{}
+```
+
+### 步骤 5: 添加 StreamOptions 支持(如适用)
+
+如果提供商支持流式选项,在 `relay/common/relay_info.go` 的 `streamSupportedChannels` 中添加:
+```go
+var streamSupportedChannels = map[int]bool{
+ constant.ChannelTypeOpenAI: true,
+ // ...
+ constant.ChannelTypeMyProvider: true, // 添加这里
+}
+```
+
+并在适配器中添加 StreamOptions 支持:
+```go
+func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
+ if info.SupportStreamOptions && info.IsStream {
+ request.StreamOptions = &dto.StreamOptions{
+ IncludeUsage: true,
+ }
+ }
+ // ...
+}
+```
+
+### 步骤 6: 添加模型列表
+
+在前端配置中添加模型映射,或实现 `GetModelList()` 返回支持模型列表。
+
+---
+
+## 添加新的异步任务提供商
+
+异步任务提供商(如 Suno、Sora、Kling)与同步提供商不同,需要:
+
+1. 提交任务到上游
+2. 获取任务 ID
+3. 轮询任务状态
+4. 返回最终结果
+
+### 步骤 1: 创建任务适配器
+
+在 `relay/channel/task/` 下创建目录和文件:
+
+```bash
+mkdir relay/channel/task/mytask
+touch relay/channel/task/mytask/adaptor.go
+touch relay/channel/task/mytask/constants.go
+```
+
+**adaptor.go** 实现 `TaskAdaptor` 接口:
+
+```go
+package mytask
+
+import (
+ "github.com/QuantumNous/new-api/dto"
+ "github.com/QuantumNous/new-api/model"
+ relaycommon "github.com/QuantumNous/new-api/relay/common"
+
+ "github.com/gin-gonic/gin"
+ "io"
+ "net/http"
+)
+
+type Adaptor struct {
+ ChannelType int
+}
+
+func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
+ a.ChannelType = info.ChannelType
+}
+
+// 验证请求并设置动作类型
+func (a *Adaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) *dto.TaskError {
+ // 验证请求参数
+ if info.Prompt == "" {
+ return &dto.TaskError{
+ Message: "prompt is required",
+ Code: "invalid_request",
+ }
+ }
+ info.Action = "create"
+ return nil
+}
+
+// 预估算计费
+func (a *Adaptor) EstimateBilling(c *gin.Context, info *relaycommon.RelayInfo) map[string]float64 {
+ // 返回额外的计费比例
+ return map[string]float64{
+ "seconds": 1.0,
+ "size": 1.5,
+ }
+}
+
+// 构建请求 URL
+func (a *Adaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
+ return info.ChannelBaseUrl + "/api/v1/create", nil
+}
+
+// 构建请求头
+func (a *Adaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
+ req.Header.Set("Authorization", "Bearer "+info.ApiKey)
+ req.Header.Set("Content-Type", "application/json")
+ return nil
+}
+
+// 构建请求体
+func (a *Adaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayInfo) (io.Reader, error) {
+ reqBody := map[string]interface{}{
+ "prompt": info.Prompt,
+ "model": info.UpstreamModelName,
+ }
+ body, _ := json.Marshal(reqBody)
+ return bytes.NewReader(body), nil
+}
+
+// 执行提交请求
+func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) {
+ client := common.GetHttpClient()
+ req, _ := http.NewRequest("POST", info.ChannelBaseUrl, requestBody)
+ a.BuildRequestHeader(c, req, info)
+ return client.Do(req)
+}
+
+// 处理提交响应
+func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (taskID string, taskData []byte, err *dto.TaskError) {
+ if resp.StatusCode != http.StatusOK {
+ return "", nil, &dto.TaskError{
+ Message: "submit failed",
+ Code: "submit_error",
+ }
+ }
+ body, _ := io.ReadAll(resp.Body)
+ var result map[string]interface{}
+ json.Unmarshal(body, &result)
+ return result["id"].(string), body, nil
+}
+
+// 获取任务状态
+func (a *Adaptor) FetchTask(baseUrl, key string, body map[string]any, proxy string) (*http.Response, error) {
+ client := common.GetHttpClient()
+ taskURL := baseUrl + "/api/v1/task/" + body["task_id"].(string)
+ req, _ := http.NewRequest("GET", taskURL, nil)
+ req.Header.Set("Authorization", "Bearer "+key)
+ return client.Do(req)
+}
+
+// 解析任务结果
+func (a *Adaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, error) {
+ var result map[string]interface{}
+ json.Unmarshal(respBody, &result)
+
+ status := result["status"].(string)
+ return &relaycommon.TaskInfo{
+ TaskID: result["id"].(string),
+ Status: status,
+ Result: result["result"],
+ Finished: status == "completed" || status == "failed",
+ }, nil
+}
+
+// 完成后调整计费
+func (a *Adaptor) AdjustBillingOnComplete(task *model.Task, taskResult *relaycommon.TaskInfo) int {
+ if taskResult.Status == "completed" {
+ // 返回正数表示需要补扣
+ return 10
+ }
+ return 0
+}
+
+func (a *Adaptor) GetModelList() []string {
+ return []string{"mytask-model-v1", "mytask-model-v2"}
+}
+
+func (a *Adaptor) GetChannelName() string {
+ return "MyTask"
+}
+```
+
+### 步骤 2: 注册任务适配器
+
+在 `relay/adaptor.go` 中添加:
+```go
+func GetTaskAdaptor(platform constant.TaskPlatform) service.TaskPollingAdaptor {
+ switch platform {
+ case constant.TaskPlatformSuno:
+ return &suno.Adaptor{}
+ // ...
+ case constant.TaskPlatformMyTask:
+ return &mytask.Adaptor{}
+ default:
+ return nil
+ }
+}
+```
+
+---
+
+## 添加新的 API 路由
+
+### 添加管理后台路由
+
+在 `router/dashboard.go` 中添加:
+
+```go
+func SetDashboardRouter(router *gin.Engine) {
+ dashboardRouter := router.Group("/api")
+ dashboardRouter.Use(middleware.RouteTag("dashboard"))
+ dashboardRouter.Use(middleware.TokenAuth())
+ {
+ // 现有路由...
+
+ // 添加新路由
+ myFeatureRouter := dashboardRouter.Group("/myfeature")
+ myFeatureRouter.Use(middleware.UserAuth())
+ {
+ myFeatureRouter.GET("", controller.ListMyFeature)
+ myFeatureRouter.POST("", controller.CreateMyFeature)
+ myFeatureRouter.PUT("/:id", controller.UpdateMyFeature)
+ myFeatureRouter.DELETE("/:id", controller.DeleteMyFeature)
+ }
+ }
+}
+```
+
+### 添加 API 路由
+
+在 `router/api-router.go` 中添加:
+
+```go
+func SetApiRouter(router *gin.Engine) {
+ apiRouter := router.Group("/api")
+ apiRouter.Use(middleware.RouteTag("api"))
+ apiRouter.Use(middleware.TokenAuth())
+ {
+ // 现有路由...
+
+ // 添加新路由
+ apiRouter.POST("/myfeature", controller.MyFeatureHandler)
+ }
+}
+```
+
+---
+
+## 扩展计费系统
+
+### 添加新的计费比例类型
+
+在 `model/channel_satisfy.go` 中扩展:
+
+```go
+type ChannelRatioConfig struct {
+ // 现有字段...
+ MyTaskSecondsRatio float64 `json:"mytask_seconds_ratio"`
+ MyTaskSizeRatio float64 `json:"mytask_size_ratio"`
+}
+```
+
+### 实现自定义计费逻辑
+
+在 `service/billing.go` 中添加:
+
+```go
+func CalculateMyTaskCost(info *relaycommon.RelayInfo, otherRatios map[string]float64) (float64, error) {
+ modelRatio, err := model.GetModelRatio(info.OriginModelName)
+ if err != nil {
+ return 0, err
+ }
+
+ // 获取额外比例
+ secondsRatio := otherRatios["seconds"]
+ sizeRatio := otherRatios["size"]
+
+ // 计算基础费用
+ baseCost := modelRatio.Ratio * 1.0 // per token/second base
+
+ // 应用额外比例
+ totalCost := baseCost * secondsRatio * sizeRatio
+
+ return totalCost, nil
+}
+```
+
+---
+
+## 添加中间件
+
+### 创建中间件文件
+
+在 `middleware/` 下创建新文件:
+
+```go
+package middleware
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+func MyCustomMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ // 前置处理
+ requestID := c.GetHeader("X-Request-ID")
+
+ // 调用下一个处理
+ c.Next()
+
+ // 后置处理
+ statusCode := c.Writer.Status()
+ // 记录日志等
+ }
+}
+```
+
+### 注册中间件
+
+在路由文件中使用:
+
+```go
+func SetRelayRouter(router *gin.Engine) {
+ relayRouter := router.Group("/v1")
+ relayRouter.Use(middleware.MyCustomMiddleware())
+ // ...
+}
+```
+
+---
+
+## 自定义配置选项
+
+### 添加新的配置类型
+
+在 `dto/channel_setting.go` 中扩展:
+
+```go
+type ChannelSettings struct {
+ // 现有字段...
+ MyCustomSetting string `json:"my_custom_setting"`
+ MyAdvancedFlag bool `json:"my_advanced_flag"`
+}
+```
+
+### 在数据库中存储
+
+在 `model/channel.go` 的 `Channel` 结构中添加:
+
+```go
+type Channel struct {
+ // 现有字段...
+ OtherSettings ChannelOtherSettings `json:"other_settings"`
+}
+
+type ChannelOtherSettings struct {
+ // 现有字段...
+ MyCustomSetting string `json:"my_custom_setting"`
+ MyAdvancedFlag bool `json:"my_advanced_flag"`
+}
+```
+
+---
+
+## 测试指南
+
+### 单元测试
+
+创建 `*_test.go` 文件:
+
+```go
+package myprovider
+
+import (
+ "testing"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAdaptorConvertRequest(t *testing.T) {
+ adaptor := &Adaptor{}
+ info := &relaycommon.RelayInfo{
+ UpstreamModelName: "gpt-4",
+ IsStream: true,
+ }
+ request := &dto.GeneralOpenAIRequest{
+ Model: "gpt-4",
+ }
+
+ result, err := adaptor.ConvertOpenAIRequest(nil, info, request)
+
+ assert.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestAdaptorGetRequestURL(t *testing.T) {
+ adaptor := &Adaptor{}
+ info := &relaycommon.RelayInfo{
+ ChannelBaseUrl: "https://api.myprovider.com",
+ }
+
+ url, err := adaptor.GetRequestURL(info)
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://api.myprovider.com/v1/chat/completions", url)
+}
+```
+
+### 运行测试
+
+```bash
+# 运行所有测试
+go test ./...
+
+# 运行特定包的测试
+go test ./relay/channel/myprovider/
+
+# 运行特定测试
+go test ./relay/channel/myprovider/ -run TestAdaptorConvertRequest
+
+# 详细输出
+go test -v ./relay/channel/myprovider/
+```
+
+### 集成测试
+
+测试完整的请求流程:
+
+```go
+func TestRelayIntegration(t *testing.T) {
+ // 启动测试服务器
+ // 发送请求
+ // 验证响应
+}
+```
+
+---
+
+## 前端扩展
+
+### 添加新页面
+
+在 `web/src/pages/` 下创建新页面组件:
+
+```tsx
+import React, { useEffect, useState } from 'react';
+import { Button, Form, Input } from '@douyinfe/semi-ui';
+
+const MyFeaturePage: React.FC = () => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ // 加载数据
+ loadData();
+ }, []);
+
+ const loadData = async () => {
+ setLoading(true);
+ const response = await API.get('/api/myfeature');
+ setData(response.data);
+ setLoading(false);
+ };
+
+ return (
+