Merge pull request #56 from tbphp/feat-proxy-key
feat: 认证Key分离-新增代理Key配置
This commit is contained in:
91
README.md
91
README.md
@@ -12,7 +12,7 @@
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **透明代理**: 完全保留原生 API 格式,支持 OpenAI、Google Gemini 和 Anthropic Claude 等多种格式(持续扩展中)
|
||||
- **透明代理**: 完全保留原生 API 格式,支持 OpenAI、Google Gemini 和 Anthropic Claude 等多种格式
|
||||
- **智能密钥管理**: 高性能密钥池,支持分组管理、自动轮换和故障恢复
|
||||
- **负载均衡**: 支持多上游端点的加权负载均衡,提升服务可用性
|
||||
- **智能故障处理**: 自动密钥黑名单管理和恢复机制,确保服务连续性
|
||||
@@ -22,6 +22,7 @@
|
||||
- **全面监控**: 实时统计、健康检查、详细请求日志
|
||||
- **高性能设计**: 零拷贝流式传输、连接池复用、原子操作
|
||||
- **生产就绪**: 优雅关闭、错误恢复、完善的安全机制
|
||||
- **双重认证体系**: 管理端与代理端认证分离,代理认证支持全局和分组级别密钥
|
||||
|
||||
## 支持的 AI 服务
|
||||
|
||||
@@ -30,7 +31,6 @@ GPT-Load 作为透明代理服务,完整保留各 AI 服务商的原生 API
|
||||
- **OpenAI 格式**: 官方 OpenAI API、Azure OpenAI、以及其他 OpenAI 兼容服务
|
||||
- **Google Gemini 格式**: Gemini Pro、Gemini Pro Vision 等模型的原生 API
|
||||
- **Anthropic Claude 格式**: Claude 系列模型,支持高质量的对话和文本生成
|
||||
- **扩展性**: 插件化架构设计,可快速集成新的 AI 服务提供商及其原生格式
|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -153,9 +153,10 @@ GPT-Load 采用双层配置架构:
|
||||
- **配置优先级**:分组配置 > 系统设置
|
||||
- **特点**:支持热重载,修改后立即生效,无需重启应用
|
||||
|
||||
### 静态配置(环境变量)
|
||||
<details>
|
||||
<summary>静态配置(环境变量)</summary>
|
||||
|
||||
#### 服务器配置
|
||||
**服务器配置:**
|
||||
|
||||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||||
| ------------ | ---------------------------------- | --------------- | -------------------------- |
|
||||
@@ -168,15 +169,15 @@ GPT-Load 采用双层配置架构:
|
||||
| 从节点模式 | `IS_SLAVE` | false | 集群部署时从节点标识 |
|
||||
| 时区 | `TZ` | `Asia/Shanghai` | 指定时区 |
|
||||
|
||||
#### 认证与数据库配置
|
||||
**认证与数据库配置:**
|
||||
|
||||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||||
| ---------- | -------------- | ------------------ | ------------------------------------ |
|
||||
| 认证密钥 | `AUTH_KEY` | `sk-123456` | 访问管理端以及请求代理的唯一认证密钥 |
|
||||
| 管理密钥 | `AUTH_KEY` | `sk-123456` | **管理端**的访问认证密钥 |
|
||||
| 数据库连接 | `DATABASE_DSN` | ./data/gpt-load.db | 数据库连接字符串 (DSN) 或文件路径 |
|
||||
| Redis 连接 | `REDIS_DSN` | - | Redis 连接字符串,为空时使用内存存储 |
|
||||
|
||||
#### 性能与跨域配置
|
||||
**性能与跨域配置:**
|
||||
|
||||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||||
| ------------ | ------------------------- | ----------------------------- | ------------------------ |
|
||||
@@ -187,7 +188,7 @@ GPT-Load 采用双层配置架构:
|
||||
| 允许的头部 | `ALLOWED_HEADERS` | `*` | 允许的请求头,逗号分隔 |
|
||||
| 允许凭据 | `ALLOW_CREDENTIALS` | false | 是否允许发送凭据 |
|
||||
|
||||
#### 日志配置
|
||||
**日志配置:**
|
||||
|
||||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||||
| ------------ | ----------------- | --------------------- | ---------------------------------- |
|
||||
@@ -196,37 +197,36 @@ GPT-Load 采用双层配置架构:
|
||||
| 启用文件日志 | `LOG_ENABLE_FILE` | false | 是否启用文件日志输出 |
|
||||
| 日志文件路径 | `LOG_FILE_PATH` | `./data/logs/app.log` | 日志文件存储路径 |
|
||||
|
||||
#### 代理配置
|
||||
**代理配置:**
|
||||
|
||||
GPT-Load 会自动从环境变量中读取代理设置,用于向上游 AI 服务商发起请求。
|
||||
|
||||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| HTTP 代理 | `HTTP_PROXY` | - | 用于 HTTP 请求的代理服务器地址 |
|
||||
| HTTPS 代理 | `HTTPS_PROXY` | - | 用于 HTTPS 请求的代理服务器地址 |
|
||||
| 无代理 | `NO_PROXY` | - | 不需要通过代理访问的主机或域名,逗号分隔 |
|
||||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||||
| ---------- | ------------- | ------ | ---------------------------------------- |
|
||||
| HTTP 代理 | `HTTP_PROXY` | - | 用于 HTTP 请求的代理服务器地址 |
|
||||
| HTTPS 代理 | `HTTPS_PROXY` | - | 用于 HTTPS 请求的代理服务器地址 |
|
||||
| 无代理 | `NO_PROXY` | - | 不需要通过代理访问的主机或域名,逗号分隔 |
|
||||
|
||||
**支持的代理协议格式:**
|
||||
支持的代理协议格式:
|
||||
|
||||
- **HTTP**: `http://user:pass@host:port`
|
||||
- **HTTPS**: `https://user:pass@host:port`
|
||||
- **SOCKS5**: `socks5://user:pass@host:port`
|
||||
</details>
|
||||
|
||||
### 动态配置(热重载)
|
||||
<details>
|
||||
<summary>动态配置(热重载)</summary>
|
||||
|
||||
动态配置存储在数据库中,支持通过 Web 管理界面进行实时修改,修改后立即生效无需重启。
|
||||
**基础设置:**
|
||||
|
||||
**配置优先级**:分组配置 > 系统设置
|
||||
| 配置项 | 字段名 | 默认值 | 分组可覆盖 | 说明 |
|
||||
| ------------ | ------------------------------------ | --------------------------- | ---------- | -------------------------------------- |
|
||||
| 项目地址 | `app_url` | `http://localhost:3001` | ❌ | 项目基础 URL |
|
||||
| 日志保留天数 | `request_log_retention_days` | 7 | ❌ | 请求日志保留天数,0 为不清理 |
|
||||
| 日志写入间隔 | `request_log_write_interval_minutes` | 1 | ❌ | 日志写入数据库周期(分钟) |
|
||||
| 全局代理密钥 | `proxy_keys` | 初始值为环境配置的 AUTH_KEY | ❌ | 全局生效的代理认证密钥,多个用逗号分隔 |
|
||||
|
||||
#### 基础设置
|
||||
|
||||
| 配置项 | 字段名 | 默认值 | 分组可覆盖 | 说明 |
|
||||
| ------------ | ------------------------------------ | ----------------------- | ---------- | ---------------------------- |
|
||||
| 项目地址 | `app_url` | `http://localhost:3001` | ❌ | 项目基础 URL |
|
||||
| 日志保留天数 | `request_log_retention_days` | 7 | ❌ | 请求日志保留天数,0 为不清理 |
|
||||
| 日志写入间隔 | `request_log_write_interval_minutes` | 1 | ❌ | 日志写入数据库周期(分钟) |
|
||||
|
||||
#### 请求设置
|
||||
**请求设置:**
|
||||
|
||||
| 配置项 | 字段名 | 默认值 | 分组可覆盖 | 说明 |
|
||||
| -------------------- | ------------------------- | ------ | ---------- | ------------------------------ |
|
||||
@@ -237,7 +237,7 @@ GPT-Load 会自动从环境变量中读取代理设置,用于向上游 AI 服
|
||||
| 最大空闲连接数 | `max_idle_conns` | 100 | ✅ | 连接池最大空闲连接总数 |
|
||||
| 每主机最大空闲连接数 | `max_idle_conns_per_host` | 50 | ✅ | 每个上游主机最大空闲连接数 |
|
||||
|
||||
#### 密钥配置
|
||||
**密钥配置:**
|
||||
|
||||
| 配置项 | 字段名 | 默认值 | 分组可覆盖 | 说明 |
|
||||
| -------------- | --------------------------------- | ------ | ---------- | ------------------------------------------------ |
|
||||
@@ -247,6 +247,8 @@ GPT-Load 会自动从环境变量中读取代理设置,用于向上游 AI 服
|
||||
| 密钥验证并发数 | `key_validation_concurrency` | 10 | ✅ | 后台定时验证无效 Key 时的并发数 |
|
||||
| 密钥验证超时 | `key_validation_timeout_seconds` | 20 | ✅ | 后台定时验证单个 Key 时的 API 请求超时时间(秒) |
|
||||
|
||||
</details>
|
||||
|
||||
## Web 管理界面
|
||||
|
||||
访问管理控制台:<http://localhost:3001>(默认地址)
|
||||
@@ -270,7 +272,8 @@ Web 管理界面提供以下功能:
|
||||
|
||||
## API 使用说明
|
||||
|
||||
### 代理接口调用方式
|
||||
<details>
|
||||
<summary>代理接口调用方式</summary>
|
||||
|
||||
GPT-Load 通过分组名称路由请求到不同的 AI 服务。使用方式如下:
|
||||
|
||||
@@ -285,11 +288,11 @@ http://localhost:3001/proxy/{group_name}/{原始API路径}
|
||||
|
||||
#### 2. 认证方式
|
||||
|
||||
作为透明代理服务,GPT-Load 完全保留各 AI 服务的原生认证格式:
|
||||
在 Web 管理界面中配置**代理密钥** (`Proxy Keys`),可设置系统级别和分组级别的代理密钥。
|
||||
|
||||
- **OpenAI 格式**: 使用 `Authorization: Bearer {AUTH_KEY}` 头部认证
|
||||
- **Gemini 格式**: 使用 URL 参数 `key={AUTH_KEY}` 认证
|
||||
- **统一密钥**: 所有服务都使用环境变量 `AUTH_KEY` 中配置的统一密钥值
|
||||
- **认证方式**: 与原生 API 一致,但需将原始密钥替换为配置的代理密钥。
|
||||
- **密钥作用域**: 在系统设置配置的 **全局代理密钥** 可以在所有分组使用,在分组配置的 **分组代理密钥** 仅在当前分组有效。
|
||||
- **格式**: 多个密钥使用半角英文逗号分隔。
|
||||
|
||||
#### 3. OpenAI 接口调用示例
|
||||
|
||||
@@ -308,7 +311,7 @@ curl -X POST https://api.openai.com/v1/chat/completions \
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/proxy/openai/v1/chat/completions \
|
||||
-H "Authorization: Bearer sk-123456" \
|
||||
-H "Authorization: Bearer your-proxy-key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Hello"}]}'
|
||||
```
|
||||
@@ -316,7 +319,7 @@ curl -X POST http://localhost:3001/proxy/openai/v1/chat/completions \
|
||||
**变更说明:**
|
||||
|
||||
- 将 `https://api.openai.com` 替换为 `http://localhost:3001/proxy/openai`
|
||||
- 将原始 API Key 替换为统一认证密钥 `sk-123456`(默认值)
|
||||
- 将原始 API Key 替换为**代理密钥**
|
||||
|
||||
#### 4. Gemini 接口调用示例
|
||||
|
||||
@@ -333,7 +336,7 @@ curl -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-
|
||||
**代理调用方式:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/proxy/gemini/v1beta/models/gemini-2.5-pro:generateContent?key=sk-123456 \
|
||||
curl -X POST http://localhost:3001/proxy/gemini/v1beta/models/gemini-2.5-pro:generateContent?key=your-proxy-key \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"contents": [{"parts": [{"text": "Hello"}]}]}'
|
||||
```
|
||||
@@ -341,7 +344,7 @@ curl -X POST http://localhost:3001/proxy/gemini/v1beta/models/gemini-2.5-pro:gen
|
||||
**变更说明:**
|
||||
|
||||
- 将 `https://generativelanguage.googleapis.com` 替换为 `http://localhost:3001/proxy/gemini`
|
||||
- 将 URL 参数中的 `key=your-gemini-key` 替换为统一认证密钥 `sk-123456`(默认值)
|
||||
- 将 URL 参数中的 `key=your-gemini-key` 替换为**代理密钥**
|
||||
|
||||
#### 5. Anthropic 接口调用示例
|
||||
|
||||
@@ -361,7 +364,7 @@ curl -X POST https://api.anthropic.com/v1/messages \
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/proxy/anthropic/v1/messages \
|
||||
-H "x-api-key: sk-123456" \
|
||||
-H "x-api-key: your-proxy-key" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "Hello"}]}'
|
||||
@@ -370,7 +373,7 @@ curl -X POST http://localhost:3001/proxy/anthropic/v1/messages \
|
||||
**变更说明:**
|
||||
|
||||
- 将 `https://api.anthropic.com` 替换为 `http://localhost:3001/proxy/anthropic`
|
||||
- 将 `x-api-key` 头部中的原始 API Key 替换为统一认证密钥 `sk-123456`(默认值)
|
||||
- 将 `x-api-key` 头部中的原始 API Key 替换为**代理密钥**
|
||||
|
||||
#### 6. 支持的接口
|
||||
|
||||
@@ -402,7 +405,7 @@ curl -X POST http://localhost:3001/proxy/anthropic/v1/messages \
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key="sk-123456", # 使用统一认证密钥
|
||||
api_key="your-proxy-key", # 使用密钥
|
||||
base_url="http://localhost:3001/proxy/openai" # 使用代理端点
|
||||
)
|
||||
|
||||
@@ -419,7 +422,7 @@ import google.generativeai as genai
|
||||
|
||||
# 配置 API 密钥和基础 URL
|
||||
genai.configure(
|
||||
api_key="sk-123456", # 使用统一认证密钥
|
||||
api_key="your-proxy-key", # 使用代理密钥
|
||||
client_options={"api_endpoint": "http://localhost:3001/proxy/gemini"}
|
||||
)
|
||||
|
||||
@@ -433,7 +436,7 @@ response = model.generate_content("Hello")
|
||||
from anthropic import Anthropic
|
||||
|
||||
client = Anthropic(
|
||||
api_key="sk-123456", # 使用统一认证密钥
|
||||
api_key="your-proxy-key", # 使用代理密钥
|
||||
base_url="http://localhost:3001/proxy/anthropic" # 使用代理端点
|
||||
)
|
||||
|
||||
@@ -443,7 +446,9 @@ response = client.messages.create(
|
||||
)
|
||||
```
|
||||
|
||||
> **重要提示**:作为透明代理服务,GPT-Load 完全保留各 AI 服务的原生 API 格式和认证方式,仅需要替换端点地址并使用统一密钥值即可无缝迁移。
|
||||
> **重要提示**:作为透明代理服务,GPT-Load 完全保留各 AI 服务的原生 API 格式和认证方式,仅需要替换端点地址并使用在管理端配置的**代理密钥**即可无缝迁移。
|
||||
|
||||
</details>
|
||||
|
||||
## 许可证
|
||||
|
||||
|
89
README_EN.md
89
README_EN.md
@@ -12,7 +12,7 @@ For detailed documentation, please visit [Official Documentation](https://www.gp
|
||||
|
||||
## Features
|
||||
|
||||
- **Transparent Proxy**: Complete preservation of native API formats, supporting OpenAI, Google Gemini, and Anthropic Claude among other formats (continuously expanding)
|
||||
- **Transparent Proxy**: Complete preservation of native API formats, supporting OpenAI, Google Gemini, and Anthropic Claude among other formats
|
||||
- **Intelligent Key Management**: High-performance key pool with group-based management, automatic rotation, and failure recovery
|
||||
- **Load Balancing**: Weighted load balancing across multiple upstream endpoints to enhance service availability
|
||||
- **Smart Failure Handling**: Automatic key blacklist management and recovery mechanisms to ensure service continuity
|
||||
@@ -22,6 +22,7 @@ For detailed documentation, please visit [Official Documentation](https://www.gp
|
||||
- **Comprehensive Monitoring**: Real-time statistics, health checks, and detailed request logging
|
||||
- **High-Performance Design**: Zero-copy streaming, connection pool reuse, and atomic operations
|
||||
- **Production Ready**: Graceful shutdown, error recovery, and comprehensive security mechanisms
|
||||
- **Dual Authentication**: Separate authentication for management and proxy, with proxy authentication supporting global and group-level keys
|
||||
|
||||
## Supported AI Services
|
||||
|
||||
@@ -30,7 +31,6 @@ GPT-Load serves as a transparent proxy service, completely preserving the native
|
||||
- **OpenAI Format**: Official OpenAI API, Azure OpenAI, and other OpenAI-compatible services
|
||||
- **Google Gemini Format**: Native APIs for Gemini Pro, Gemini Pro Vision, and other models
|
||||
- **Anthropic Claude Format**: Claude series models, supporting high-quality conversations and text generation
|
||||
- **Extensibility**: Plugin-based architecture design for rapid integration of new AI service providers and their native formats
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -153,9 +153,10 @@ GPT-Load adopts a dual-layer configuration architecture:
|
||||
- **Configuration Priority**: Group Configuration > System Settings
|
||||
- **Characteristics**: Supports hot-reload, takes effect immediately after modification without application restart
|
||||
|
||||
### Static Configuration (Environment Variables)
|
||||
<details>
|
||||
<summary>Static Configuration (Environment Variables)</summary>
|
||||
|
||||
#### Server Configuration
|
||||
**Server Configuration:**
|
||||
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| ------------------------- | ---------------------------------- | --------------- | ----------------------------------------------- |
|
||||
@@ -168,15 +169,15 @@ GPT-Load adopts a dual-layer configuration architecture:
|
||||
| Follower Mode | `IS_SLAVE` | false | Follower node identifier for cluster deployment |
|
||||
| Timezone | `TZ` | `Asia/Shanghai` | Specify timezone |
|
||||
|
||||
#### Authentication & Database Configuration
|
||||
**Authentication & Database Configuration:**
|
||||
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| ------------------- | -------------------- | -------------------- | ------------------------------------------------------------------------------- |
|
||||
| Authentication Key | `AUTH_KEY` | `sk-123456` | Unique authentication key for accessing management interface and proxy requests |
|
||||
| Database Connection | `DATABASE_DSN` | `./data/gpt-load.db` | Database connection string (DSN) or file path |
|
||||
| Redis Connection | `REDIS_DSN` | - | Redis connection string, uses memory storage when empty |
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| ------------------- | -------------------- | -------------------- | --------------------------------------------------- |
|
||||
| Admin Key | `AUTH_KEY` | `sk-123456` | Access authentication key for the **management end**|
|
||||
| Database Connection | `DATABASE_DSN` | `./data/gpt-load.db` | Database connection string (DSN) or file path |
|
||||
| Redis Connection | `REDIS_DSN` | - | Redis connection string, uses memory storage when empty |
|
||||
|
||||
#### Performance & CORS Configuration
|
||||
**Performance & CORS Configuration:**
|
||||
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| ----------------------- | ------------------------- | ----------------------------- | ----------------------------------------------- |
|
||||
@@ -187,7 +188,7 @@ GPT-Load adopts a dual-layer configuration architecture:
|
||||
| Allowed Headers | `ALLOWED_HEADERS` | `*` | Allowed request headers, comma-separated |
|
||||
| Allow Credentials | `ALLOW_CREDENTIALS` | false | Whether to allow sending credentials |
|
||||
|
||||
#### Logging Configuration
|
||||
**Logging Configuration:**
|
||||
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| ------------------- | -------------------- | --------------------- | ----------------------------------- |
|
||||
@@ -196,37 +197,36 @@ GPT-Load adopts a dual-layer configuration architecture:
|
||||
| Enable File Logging | `LOG_ENABLE_FILE` | false | Whether to enable file log output |
|
||||
| Log File Path | `LOG_FILE_PATH` | `./data/logs/app.log` | Log file storage path |
|
||||
|
||||
#### Proxy Configuration
|
||||
**Proxy Configuration:**
|
||||
|
||||
GPT-Load automatically reads proxy settings from environment variables to make requests to upstream AI providers.
|
||||
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| HTTP Proxy | `HTTP_PROXY` | - | Proxy server address for HTTP requests |
|
||||
| HTTPS Proxy | `HTTPS_PROXY` | - | Proxy server address for HTTPS requests |
|
||||
| No Proxy | `NO_PROXY` | - | Comma-separated list of hosts or domains to bypass the proxy |
|
||||
| Setting | Environment Variable | Default | Description |
|
||||
| ----------- | -------------------- | ------- | ----------------------------------------------- |
|
||||
| HTTP Proxy | `HTTP_PROXY` | - | Proxy server address for HTTP requests |
|
||||
| HTTPS Proxy | `HTTPS_PROXY` | - | Proxy server address for HTTPS requests |
|
||||
| No Proxy | `NO_PROXY` | - | Comma-separated list of hosts or domains to bypass the proxy |
|
||||
|
||||
**Supported Proxy Protocol Formats:**
|
||||
Supported Proxy Protocol Formats:
|
||||
|
||||
- **HTTP**: `http://user:pass@host:port`
|
||||
- **HTTPS**: `https://user:pass@host:port`
|
||||
- **SOCKS5**: `socks5://user:pass@host:port`
|
||||
</details>
|
||||
|
||||
### Dynamic Configuration (Hot-Reload)
|
||||
<details>
|
||||
<summary>Dynamic Configuration (Hot-Reload)</summary>
|
||||
|
||||
Dynamic configuration is stored in the database and supports real-time modification through the web management interface, taking effect immediately without restart.
|
||||
|
||||
**Configuration Priority**: Group Configuration > System Settings
|
||||
|
||||
#### Basic Settings
|
||||
**Basic Settings:**
|
||||
|
||||
| Setting | Field Name | Default | Group Override | Description |
|
||||
| ------------------ | ------------------------------------ | ----------------------- | -------------- | -------------------------------------------- |
|
||||
| Project URL | `app_url` | `http://localhost:3001` | ❌ | Project base URL |
|
||||
| Log Retention Days | `request_log_retention_days` | 7 | ❌ | Request log retention days, 0 for no cleanup |
|
||||
| Log Write Interval | `request_log_write_interval_minutes` | 1 | ❌ | Log write to database cycle (minutes) |
|
||||
| Global Proxy Keys | `proxy_keys` | Initial value from `AUTH_KEY` | ❌ | Globally effective proxy keys, comma-separated |
|
||||
|
||||
#### Request Settings
|
||||
**Request Settings:**
|
||||
|
||||
| Setting | Field Name | Default | Group Override | Description |
|
||||
| ----------------------------- | ------------------------- | ------- | -------------- | ------------------------------------------------------------------- |
|
||||
@@ -237,7 +237,7 @@ Dynamic configuration is stored in the database and supports real-time modificat
|
||||
| Max Idle Connections | `max_idle_conns` | 100 | ✅ | Connection pool maximum total idle connections |
|
||||
| Max Idle Connections Per Host | `max_idle_conns_per_host` | 50 | ✅ | Maximum idle connections per upstream host |
|
||||
|
||||
#### Key Configuration
|
||||
**Key Configuration:**
|
||||
|
||||
| Setting | Field Name | Default | Group Override | Description |
|
||||
| -------------------------- | --------------------------------- | ------- | -------------- | -------------------------------------------------------------------------- |
|
||||
@@ -247,6 +247,8 @@ Dynamic configuration is stored in the database and supports real-time modificat
|
||||
| Key Validation Concurrency | `key_validation_concurrency` | 10 | ✅ | Concurrency for background validation of invalid keys |
|
||||
| Key Validation Timeout | `key_validation_timeout_seconds` | 20 | ✅ | API request timeout for validating individual keys in background (seconds) |
|
||||
|
||||
</details>
|
||||
|
||||
## Web Management Interface
|
||||
|
||||
Access the management console at: <http://localhost:3001> (default address)
|
||||
@@ -270,7 +272,8 @@ The web management interface provides the following features:
|
||||
|
||||
## API Usage Guide
|
||||
|
||||
### Proxy Interface Invocation
|
||||
<details>
|
||||
<summary>Proxy Interface Invocation</summary>
|
||||
|
||||
GPT-Load routes requests to different AI services through group names. Usage is as follows:
|
||||
|
||||
@@ -285,11 +288,11 @@ http://localhost:3001/proxy/{group_name}/{original_api_path}
|
||||
|
||||
#### 2. Authentication Methods
|
||||
|
||||
As a transparent proxy service, GPT-Load completely preserves the native authentication formats of various AI services:
|
||||
Configure **Proxy Keys** in the web management interface, which supports system-level and group-level proxy keys.
|
||||
|
||||
- **OpenAI Format**: Uses `Authorization: Bearer {AUTH_KEY}` header authentication
|
||||
- **Gemini Format**: Uses URL parameter `key={AUTH_KEY}` authentication
|
||||
- **Unified Key**: All services use the unified key value configured in the `AUTH_KEY` environment variable
|
||||
- **Authentication Method**: Consistent with the native API, but replace the original key with the configured proxy key.
|
||||
- **Key Scope**: **Global Proxy Keys** configured in system settings can be used in all groups. **Group Proxy Keys** configured in a group are only valid for the current group.
|
||||
- **Format**: Multiple keys are separated by commas.
|
||||
|
||||
#### 3. OpenAI Interface Example
|
||||
|
||||
@@ -308,7 +311,7 @@ curl -X POST https://api.openai.com/v1/chat/completions \
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/proxy/openai/v1/chat/completions \
|
||||
-H "Authorization: Bearer sk-123456" \
|
||||
-H "Authorization: Bearer your-proxy-key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Hello"}]}'
|
||||
```
|
||||
@@ -316,7 +319,7 @@ curl -X POST http://localhost:3001/proxy/openai/v1/chat/completions \
|
||||
**Changes required:**
|
||||
|
||||
- Replace `https://api.openai.com` with `http://localhost:3001/proxy/openai`
|
||||
- Replace original API Key with unified authentication key `sk-123456` (default value)
|
||||
- Replace original API Key with the **Proxy Key**
|
||||
|
||||
#### 4. Gemini Interface Example
|
||||
|
||||
@@ -333,7 +336,7 @@ curl -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-
|
||||
**Proxy invocation:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/proxy/gemini/v1beta/models/gemini-2.5-pro:generateContent?key=sk-123456 \
|
||||
curl -X POST http://localhost:3001/proxy/gemini/v1beta/models/gemini-2.5-pro:generateContent?key=your-proxy-key \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"contents": [{"parts": [{"text": "Hello"}]}]}'
|
||||
```
|
||||
@@ -341,7 +344,7 @@ curl -X POST http://localhost:3001/proxy/gemini/v1beta/models/gemini-2.5-pro:gen
|
||||
**Changes required:**
|
||||
|
||||
- Replace `https://generativelanguage.googleapis.com` with `http://localhost:3001/proxy/gemini`
|
||||
- Replace `key=your-gemini-key` in URL parameter with unified authentication key `sk-123456` (default value)
|
||||
- Replace `key=your-gemini-key` in URL parameter with the **Proxy Key**
|
||||
|
||||
#### 5. Anthropic Interface Example
|
||||
|
||||
@@ -361,7 +364,7 @@ curl -X POST https://api.anthropic.com/v1/messages \
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/proxy/anthropic/v1/messages \
|
||||
-H "x-api-key: sk-123456" \
|
||||
-H "x-api-key: your-proxy-key" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "Hello"}]}'
|
||||
@@ -370,7 +373,7 @@ curl -X POST http://localhost:3001/proxy/anthropic/v1/messages \
|
||||
**Changes required:**
|
||||
|
||||
- Replace `https://api.anthropic.com` with `http://localhost:3001/proxy/anthropic`
|
||||
- Replace the original API Key in `x-api-key` header with unified authentication key `sk-123456` (default value)
|
||||
- Replace the original API Key in `x-api-key` header with the **Proxy Key**
|
||||
|
||||
#### 6. Supported Interfaces
|
||||
|
||||
@@ -402,7 +405,7 @@ curl -X POST http://localhost:3001/proxy/anthropic/v1/messages \
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key="sk-123456", # Use unified authentication key
|
||||
api_key="your-proxy-key", # Use the proxy key
|
||||
base_url="http://localhost:3001/proxy/openai" # Use proxy endpoint
|
||||
)
|
||||
|
||||
@@ -419,7 +422,7 @@ import google.generativeai as genai
|
||||
|
||||
# Configure API key and base URL
|
||||
genai.configure(
|
||||
api_key="sk-123456", # Use unified authentication key
|
||||
api_key="your-proxy-key", # Use the proxy key
|
||||
client_options={"api_endpoint": "http://localhost:3001/proxy/gemini"}
|
||||
)
|
||||
|
||||
@@ -433,7 +436,7 @@ response = model.generate_content("Hello")
|
||||
from anthropic import Anthropic
|
||||
|
||||
client = Anthropic(
|
||||
api_key="sk-123456", # Use unified authentication key
|
||||
api_key="your-proxy-key", # Use the proxy key
|
||||
base_url="http://localhost:3001/proxy/anthropic" # Use proxy endpoint
|
||||
)
|
||||
|
||||
@@ -443,7 +446,9 @@ response = client.messages.create(
|
||||
)
|
||||
```
|
||||
|
||||
> **Important Note**: As a transparent proxy service, GPT-Load completely preserves the native API formats and authentication methods of various AI services. You only need to replace the endpoint address and use the unified key value for seamless migration.
|
||||
> **Important Note**: As a transparent proxy service, GPT-Load completely preserves the native API formats and authentication methods of various AI services. You only need to replace the endpoint address and use the **Proxy Key** configured in the management interface for seamless migration.
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
|
||||
|
@@ -94,7 +94,7 @@ func (a *App) Start() error {
|
||||
logrus.Info("Database auto-migration completed.")
|
||||
|
||||
// 初始化系统设置
|
||||
if err := a.settingsManager.EnsureSettingsInitialized(); err != nil {
|
||||
if err := a.settingsManager.EnsureSettingsInitialized(a.configManager.GetAuthConfig()); err != nil {
|
||||
return fmt.Errorf("failed to initialize system settings: %w", err)
|
||||
}
|
||||
logrus.Info("System settings initialized in DB.")
|
||||
|
@@ -73,6 +73,8 @@ func (sm *SystemSettingsManager) Initialize(store store.Store, gm groupManager,
|
||||
}
|
||||
}
|
||||
|
||||
settings.ProxyKeysMap = utils.StringToSet(settings.ProxyKeys, ",")
|
||||
|
||||
sm.DisplaySystemConfig(settings)
|
||||
|
||||
return settings, nil
|
||||
@@ -108,7 +110,7 @@ func (sm *SystemSettingsManager) Stop(ctx context.Context) {
|
||||
}
|
||||
|
||||
// EnsureSettingsInitialized 确保数据库中存在所有系统设置的记录。
|
||||
func (sm *SystemSettingsManager) EnsureSettingsInitialized() error {
|
||||
func (sm *SystemSettingsManager) EnsureSettingsInitialized(authConfig types.AuthConfig) error {
|
||||
defaultSettings := utils.DefaultSystemSettings()
|
||||
metadata := utils.GenerateSettingsMetadata(&defaultSettings)
|
||||
|
||||
@@ -128,6 +130,11 @@ func (sm *SystemSettingsManager) EnsureSettingsInitialized() error {
|
||||
}
|
||||
value = fmt.Sprintf("http://%s:%s", host, port)
|
||||
}
|
||||
|
||||
if meta.Key == "proxy_keys" {
|
||||
value = authConfig.Key
|
||||
}
|
||||
|
||||
setting := models.SystemSetting{
|
||||
SettingKey: meta.Key,
|
||||
SettingValue: value,
|
||||
|
@@ -211,6 +211,7 @@ func (s *Server) CreateGroup(c *gin.Context) {
|
||||
ValidationEndpoint: validationEndpoint,
|
||||
ParamOverrides: req.ParamOverrides,
|
||||
Config: cleanedConfig,
|
||||
ProxyKeys: strings.TrimSpace(req.ProxyKeys),
|
||||
}
|
||||
|
||||
if err := s.DB.Create(&group).Error; err != nil {
|
||||
@@ -253,6 +254,7 @@ type GroupUpdateRequest struct {
|
||||
ValidationEndpoint *string `json:"validation_endpoint,omitempty"`
|
||||
ParamOverrides map[string]any `json:"param_overrides"`
|
||||
Config map[string]any `json:"config"`
|
||||
ProxyKeys *string `json:"proxy_keys,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateGroup handles updating an existing group.
|
||||
@@ -351,6 +353,10 @@ func (s *Server) UpdateGroup(c *gin.Context) {
|
||||
group.Config = cleanedConfig
|
||||
}
|
||||
|
||||
if req.ProxyKeys != nil {
|
||||
group.ProxyKeys = strings.TrimSpace(*req.ProxyKeys)
|
||||
}
|
||||
|
||||
// Save the updated group object
|
||||
if err := tx.Save(&group).Error; err != nil {
|
||||
response.Error(c, app_errors.ParseDBError(err))
|
||||
@@ -382,6 +388,7 @@ type GroupResponse struct {
|
||||
ValidationEndpoint string `json:"validation_endpoint"`
|
||||
ParamOverrides datatypes.JSONMap `json:"param_overrides"`
|
||||
Config datatypes.JSONMap `json:"config"`
|
||||
ProxyKeys string `json:"proxy_keys"`
|
||||
LastValidatedAt *time.Time `json:"last_validated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@@ -412,6 +419,7 @@ func (s *Server) newGroupResponse(group *models.Group) *GroupResponse {
|
||||
ValidationEndpoint: group.ValidationEndpoint,
|
||||
ParamOverrides: group.ParamOverrides,
|
||||
Config: group.Config,
|
||||
ProxyKeys: group.ProxyKeys,
|
||||
LastValidatedAt: group.LastValidatedAt,
|
||||
CreatedAt: group.CreatedAt,
|
||||
UpdatedAt: group.UpdatedAt,
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"gpt-load/internal/models"
|
||||
"gpt-load/internal/response"
|
||||
"gpt-load/internal/utils"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -51,6 +52,14 @@ func (s *Server) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Sanitize proxy_keys input
|
||||
if proxyKeys, ok := settingsMap["proxy_keys"]; ok {
|
||||
if proxyKeysStr, ok := proxyKeys.(string); ok {
|
||||
cleanedKeys := utils.SplitAndTrim(proxyKeysStr, ",")
|
||||
settingsMap["proxy_keys"] = strings.Join(cleanedKeys, ",")
|
||||
}
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
if err := s.SettingsManager.UpdateSettings(settingsMap); err != nil {
|
||||
response.Error(c, app_errors.NewAPIError(app_errors.ErrDatabase, err.Error()))
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
app_errors "gpt-load/internal/errors"
|
||||
"gpt-load/internal/response"
|
||||
"gpt-load/internal/services"
|
||||
"gpt-load/internal/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -135,6 +136,41 @@ func Auth(authConfig types.AuthConfig) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyAuth
|
||||
func ProxyAuth(gm *services.GroupManager) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Check key
|
||||
key := extractAuthKey(c)
|
||||
if key == "" {
|
||||
response.Error(c, app_errors.ErrUnauthorized)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
group, err := gm.GetGroupByName(c.Param("group_name"))
|
||||
if err != nil {
|
||||
response.Error(c, app_errors.NewAPIError(app_errors.ErrInternalServer, "Failed to retrieve proxy group"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Then check System-wide keys (O(1) lookup)
|
||||
if _, ok := group.EffectiveConfig.ProxyKeysMap[key]; ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Check Group keys first (O(1) lookup)
|
||||
if _, ok := group.ProxyKeysMap[key]; ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
response.Error(c, app_errors.ErrUnauthorized)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
// Recovery creates a recovery middleware with custom error handling
|
||||
func Recovery() gin.HandlerFunc {
|
||||
return gin.CustomRecovery(func(c *gin.Context, recovered any) {
|
||||
|
@@ -45,6 +45,7 @@ type Group struct {
|
||||
Name string `gorm:"type:varchar(255);not null;unique" json:"name"`
|
||||
Endpoint string `gorm:"-" json:"endpoint"`
|
||||
DisplayName string `gorm:"type:varchar(255)" json:"display_name"`
|
||||
ProxyKeys string `gorm:"type:text" json:"proxy_keys"`
|
||||
Description string `gorm:"type:varchar(512)" json:"description"`
|
||||
Upstreams datatypes.JSON `gorm:"type:json;not null" json:"upstreams"`
|
||||
ValidationEndpoint string `gorm:"type:varchar(255)" json:"validation_endpoint"`
|
||||
@@ -57,6 +58,9 @@ type Group struct {
|
||||
LastValidatedAt *time.Time `json:"last_validated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// For cache
|
||||
ProxyKeysMap map[string]struct{} `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// APIKey 对应 api_keys 表
|
||||
|
@@ -2,7 +2,6 @@ package router
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"gpt-load/internal/channel"
|
||||
"gpt-load/internal/handler"
|
||||
"gpt-load/internal/middleware"
|
||||
"gpt-load/internal/proxy"
|
||||
@@ -43,7 +42,6 @@ func NewRouter(
|
||||
proxyServer *proxy.ProxyServer,
|
||||
configManager types.ConfigManager,
|
||||
groupManager *services.GroupManager,
|
||||
channelFactory *channel.Factory,
|
||||
buildFS embed.FS,
|
||||
indexPage []byte,
|
||||
) *gin.Engine {
|
||||
@@ -66,7 +64,7 @@ func NewRouter(
|
||||
// 注册路由
|
||||
registerSystemRoutes(router, serverHandler)
|
||||
registerAPIRoutes(router, serverHandler, configManager)
|
||||
registerProxyRoutes(router, proxyServer, configManager)
|
||||
registerProxyRoutes(router, proxyServer, groupManager)
|
||||
registerFrontendRoutes(router, buildFS, indexPage)
|
||||
|
||||
return router
|
||||
@@ -159,12 +157,11 @@ func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Ser
|
||||
func registerProxyRoutes(
|
||||
router *gin.Engine,
|
||||
proxyServer *proxy.ProxyServer,
|
||||
configManager types.ConfigManager,
|
||||
groupManager *services.GroupManager,
|
||||
) {
|
||||
proxyGroup := router.Group("/proxy")
|
||||
authConfig := configManager.GetAuthConfig()
|
||||
|
||||
proxyGroup.Use(middleware.Auth(authConfig))
|
||||
proxyGroup.Use(middleware.ProxyAuth(groupManager))
|
||||
|
||||
proxyGroup.Any("/:group_name/*path", proxyServer.HandleProxy)
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"gpt-load/internal/models"
|
||||
"gpt-load/internal/store"
|
||||
"gpt-load/internal/syncer"
|
||||
"gpt-load/internal/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
@@ -47,6 +48,7 @@ func (gm *GroupManager) Initialize() error {
|
||||
for _, group := range groups {
|
||||
g := *group
|
||||
g.EffectiveConfig = gm.settingsManager.GetEffectiveConfig(g.Config)
|
||||
g.ProxyKeysMap = utils.StringToSet(g.ProxyKeys, ",")
|
||||
groupMap[g.Name] = &g
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"group_name": g.Name,
|
||||
|
@@ -21,6 +21,7 @@ type SystemSettings struct {
|
||||
AppUrl string `json:"app_url" default:"http://localhost:3001" name:"项目地址" category:"基础参数" desc:"项目的基础 URL,用于拼接分组终端节点地址。系统配置优先于环境变量 APP_URL。"`
|
||||
RequestLogRetentionDays int `json:"request_log_retention_days" default:"7" name:"日志保留时长(天)" category:"基础参数" desc:"请求日志在数据库中的保留天数,0为不清理日志。" validate:"min=0"`
|
||||
RequestLogWriteIntervalMinutes int `json:"request_log_write_interval_minutes" default:"1" name:"日志延迟写入周期(分钟)" category:"基础参数" desc:"请求日志从缓存写入数据库的周期(分钟),0为实时写入数据。" validate:"min=0"`
|
||||
ProxyKeys string `json:"proxy_keys" name:"全局代理密钥" category:"基础参数" desc:"全局代理密钥,用于访问所有分组的代理端点。多个密钥请用逗号分隔。"`
|
||||
|
||||
// 请求设置
|
||||
RequestTimeout int `json:"request_timeout" default:"600" name:"请求超时(秒)" category:"请求设置" desc:"转发请求的完整生命周期超时(秒)等。" validate:"min=1"`
|
||||
@@ -36,6 +37,9 @@ type SystemSettings struct {
|
||||
KeyValidationIntervalMinutes int `json:"key_validation_interval_minutes" default:"60" name:"密钥验证间隔(分钟)" category:"密钥配置" desc:"后台验证密钥的默认间隔(分钟)。" validate:"min=30"`
|
||||
KeyValidationConcurrency int `json:"key_validation_concurrency" default:"10" name:"密钥验证并发数" category:"密钥配置" desc:"后台定时验证无效 Key 时的并发数。" validate:"min=1"`
|
||||
KeyValidationTimeoutSeconds int `json:"key_validation_timeout_seconds" default:"20" name:"密钥验证超时(秒)" category:"密钥配置" desc:"后台定时验证单个 Key 时的 API 请求超时时间(秒)。" validate:"min=5"`
|
||||
|
||||
// For cache
|
||||
ProxyKeysMap map[string]struct{} `json:"-"`
|
||||
}
|
||||
|
||||
// ServerConfig represents server configuration
|
||||
|
@@ -23,7 +23,7 @@ func GenerateSettingsMetadata(s *types.SystemSettings) []models.SystemSettingInf
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package utils
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MaskAPIKey masks an API key for safe logging.
|
||||
func MaskAPIKey(key string) string {
|
||||
@@ -18,3 +21,36 @@ func TruncateString(s string, maxLength int) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SplitAndTrim splits a string by a separator
|
||||
func SplitAndTrim(s string, sep string) []string {
|
||||
if s == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
parts := strings.Split(s, sep)
|
||||
result := make([]string, 0, len(parts))
|
||||
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// StringToSet converts a separator-delimited string into a set
|
||||
func StringToSet(s string, sep string) map[string]struct{} {
|
||||
parts := SplitAndTrim(s, sep)
|
||||
if len(parts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
set := make(map[string]struct{}, len(parts))
|
||||
for _, part := range parts {
|
||||
set[part] = struct{}{}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ interface GroupFormData {
|
||||
param_overrides: string;
|
||||
config: Record<string, number>;
|
||||
configItems: ConfigItem[];
|
||||
proxy_keys: string;
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
@@ -79,6 +80,7 @@ const formData = reactive<GroupFormData>({
|
||||
param_overrides: "",
|
||||
config: {},
|
||||
configItems: [] as ConfigItem[],
|
||||
proxy_keys: "",
|
||||
});
|
||||
|
||||
const channelTypeOptions = ref<{ label: string; value: string }[]>([]);
|
||||
@@ -269,6 +271,7 @@ function resetForm() {
|
||||
param_overrides: "",
|
||||
config: {},
|
||||
configItems: [],
|
||||
proxy_keys: "",
|
||||
});
|
||||
|
||||
// 重置用户修改状态追踪
|
||||
@@ -304,6 +307,7 @@ function loadGroupData() {
|
||||
param_overrides: JSON.stringify(props.group.param_overrides || {}, null, 2),
|
||||
config: {},
|
||||
configItems,
|
||||
proxy_keys: props.group.proxy_keys || "",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -408,6 +412,7 @@ async function handleSubmit() {
|
||||
validation_endpoint: formData.validation_endpoint,
|
||||
param_overrides: paramOverrides,
|
||||
config,
|
||||
proxy_keys: formData.proxy_keys,
|
||||
};
|
||||
|
||||
let res: Group;
|
||||
@@ -592,6 +597,25 @@ async function handleSubmit() {
|
||||
<div v-else class="form-item-half" />
|
||||
</div>
|
||||
|
||||
<!-- 代理密钥 -->
|
||||
<n-form-item label="代理密钥" path="proxy_keys">
|
||||
<template #label>
|
||||
<div class="form-label-with-tooltip">
|
||||
代理密钥
|
||||
<n-tooltip trigger="hover" placement="top">
|
||||
<template #trigger>
|
||||
<n-icon :component="HelpCircleOutline" class="help-icon" />
|
||||
</template>
|
||||
分组专用代理密钥,用于访问此分组的代理端点。多个密钥请用逗号分隔。
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<n-input
|
||||
v-model:value="formData.proxy_keys"
|
||||
placeholder="多个密钥请用英文逗号 , 分隔"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 描述独占一行 -->
|
||||
<n-form-item label="描述" path="description">
|
||||
<template #label>
|
||||
|
@@ -43,6 +43,7 @@ export interface Group {
|
||||
api_keys?: APIKey[];
|
||||
endpoint?: string;
|
||||
param_overrides: Record<string, unknown>;
|
||||
proxy_keys: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
@@ -72,8 +72,12 @@ async function handleSubmit() {
|
||||
hoverable
|
||||
bordered
|
||||
>
|
||||
<n-grid :x-gap="24" :y-gap="24" responsive="screen" cols="1 s:2 m:2 l:3 xl:4">
|
||||
<n-grid-item v-for="item in category.settings" :key="item.key">
|
||||
<n-grid :x-gap="24" :y-gap="0" responsive="screen" cols="1 s:2 m:2 l:3 xl:4">
|
||||
<n-grid-item
|
||||
v-for="item in category.settings"
|
||||
:key="item.key"
|
||||
:span="item.key === 'proxy_keys' ? 4 : 1"
|
||||
>
|
||||
<n-form-item
|
||||
:path="item.key"
|
||||
:rule="{ required: true, message: `请输入 ${item.name}` }"
|
||||
|
Reference in New Issue
Block a user