feat: 上游 Key 唯一化、分组导入跳过、账号导入平台识别&远端校验&base_url 注入

- 上游 Key 命名改为 {prefix}-{upstream.id}-{safe_group_name}-{group_id}
- 唯一约束 (upstream_id, group_id, managed_prefix) 加 managed_prefix 列
- 上游检测成功时同步 Key 状态,远端已删/分组已删自动清理
- 重复分组导入跳过,目标网站已存在同名分组返回 exists
- 账号导入平台自动识别(auto/manual 模式)
- 全选可导入 Key 按钮 + 目标分组自动匹配
- 导入幂等:已导入过的 Key 校验远端账号,不存在则重建
- 新增同步接口 POST /sync-imported-upstream-keys
- account_exists() 通过拉取账号列表判断,避免 404 误判
- credentials.base_url 注入来源上游地址,避免 401
- 前端导入弹窗自动同步+刷新按钮+并发/优先级设置
- 新增 12 个测试覆盖同步、幂等、远端删除、校验失败路径
This commit is contained in:
liumangmang
2026-05-21 01:16:39 +08:00
parent 0a27bba296
commit 6044b00685
18 changed files with 3112 additions and 50 deletions
+36 -1
View File
@@ -1,6 +1,6 @@
from datetime import datetime
from typing import Optional, Any
from pydantic import BaseModel
from pydantic import BaseModel, Field
class AuthConfigBearer(BaseModel):
@@ -89,3 +89,38 @@ class TestResult(BaseModel):
success: bool
message: str
detail: Optional[str] = None
class GenerateKeysByGroupsRequest(BaseModel):
group_ids: list[str] = Field(default_factory=list)
name_prefix: str = "SmartUp"
quota: float = Field(default=0, ge=0)
expires_in_days: Optional[int] = Field(default=None, ge=1)
rate_limit_5h: float = Field(default=0, ge=0)
rate_limit_1d: float = Field(default=0, ge=0)
rate_limit_7d: float = Field(default=0, ge=0)
endpoint: str = "/keys"
class GeneratedUpstreamKeyResponse(BaseModel):
id: Optional[int] = None
upstream_id: int
group_id: str
group_name: str = ""
key_id: Optional[str] = None
key_name: str
key_value: Optional[str] = None
masked_key: str = ""
status: str
error: Optional[str] = None
imported_website_id: Optional[int] = None
imported_account_id: Optional[str] = None
imported_at: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class GenerateKeysByGroupsResponse(BaseModel):
success: bool
message: str
items: list[GeneratedUpstreamKeyResponse]
+55
View File
@@ -122,3 +122,58 @@ class WebsiteSyncLogResponse(BaseModel):
status: str
message: str
created_at: datetime
class ImportGroupsRequest(BaseModel):
group_ids: list[str] = Field(default_factory=list)
name_prefix: str = ""
class ImportGroupItem(BaseModel):
source_group_id: str
source_group_name: str
target_group_id: Optional[str] = None
target_group_name: str = ""
status: str
message: str = ""
raw: dict[str, Any] = {}
class ImportGroupsResponse(BaseModel):
success: bool
message: str
items: list[ImportGroupItem]
class SyncImportStatusRequest(BaseModel):
upstream_id: int = Field(default=0)
class ImportAccountsRequest(BaseModel):
upstream_key_ids: list[int] = Field(default_factory=list)
target_group_map: dict[str, str] = Field(default_factory=dict)
account_name_prefix: str = "SmartUp"
default_platform: str = "openai"
platform_mode: str = "auto" # "auto" | "manual"
concurrency: int = Field(default=10, ge=1)
priority: int = Field(default=1, ge=0)
class ImportAccountItem(BaseModel):
upstream_key_id: int
source_group_id: str
source_group_name: str
target_group_id: Optional[str] = None
account_id: Optional[str] = None
account_name: str = ""
platform: str = ""
upstream_base_url: str = ""
status: str
message: str = ""
raw: dict[str, Any] = {}
class ImportAccountsResponse(BaseModel):
success: bool
message: str
items: list[ImportAccountItem]