feat: 添加访问令牌管理功能并升级至 0.2.4

- 新增 Management Token(访问令牌)功能,支持创建、更新、删除和管理
- 前端添加访问令牌管理页面,支持普通用户和管理员
- 后端实现完整的令牌生命周期管理 API
- 添加数据库迁移脚本创建 management_tokens 表
- Nginx 配置添加 gzip 压缩,优化响应传输
- Dialog 组件添加 persistent 属性,防止意外关闭
- 为管理后台 API 添加详细的中文文档注释
- 简化多处类型注解,统一代码风格
This commit is contained in:
fawney19
2026-01-07 14:55:07 +08:00
parent f6a6410626
commit 0061fc04b7
59 changed files with 6265 additions and 648 deletions

View File

@@ -13,12 +13,26 @@ from src.core.key_capabilities import (
)
from src.database import get_db
router = APIRouter(prefix="/api/capabilities", tags=["Capabilities"])
router = APIRouter(prefix="/api/capabilities", tags=["System Catalog"])
@router.get("")
async def list_capabilities():
"""获取所有能力定义"""
"""
获取所有能力定义
返回系统中定义的所有能力capabilities包括用户可配置和系统内部使用的能力。
能力用于描述模型支持的功能特性,如视觉输入、函数调用、流式输出等。
**返回字段**
- capabilities: 能力列表,每个能力包含:
- name: 能力的唯一标识符(如 vision、function_calling
- display_name: 能力的显示名称(如"视觉输入""函数调用"
- short_name: 能力的简短名称(如"视觉""函数"
- description: 能力的详细描述
- match_mode: 匹配模式exact 精确匹配fuzzy 模糊匹配prefix 前缀匹配等)
- config_mode: 配置模式user_configurable 用户可配置system_only 仅系统使用)
"""
return {
"capabilities": [
{
@@ -36,7 +50,21 @@ async def list_capabilities():
@router.get("/user-configurable")
async def list_user_configurable_capabilities():
"""获取用户可配置的能力列表(用于前端展示配置选项)"""
"""
获取用户可配置的能力列表
返回允许用户在 API Key 中配置的能力列表,用于前端展示配置选项。
用户可以通过配置这些能力来限制或指定 API Key 可以访问的模型功能。
**返回字段**
- capabilities: 用户可配置的能力列表,每个能力包含:
- name: 能力的唯一标识符
- display_name: 能力的显示名称
- short_name: 能力的简短名称
- description: 能力的详细描述
- match_mode: 匹配模式exact、fuzzy、prefix 等)
- config_mode: 配置模式(此接口返回的都是 user_configurable
"""
return {
"capabilities": [
{
@@ -60,11 +88,24 @@ async def get_model_supported_capabilities(
"""
获取指定模型支持的能力列表
Args:
model_name: 模型名称(如 claude-sonnet-4-20250514必须是 GlobalModel.name
根据全局模型名称GlobalModel.name查询该模型支持的能力
并返回每个能力的详细定义。只查询活跃的全局模型。
Returns:
模型支持的能力列表,以及每个能力的详细定义
**路径参数**
- model_name: 全局模型名称(如 claude-sonnet-4-20250514必须是 GlobalModel.name
**返回字段**
- model: 查询的模型名称
- global_model_id: 全局模型的 UUID
- global_model_name: 全局模型的标准名称
- supported_capabilities: 该模型支持的能力名称列表
- capability_details: 支持的能力详细信息列表,每个能力包含:
- name: 能力标识符
- display_name: 能力显示名称
- description: 能力描述
- match_mode: 匹配模式
- config_mode: 配置模式
- error: 错误信息(仅在模型不存在时返回)
"""
from src.models.database import GlobalModel

View File

@@ -37,7 +37,7 @@ from src.models.endpoint_models import (
)
from src.services.health.endpoint import EndpointHealthService
router = APIRouter(prefix="/api/public", tags=["Public Catalog"])
router = APIRouter(prefix="/api/public", tags=["System Catalog"])
pipeline = ApiRequestPipeline()
@@ -49,7 +49,29 @@ async def get_public_providers(
limit: int = Query(100, description="返回记录数限制"),
db: Session = Depends(get_db),
):
"""获取提供商列表(用户视图)。"""
"""
获取提供商列表(用户视图)
返回系统中可用的提供商列表,包含提供商的基本信息和统计数据。
默认只返回活跃的提供商。
**查询参数**
- is_active: 可选过滤活跃状态。None 表示只返回活跃提供商True 返回活跃False 返回非活跃
- skip: 跳过的记录数,用于分页,默认 0
- limit: 返回记录数限制,默认 100最大 100
**返回字段**
- id: 提供商唯一标识符
- name: 提供商名称(英文标识)
- display_name: 提供商显示名称
- description: 提供商描述信息
- is_active: 是否活跃
- provider_priority: 提供商优先级
- models_count: 该提供商下的模型总数
- active_models_count: 该提供商下活跃的模型数
- endpoints_count: 该提供商下的端点总数
- active_endpoints_count: 该提供商下活跃的端点数
"""
adapter = PublicProvidersAdapter(is_active=is_active, skip=skip, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.PUBLIC)
@@ -64,6 +86,37 @@ async def get_public_models(
limit: int = Query(100, description="返回记录数限制"),
db: Session = Depends(get_db),
):
"""
获取模型列表(用户视图)
返回系统中可用的模型列表,包含模型的详细信息和定价。
默认只返回活跃提供商下的活跃模型。
**查询参数**
- provider_id: 可选,按提供商 ID 过滤,只返回该提供商下的模型
- is_active: 可选,过滤活跃状态(当前未使用,始终返回活跃模型)
- skip: 跳过的记录数,用于分页,默认 0
- limit: 返回记录数限制,默认 100最大 100
**返回字段**
- id: 模型唯一标识符
- provider_id: 所属提供商 ID
- provider_name: 提供商名称
- provider_display_name: 提供商显示名称
- name: 模型统一名称(优先使用 GlobalModel 名称)
- display_name: 模型显示名称
- description: 模型描述信息
- tags: 模型标签(当前为 null
- icon_url: 模型图标 URL
- input_price_per_1m: 输入价格(每 100 万 token
- output_price_per_1m: 输出价格(每 100 万 token
- cache_creation_price_per_1m: 缓存创建价格(每 100 万 token
- cache_read_price_per_1m: 缓存读取价格(每 100 万 token
- supports_vision: 是否支持视觉输入
- supports_function_calling: 是否支持函数调用
- supports_streaming: 是否支持流式输出
- is_active: 是否活跃
"""
adapter = PublicModelsAdapter(
provider_id=provider_id, is_active=is_active, skip=skip, limit=limit
)
@@ -72,6 +125,19 @@ async def get_public_models(
@router.get("/stats", response_model=ProviderStatsResponse)
async def get_public_stats(request: Request, db: Session = Depends(get_db)):
"""
获取系统统计信息
返回系统的整体统计数据,包括提供商数量、模型数量和支持的 API 格式。
只统计活跃的提供商和模型。
**返回字段**
- total_providers: 活跃提供商总数
- active_providers: 活跃提供商数量(与 total_providers 相同)
- total_models: 活跃模型总数
- active_models: 活跃模型数量(与 total_models 相同)
- supported_formats: 支持的 API 格式列表(如 claude、openai、gemini 等)
"""
adapter = PublicStatsAdapter()
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.PUBLIC)
@@ -84,6 +150,37 @@ async def search_models(
limit: int = Query(20, description="返回记录数限制"),
db: Session = Depends(get_db),
):
"""
搜索模型
根据关键词搜索模型,支持按模型名称、显示名称等字段进行模糊匹配。
只返回活跃提供商下的活跃模型。
**查询参数**
- q: 必填,搜索关键词,支持模糊匹配模型的 provider_model_name、GlobalModel.name 或 GlobalModel.display_name
- provider_id: 可选,按提供商 ID 过滤,只在该提供商下搜索
- limit: 返回记录数限制,默认 20最大值取决于系统配置
**返回字段**
返回符合条件的模型列表,字段与 /api/public/models 接口相同:
- id: 模型唯一标识符
- provider_id: 所属提供商 ID
- provider_name: 提供商名称
- provider_display_name: 提供商显示名称
- name: 模型统一名称
- display_name: 模型显示名称
- description: 模型描述
- tags: 模型标签
- icon_url: 模型图标 URL
- input_price_per_1m: 输入价格(每 100 万 token
- output_price_per_1m: 输出价格(每 100 万 token
- cache_creation_price_per_1m: 缓存创建价格(每 100 万 token
- cache_read_price_per_1m: 缓存读取价格(每 100 万 token
- supports_vision: 是否支持视觉
- supports_function_calling: 是否支持函数调用
- supports_streaming: 是否支持流式输出
- is_active: 是否活跃
"""
adapter = PublicSearchModelsAdapter(query=q, provider_id=provider_id, limit=limit)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=ApiMode.PUBLIC)
@@ -95,7 +192,37 @@ async def get_public_api_format_health(
per_format_limit: int = Query(100, ge=10, le=500, description="每个格式的事件数限制"),
db: Session = Depends(get_db),
):
"""获取各 API 格式的健康监控数据(公开版,不含敏感信息)"""
"""
获取各 API 格式的健康监控数据
返回系统中各 API 格式(如 Claude、OpenAI、Gemini的健康状态和历史事件。
公开版本,不包含敏感信息(如 provider_id、key_id 等)。
**查询参数**
- lookback_hours: 回溯的时间范围(小时),默认 6 小时,范围 1-1687 天)
- per_format_limit: 每个 API 格式返回的历史事件数量上限,默认 100范围 10-500
**返回字段**
- generated_at: 响应生成时间
- formats: API 格式健康监控数据列表,每个格式包含:
- api_format: API 格式名称(如 claude、openai、gemini
- api_path: 本站入口路径
- total_attempts: 总请求尝试次数
- success_count: 成功次数
- failed_count: 失败次数
- skipped_count: 跳过次数
- success_rate: 成功率success / (success + failed)
- last_event_at: 最后事件时间
- events: 历史事件列表,按时间倒序,每个事件包含:
- timestamp: 事件时间
- status: 状态success、failed、skipped
- status_code: HTTP 状态码
- latency_ms: 延迟(毫秒)
- error_type: 错误类型(如果失败)
- timeline: 时间线数据,用于展示请求量趋势
- time_range_start: 时间范围起始
- time_range_end: 时间范围结束
"""
adapter = PublicApiFormatHealthMonitorAdapter(
lookback_hours=lookback_hours,
per_format_limit=per_format_limit,
@@ -112,7 +239,30 @@ async def get_public_global_models(
search: Optional[str] = Query(None, description="搜索关键词"),
db: Session = Depends(get_db),
):
"""获取 GlobalModel 列表(用户视图,只读)"""
"""
获取全局模型GlobalModel列表
返回系统定义的全局模型列表,用于统一不同提供商的模型标识。
默认只返回活跃的全局模型。
**查询参数**
- skip: 跳过的记录数,用于分页,默认 0最小 0
- limit: 返回记录数限制,默认 100范围 1-1000
- is_active: 可选过滤活跃状态。None 表示只返回活跃模型True 返回活跃False 返回非活跃
- search: 可选搜索关键词支持模糊匹配模型名称name和显示名称display_name
**返回字段**
- models: 全局模型列表,每个模型包含:
- id: 全局模型唯一标识符UUID
- name: 模型名称(统一标识符)
- display_name: 模型显示名称
- is_active: 是否活跃
- default_price_per_request: 默认的按请求计价配置
- default_tiered_pricing: 默认的阶梯定价配置
- supported_capabilities: 支持的能力列表(如 vision、function_calling 等)
- config: 模型配置信息(如 description、icon_url 等)
- total: 符合条件的模型总数
"""
adapter = PublicGlobalModelsAdapter(
skip=skip,
limit=limit,

View File

@@ -29,7 +29,27 @@ async def create_message(
http_request: Request,
db: Session = Depends(get_db),
):
"""统一入口:根据 x-app 自动在标准/Claude Code 之间切换。"""
"""
Claude Messages API
兼容 Anthropic Claude Messages API 格式的代理接口。
根据请求头 `x-app` 自动在标准 API 和 Claude Code CLI 模式之间切换。
**认证方式**: x-api-key 请求头
**请求格式**:
```json
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}
```
**必需请求头**:
- `x-api-key`: API 密钥
- `anthropic-version`: API 版本(如 2023-06-01
"""
adapter = build_claude_adapter(http_request.headers.get("x-app", ""))
return await pipeline.run(
adapter=adapter,
@@ -45,6 +65,13 @@ async def count_tokens(
http_request: Request,
db: Session = Depends(get_db),
):
"""
Claude Token Count API
计算消息的 Token 数量,用于预估请求成本。
**认证方式**: x-api-key 请求头
"""
adapter = ClaudeTokenCountAdapter()
return await pipeline.run(
adapter=adapter,

View File

@@ -56,9 +56,23 @@ async def generate_content(
db: Session = Depends(get_db),
):
"""
Gemini generateContent 端点
Gemini generateContent API
非流式生成内容请求
兼容 Google Gemini API 格式的代理接口(非流式)。
**认证方式**:
- `x-goog-api-key` 请求头,或
- `?key=` URL 参数
**请求格式**:
```json
{
"contents": [{"parts": [{"text": "Hello"}]}]
}
```
**路径参数**:
- `model`: 模型名称,如 gemini-2.0-flash
"""
# 根据 user-agent 或 x-app header 选择适配器
if _is_cli_request(http_request):
@@ -84,9 +98,16 @@ async def stream_generate_content(
db: Session = Depends(get_db),
):
"""
Gemini streamGenerateContent 端点
Gemini streamGenerateContent API
流式生成内容请求
兼容 Google Gemini API 格式的代理接口(流式)。
**认证方式**:
- `x-goog-api-key` 请求头,或
- `?key=` URL 参数
**路径参数**:
- `model`: 模型名称,如 gemini-2.0-flash
注意: Gemini API 通过 URL 端点区分流式/非流式,不需要在请求体中添加 stream 字段
"""
@@ -114,7 +135,11 @@ async def generate_content_v1(
http_request: Request,
db: Session = Depends(get_db),
):
"""v1 兼容端点"""
"""
Gemini generateContent API (v1 兼容)
v1 版本 API 端点,兼容部分使用旧版路径的 SDK。
"""
return await generate_content(model, http_request, db)
@@ -124,5 +149,9 @@ async def stream_generate_content_v1(
http_request: Request,
db: Session = Depends(get_db),
):
"""v1 兼容端点"""
"""
Gemini streamGenerateContent API (v1 兼容)
v1 版本流式 API 端点,兼容部分使用旧版路径的 SDK。
"""
return await stream_generate_content(model, http_request, db)

View File

@@ -27,7 +27,7 @@ from src.database import get_db
from src.models.database import ApiKey, User
from src.services.auth.service import AuthService
router = APIRouter(tags=["Models API"])
router = APIRouter(tags=["System Catalog"])
# 各格式对应的 API 格式列表
# 注意: CLI 格式是透传格式Models API 只返回非 CLI 格式的端点支持的模型
@@ -395,11 +395,65 @@ async def list_models(
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""
List models - 根据请求头认证方式返回对应格式
列出可用模型(统一端点)
- x-api-key -> Claude 格式
- x-goog-api-key 或 ?key= -> Gemini 格式
- Authorization: Bearer -> OpenAI 格式
根据请求头中的认证方式自动检测 API 格式,并返回相应格式的模型列表。
此接口兼容 Claude、OpenAI 和 Gemini 三种 API 格式
**格式检测规则**
- x-api-key + anthropic-version → Claude 格式
- x-goog-api-key 或 ?key= → Gemini 格式
- Authorization: Bearer → OpenAI 格式(默认)
**查询参数**
Claude 格式:
- before_id: 返回此 ID 之前的结果,用于向前分页
- after_id: 返回此 ID 之后的结果,用于向后分页
- limit: 返回数量限制,默认 20范围 1-1000
Gemini 格式:
- pageSize: 每页数量,默认 50范围 1-1000
- pageToken: 分页 token用于获取下一页
**返回字段**
Claude 格式:
- data: 模型列表,每个模型包含:
- id: 模型标识符
- type: "model"
- display_name: 显示名称
- created_at: 创建时间ISO 8601 格式)
- has_more: 是否有更多结果
- first_id: 当前页第一个模型 ID
- last_id: 当前页最后一个模型 ID
OpenAI 格式:
- object: "list"
- data: 模型列表,每个模型包含:
- id: 模型标识符
- object: "model"
- created: Unix 时间戳
- owned_by: 提供商名称
Gemini 格式:
- models: 模型列表,每个模型包含:
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
- nextPageToken: 下一页的 token如果有更多结果
**错误响应**
401: API Key 无效或未提供(格式根据检测到的 API 格式返回)
"""
api_format, api_key = _detect_api_format_and_key(request)
logger.info(f"[Models] GET /v1/models | format={api_format}")
@@ -440,7 +494,50 @@ async def retrieve_model(
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""
Retrieve model - 根据请求头认证方式返回对应格式
获取单个模型详情(统一端点)
根据请求头中的认证方式自动检测 API 格式,并返回相应格式的模型详情。
此接口兼容 Claude、OpenAI 和 Gemini 三种 API 格式。
**格式检测规则**
- x-api-key + anthropic-version → Claude 格式
- x-goog-api-key 或 ?key= → Gemini 格式
- Authorization: Bearer → OpenAI 格式(默认)
**路径参数**
- model_id: 模型标识符Gemini 格式支持 models/ 前缀,会自动移除)
**返回字段**
Claude 格式:
- id: 模型标识符
- type: "model"
- display_name: 显示名称
- created_at: 创建时间ISO 8601 格式)
OpenAI 格式:
- id: 模型标识符
- object: "model"
- created: Unix 时间戳
- owned_by: 提供商名称
Gemini 格式:
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
**错误响应**
401: API Key 无效或未提供
404: 模型不存在或不可访问
"""
api_format, api_key = _detect_api_format_and_key(request)
@@ -486,7 +583,35 @@ async def list_models_gemini(
page_token: Optional[str] = Query(None, alias="pageToken"),
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""List models (Gemini v1beta 端点)"""
"""
列出可用模型Gemini v1beta 专用端点)
Gemini API 的专用模型列表端点,使用 x-goog-api-key 或 ?key= 参数进行认证。
返回 Gemini 格式的模型列表。
**查询参数**
- pageSize: 每页数量,默认 50范围 1-1000
- pageToken: 分页 token用于获取下一页
**返回字段**
- models: 模型列表,每个模型包含:
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法列表
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
- nextPageToken: 下一页的 token如果有更多结果
**错误响应**
401: API Key 无效或未提供
"""
logger.info("[Models] GET /v1beta/models | format=gemini")
# 从 x-goog-api-key 或 ?key= 提取 API Key
@@ -525,7 +650,33 @@ async def get_model_gemini(
model_name: str,
db: Session = Depends(get_db),
) -> Union[dict, JSONResponse]:
"""Get model (Gemini v1beta 端点)"""
"""
获取单个模型详情Gemini v1beta 专用端点)
Gemini API 的专用模型详情端点,使用 x-goog-api-key 或 ?key= 参数进行认证。
返回 Gemini 格式的模型详情。
**路径参数**
- model_name: 模型名称或资源路径(支持 models/ 前缀,会自动移除)
**返回字段**
- name: 模型资源名称(如 models/gemini-pro
- baseModelId: 基础模型 ID
- version: 版本号
- displayName: 显示名称
- description: 描述信息
- inputTokenLimit: 输入 token 上限
- outputTokenLimit: 输出 token 上限
- supportedGenerationMethods: 支持的生成方法列表
- temperature: 默认温度参数
- maxTemperature: 最大温度参数
- topP: Top-P 参数
- topK: Top-K 参数
**错误响应**
401: API Key 无效或未提供
404: 模型不存在或不可访问
"""
# 移除 "models/" 前缀(如果有)
model_id = model_name[7:] if model_name.startswith("models/") else model_name
logger.info(f"[Models] GET /v1beta/models/{model_id} | format=gemini")

View File

@@ -27,6 +27,24 @@ async def create_chat_completion(
http_request: Request,
db: Session = Depends(get_db),
):
"""
OpenAI Chat Completions API
兼容 OpenAI Chat Completions API 格式的代理接口。
**认证方式**: Bearer TokenAPI Key 或 JWT Token
**请求格式**:
```json
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}
```
**支持的参数**: model, messages, stream, temperature, max_tokens 等标准 OpenAI 参数
"""
adapter = OpenAIChatAdapter()
return await pipeline.run(
adapter=adapter,
@@ -42,6 +60,13 @@ async def create_responses(
http_request: Request,
db: Session = Depends(get_db),
):
"""
OpenAI Responses API (CLI)
兼容 OpenAI Codex CLI 使用的 Responses API 格式,请求透传到上游。
**认证方式**: Bearer TokenAPI Key 或 JWT Token
"""
adapter = OpenAICliAdapter()
return await pipeline.run(
adapter=adapter,