feat(fingerprint): 引入 per-key 请求指纹系统,替代全局 TLS 指纹开关

为每个 ProviderAPIKey 生成并持久化独立的请求指纹配置,涵盖 TLS impersonate
profile、浏览器 UA、Stainless SDK 头部、Node/Chrome/Electron 版本等维度。
指纹基于 key ID 确定性生成,支持手动编辑和批量重新生成。

- 新增 fingerprint 模块:生成、加载、校验、懒持久化
- 数据库迁移:provider_api_keys 新增 fingerprint JSON 列
- 请求链路注入:handler 基类设置上下文指纹,request_builder 和 envelope 消费
- HTTP Client 支持动态 impersonate profile 选择
- Antigravity 适配器使用指纹覆盖 UA/session/Node 版本
- 前端移除手动 TLS 指纹开关,新增批量 regenerate_fingerprint 操作
- 号池管理 UI 优化:token 缩写格式、blocked 行样式、时间显示改为日期格式
This commit is contained in:
fawney19
2026-03-05 02:07:34 +08:00
parent 1d04c41ae7
commit 32ccf61baa
24 changed files with 782 additions and 84 deletions

View File

@@ -27,6 +27,7 @@ from src.core.exceptions import NotFoundException
from src.core.logger import logger
from src.database import get_db
from src.models.database import Provider, ProviderAPIKey, Usage
from src.services.provider.fingerprint import generate_fingerprint
from src.services.provider.pool import redis_ops as pool_redis
from src.services.provider.pool.account_state import resolve_pool_account_state
from src.services.provider.pool.config import parse_pool_config
@@ -143,7 +144,14 @@ async def batch_import_keys(
# POST /api/admin/pool/{provider_id}/keys/batch-action
# ---------------------------------------------------------------------------
ALLOWED_ACTIONS = {"enable", "disable", "delete", "clear_cooldown", "reset_cost"}
ALLOWED_ACTIONS = {
"enable",
"disable",
"delete",
"clear_cooldown",
"reset_cost",
"regenerate_fingerprint",
}
_COOLDOWN_REASON_LABELS: dict[str, str] = {
"rate_limited_429": "429 限流",
@@ -568,7 +576,7 @@ async def batch_action_keys(
request: Request,
db: Session = Depends(get_db),
) -> BatchActionResponse:
"""Batch enable/disable/delete/clear_cooldown/reset_cost on pool keys."""
"""Batch enable/disable/delete/clear_cooldown/reset_cost/regenerate_fingerprint on pool keys."""
adapter = AdminBatchActionKeysAdapter(provider_id=provider_id, body=body)
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
@@ -977,6 +985,11 @@ class AdminListPoolKeysAdapter(AdminApiAdapter):
model_include_patterns=include_patterns,
model_exclude_patterns=exclude_patterns,
proxy=_mask_proxy_password(getattr(k, "proxy", None)),
fingerprint=(
getattr(k, "fingerprint", None)
if isinstance(getattr(k, "fingerprint", None), dict)
else None
),
account_quota=_build_account_quota(
provider_type,
getattr(k, "upstream_metadata", None),
@@ -1035,13 +1048,15 @@ class AdminBatchImportKeysAdapter(AdminApiAdapter):
try:
encrypted_key = crypto_service.encrypt(item.api_key)
new_key_id = str(uuid.uuid4())
new_key = ProviderAPIKey(
id=str(uuid.uuid4()),
id=new_key_id,
provider_id=self.provider_id,
name=item.name or f"imported-{idx}",
api_key=encrypted_key,
auth_type=item.auth_type or "api_key",
proxy=key_proxy,
fingerprint=generate_fingerprint(seed=new_key_id),
is_active=True,
created_at=now,
updated_at=now,
@@ -1136,7 +1151,11 @@ class AdminBatchActionKeysAdapter(AdminApiAdapter):
await pool_redis.clear_cost(pid, kid)
affected += 1
if self.body.action in {"enable", "disable", "delete"}:
elif self.body.action == "regenerate_fingerprint":
key.fingerprint = generate_fingerprint(seed=None)
affected += 1
if self.body.action in {"enable", "disable", "delete", "regenerate_fingerprint"}:
try:
db.commit()
except Exception as exc:
@@ -1150,6 +1169,7 @@ class AdminBatchActionKeysAdapter(AdminApiAdapter):
"delete": "deleted",
"clear_cooldown": "cooldown cleared",
"reset_cost": "cost reset",
"regenerate_fingerprint": "fingerprint regenerated",
}
admin_name = context.user.username if context.user else "admin"

View File

@@ -64,7 +64,6 @@ class PoolSchedulingReason(BaseModel):
detail: str | None = None
class PoolKeyDetail(BaseModel):
"""Detailed status of a single pool key."""
@@ -95,6 +94,7 @@ class PoolKeyDetail(BaseModel):
model_include_patterns: list[str] | None = None
model_exclude_patterns: list[str] | None = None
proxy: dict[str, Any] | None = None
fingerprint: dict[str, Any] | None = None
account_quota: str | None = None
cooldown_reason: str | None = None
cooldown_ttl_seconds: int | None = None
@@ -163,7 +163,7 @@ class BatchImportResponse(BaseModel):
class BatchActionRequest(BaseModel):
key_ids: list[str] = Field(..., max_length=500)
action: str # enable / disable / delete / clear_cooldown / reset_cost
action: str # enable / disable / delete / clear_cooldown / reset_cost / regenerate_fingerprint
class BatchActionResponse(BaseModel):

View File

@@ -1491,8 +1491,11 @@ class AdminImportConfigAdapter(AdminApiAdapter):
)
encrypted_auth_config = crypto_service.encrypt(auth_config_str)
from src.services.provider.fingerprint import generate_fingerprint
new_key_id = str(uuid.uuid4())
new_key = ProviderAPIKey(
id=str(uuid.uuid4()),
id=new_key_id,
provider_id=provider_id,
api_formats=normalized_formats,
auth_type=key_data.get("auth_type", "api_key"),
@@ -1514,6 +1517,7 @@ class AdminImportConfigAdapter(AdminApiAdapter):
model_exclude_patterns=key_data.get("model_exclude_patterns"),
is_active=key_data.get("is_active", True),
proxy=key_data.get("proxy"),
fingerprint=generate_fingerprint(seed=new_key_id),
health_by_format={},
circuit_breaker_by_format={},
)