mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat: 添加请求体规则功能和 UI 优化
- 添加 body_rules 支持(set/drop/rename 操作) - 后端 apply_body_rules 函数处理请求体修改 - 保护 model/stream 字段不被修改 - 前端 UI 重构:合并请求头/请求体规则为统一区域 - 使用左边框颜色区分规则类型(H=主题色,B=灰色) Closes #139 Co-authored-by: CC <81609553+hemo94931@users.noreply.github.com>
This commit is contained in:
@@ -56,6 +56,14 @@ class ProviderAuthInfo:
|
||||
# 兼容别名:历史代码使用 SENSITIVE_HEADERS 命名
|
||||
SENSITIVE_HEADERS: frozenset[str] = UPSTREAM_DROP_HEADERS
|
||||
|
||||
# 请求体中受保护的字段(不能被 body_rules 修改)
|
||||
PROTECTED_BODY_FIELDS: frozenset[str] = frozenset(
|
||||
{
|
||||
"model", # 模型名由系统管理
|
||||
"stream", # 流式标志由系统管理
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 测试请求常量与辅助函数
|
||||
@@ -126,6 +134,65 @@ def build_test_request_body(
|
||||
)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 请求体规则应用
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
def apply_body_rules(
|
||||
body: dict[str, Any],
|
||||
rules: list[dict[str, Any]],
|
||||
protected_keys: frozenset[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
应用请求体规则
|
||||
|
||||
支持的规则类型:
|
||||
- set: 设置/覆盖字段 {"action": "set", "path": "metadata", "value": {"custom": "val"}}
|
||||
- drop: 删除字段 {"action": "drop", "path": "unwanted_field"}
|
||||
- rename: 重命名字段 {"action": "rename", "from": "old_key", "to": "new_key"}
|
||||
|
||||
Args:
|
||||
body: 原始请求体
|
||||
rules: 规则列表
|
||||
protected_keys: 受保护的字段(不能被 set/drop/rename 修改)
|
||||
|
||||
Returns:
|
||||
应用规则后的请求体
|
||||
"""
|
||||
if not rules:
|
||||
return body
|
||||
|
||||
# 复制一份,避免修改原始数据
|
||||
result = dict(body)
|
||||
protected = protected_keys or PROTECTED_BODY_FIELDS
|
||||
|
||||
for rule in rules:
|
||||
action = rule.get("action")
|
||||
|
||||
if action == "set":
|
||||
path = rule.get("path", "")
|
||||
value = rule.get("value")
|
||||
if path and path not in protected:
|
||||
result[path] = value
|
||||
|
||||
elif action == "drop":
|
||||
path = rule.get("path", "")
|
||||
if path and path not in protected:
|
||||
result.pop(path, None)
|
||||
|
||||
elif action == "rename":
|
||||
from_key = rule.get("from", "")
|
||||
to_key = rule.get("to", "")
|
||||
if from_key and to_key:
|
||||
# 两个 key 都不能是受保护的
|
||||
if from_key not in protected and to_key not in protected:
|
||||
if from_key in result:
|
||||
result[to_key] = result.pop(from_key)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 请求构建器
|
||||
# ==============================================================================
|
||||
@@ -191,6 +258,12 @@ class RequestBuilder(ABC):
|
||||
mapped_model=mapped_model,
|
||||
is_stream=is_stream,
|
||||
)
|
||||
|
||||
# 应用请求体规则(如果 endpoint 配置了 body_rules)
|
||||
body_rules = getattr(endpoint, "body_rules", None)
|
||||
if body_rules:
|
||||
payload = apply_body_rules(payload, body_rules)
|
||||
|
||||
headers = self.build_headers(
|
||||
original_headers,
|
||||
endpoint,
|
||||
|
||||
@@ -739,6 +739,7 @@ class ProviderEndpoint(Base):
|
||||
|
||||
# 请求配置
|
||||
header_rules = Column(JSON, nullable=True) # 请求头规则 [{action, key, value, from, to}]
|
||||
body_rules = Column(JSON, nullable=True) # 请求体规则 [{action, path, value, from, to}]
|
||||
max_retries = Column(Integer, default=2) # 最大重试次数
|
||||
|
||||
# 状态
|
||||
|
||||
@@ -21,6 +21,15 @@ from src.models.admin_requests import ProxyConfig
|
||||
HeaderRule = dict[str, Any]
|
||||
|
||||
|
||||
# ========== Body Rule 类型定义 ==========
|
||||
# 请求体规则支持三种操作:
|
||||
# - set: 设置/覆盖字段 {"action": "set", "path": "metadata", "value": {"custom": "val"}}
|
||||
# - drop: 删除字段 {"action": "drop", "path": "unwanted_field"}
|
||||
# - rename: 重命名字段 {"action": "rename", "from": "old_key", "to": "new_key"}
|
||||
# 实际验证在 request_builder.py 的 apply_body_rules 中处理
|
||||
BodyRule = dict[str, Any]
|
||||
|
||||
|
||||
# ========== ProviderEndpoint CRUD ==========
|
||||
|
||||
|
||||
@@ -43,6 +52,12 @@ class ProviderEndpointCreate(BaseModel):
|
||||
description="请求头规则列表,支持 set/drop/rename 操作",
|
||||
)
|
||||
|
||||
# 请求体配置
|
||||
body_rules: list[BodyRule] | None = Field(
|
||||
default=None,
|
||||
description="请求体规则列表,支持 set/drop/rename 操作",
|
||||
)
|
||||
|
||||
max_retries: int = Field(default=2, ge=0, le=10, description="最大重试次数")
|
||||
|
||||
# 额外配置
|
||||
@@ -93,6 +108,12 @@ class ProviderEndpointUpdate(BaseModel):
|
||||
description="请求头规则列表,支持 set/drop/rename 操作",
|
||||
)
|
||||
|
||||
# 请求体配置
|
||||
body_rules: list[BodyRule] | None = Field(
|
||||
default=None,
|
||||
description="请求体规则列表,支持 set/drop/rename 操作",
|
||||
)
|
||||
|
||||
max_retries: int | None = Field(default=None, ge=0, le=10, description="最大重试次数")
|
||||
is_active: bool | None = Field(default=None, description="是否启用")
|
||||
config: dict[str, Any] | None = Field(default=None, description="额外配置")
|
||||
@@ -132,6 +153,11 @@ class ProviderEndpointResponse(BaseModel):
|
||||
# 请求头配置
|
||||
header_rules: list[HeaderRule] | None = Field(default=None, description="请求头规则列表")
|
||||
|
||||
# 请求体配置
|
||||
body_rules: list[BodyRule] | None = Field(
|
||||
default=None, description="请求体规则列表"
|
||||
)
|
||||
|
||||
max_retries: int
|
||||
|
||||
# 状态
|
||||
|
||||
Reference in New Issue
Block a user