Add default remote proxy support for Sub2API services.

Let Sub2API service settings store and load a default remote proxy so uploads can reuse the target service's remote proxy bindings without forcing users to pick one each time.
This commit is contained in:
Jack committed 2026-03-24 09:37:33 +08:00
1 parent dfebd9a780
commit 6a875bfced
10 files changed
+240 -24

No files matched your search

+3 -1
View File
@@ -596,13 +596,15 @@ def create_sub2api_service(
api_url: str,
api_key: str,
enabled: bool = True,
priority: int = 0
priority: int = 0,
default_remote_proxy_id: Optional[int] = None,
) -> Sub2ApiService:
"""创建 Sub2API 服务配置"""
svc = Sub2ApiService(
name=name,
api_url=api_url,
api_key=api_key,
default_remote_proxy_id=default_remote_proxy_id,
enabled=enabled,
priority=priority,
)
+1
View File
@@ -152,6 +152,7 @@ class Sub2ApiService(Base):
name = Column(String(100), nullable=False) # 服务名称
api_url = Column(String(500), nullable=False) # API URL (host)
api_key = Column(Text, nullable=False) # x-api-key
default_remote_proxy_id = Column(Integer, nullable=True) # 默认远端代理 ID
enabled = Column(Boolean, default=True)
priority = Column(Integer, default=0) # 优先级
created_at = Column(DateTime, default=datetime.utcnow)
+1
View File
@@ -111,6 +111,7 @@ class DatabaseSessionManager:
("accounts", "subscription_at", "DATETIME"),
("accounts", "cookies", "TEXT"),
("proxies", "is_default", "BOOLEAN DEFAULT 0"),
("sub2api_services", "default_remote_proxy_id", "INTEGER"),
]
# 确保新表存在(create_tables 已处理,此处兜底)
+17 -9
View File
@@ -819,6 +819,7 @@ async def batch_upload_accounts_to_sub2api(request: BatchSub2ApiUploadRequest):
# 解析指定的 Sub2API 服务
api_url = None
api_key = None
svc = None
if request.service_id:
with get_db() as db:
svc = crud.get_sub2api_service_by_id(db, request.service_id)
@@ -830,10 +831,11 @@ async def batch_upload_accounts_to_sub2api(request: BatchSub2ApiUploadRequest):
with get_db() as db:
svcs = crud.get_sub2api_services(db, enabled=True)
if svcs:
api_url = svcs[0].api_url
api_key = svcs[0].api_key
svc = svcs[0]
api_url = svc.api_url
api_key = svc.api_key
if not api_url or not api_key:
if not api_url or not api_key or not svc:
raise HTTPException(status_code=400, detail="未找到可用的 Sub2API 服务,请先在设置中配置")
with get_db() as db:
@@ -842,11 +844,13 @@ async def batch_upload_accounts_to_sub2api(request: BatchSub2ApiUploadRequest):
request.status_filter, request.email_service_filter, request.search_filter
)
selected_proxy_id = request.proxy_id if "proxy_id" in request.model_fields_set else svc.default_remote_proxy_id
results = batch_upload_to_sub2api(
ids, api_url, api_key,
concurrency=request.concurrency,
priority=request.priority,
proxy_id=request.proxy_id,
proxy_id=selected_proxy_id,
)
return results
@@ -858,10 +862,11 @@ async def upload_account_to_sub2api(account_id: int, request: Optional[Sub2ApiUp
service_id = request.service_id if request else None
concurrency = request.concurrency if request else 3
priority = request.priority if request else 50
proxy_id = request.proxy_id if request else None
proxy_id = request.proxy_id if request and "proxy_id" in request.model_fields_set else None
api_url = None
api_key = None
svc = None
if service_id:
with get_db() as db:
svc = crud.get_sub2api_service_by_id(db, service_id)
@@ -873,10 +878,11 @@ async def upload_account_to_sub2api(account_id: int, request: Optional[Sub2ApiUp
with get_db() as db:
svcs = crud.get_sub2api_services(db, enabled=True)
if svcs:
api_url = svcs[0].api_url
api_key = svcs[0].api_key
svc = svcs[0]
api_url = svc.api_url
api_key = svc.api_key
if not api_url or not api_key:
if not api_url or not api_key or not svc:
raise HTTPException(status_code=400, detail="未找到可用的 Sub2API 服务,请先在设置中配置")
with get_db() as db:
@@ -888,7 +894,9 @@ async def upload_account_to_sub2api(account_id: int, request: Optional[Sub2ApiUp
success, message = upload_to_sub2api(
[account], api_url, api_key,
concurrency=concurrency, priority=priority, proxy_id=proxy_id
concurrency=concurrency,
priority=priority,
proxy_id=proxy_id if request and "proxy_id" in request.model_fields_set else svc.default_remote_proxy_id,
)
if success:
return {"success": True, "message": message}
+6 -1
View File
@@ -462,7 +462,12 @@ def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy:
if not _svc:
continue
log_callback(f"[Sub2API] 正在把账号发往服务站: {_svc.name}")
_ok, _msg = upload_to_sub2api([saved_account], _svc.api_url, _svc.api_key)
_ok, _msg = upload_to_sub2api(
[saved_account],
_svc.api_url,
_svc.api_key,
proxy_id=_svc.default_remote_proxy_id,
)
log_callback(f"[Sub2API] {'成功' if _ok else '失败'}({_svc.name}): {_msg}")
except Exception as _e:
log_callback(f"[Sub2API] 异常({_sid}): {_e}")
+59 -4
View File
@@ -23,6 +23,7 @@ class Sub2ApiServiceCreate(BaseModel):
name: str
api_url: str
api_key: str
default_remote_proxy_id: Optional[int] = None
enabled: bool = True
priority: int = 0
@@ -31,6 +32,7 @@ class Sub2ApiServiceUpdate(BaseModel):
name: Optional[str] = None
api_url: Optional[str] = None
api_key: Optional[str] = None
default_remote_proxy_id: Optional[int] = None
enabled: Optional[bool] = None
priority: Optional[int] = None
@@ -40,6 +42,7 @@ class Sub2ApiServiceResponse(BaseModel):
name: str
api_url: str
has_key: bool
default_remote_proxy_id: Optional[int] = None
enabled: bool
priority: int
created_at: Optional[str] = None
@@ -50,8 +53,10 @@ class Sub2ApiServiceResponse(BaseModel):
class Sub2ApiTestRequest(BaseModel):
service_id: Optional[int] = None
api_url: Optional[str] = None
api_key: Optional[str] = None
default_remote_proxy_id: Optional[int] = None
class Sub2ApiUploadRequest(BaseModel):
@@ -83,6 +88,7 @@ def _to_response(svc) -> Sub2ApiServiceResponse:
name=svc.name,
api_url=svc.api_url,
has_key=bool(svc.api_key),
default_remote_proxy_id=svc.default_remote_proxy_id,
enabled=svc.enabled,
priority=svc.priority,
created_at=svc.created_at.isoformat() if svc.created_at else None,
@@ -119,6 +125,23 @@ def _to_remote_proxy_response(proxy: dict) -> Sub2ApiRemoteProxyResponse:
)
def _resolve_temp_sub2api_request(request: Sub2ApiTestRequest):
svc = None
if request.service_id is not None:
with get_db() as db:
svc = crud.get_sub2api_service_by_id(db, request.service_id)
if not svc:
raise HTTPException(status_code=404, detail="Sub2API 服务不存在")
api_url = (request.api_url or (svc.api_url if svc else "")).strip()
api_key = (request.api_key or (svc.api_key if svc else "")).strip()
if not api_url or not api_key:
raise HTTPException(status_code=400, detail="api_url 和 api_key 不能为空")
return svc, api_url, api_key
# ============== API Endpoints ==============
@router.get("", response_model=List[Sub2ApiServiceResponse])
@@ -140,6 +163,7 @@ async def create_sub2api_service(request: Sub2ApiServiceCreate):
api_key=request.api_key,
enabled=request.enabled,
priority=request.priority,
default_remote_proxy_id=request.default_remote_proxy_id,
)
return _to_response(svc)
@@ -161,6 +185,33 @@ async def list_remote_sub2api_proxies(service_id: Optional[int] = None):
)
@router.post("/remote-proxies", response_model=Sub2ApiRemoteProxyListResponse)
async def list_remote_sub2api_proxies_direct(request: Sub2ApiTestRequest):
"""按临时 Sub2API 配置拉取远端代理列表(用于新增前选择默认代理)"""
svc, api_url, api_key = _resolve_temp_sub2api_request(request)
try:
proxies = fetch_remote_sub2api_proxies(api_url, api_key)
proxy_items = [_to_remote_proxy_response(proxy) for proxy in proxies]
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return Sub2ApiRemoteProxyListResponse(
service=_to_response(svc) if svc else Sub2ApiServiceResponse(
id=0,
name="未保存服务",
api_url=api_url,
has_key=bool(api_key),
default_remote_proxy_id=request.default_remote_proxy_id,
enabled=True,
priority=0,
created_at=None,
updated_at=None,
),
proxies=proxy_items,
)
@router.get("/{service_id}", response_model=Sub2ApiServiceResponse)
async def get_sub2api_service(service_id: int):
"""获取单个 Sub2API 服务详情"""
@@ -183,6 +234,7 @@ async def get_sub2api_service_full(service_id: int):
"name": svc.name,
"api_url": svc.api_url,
"api_key": svc.api_key,
"default_remote_proxy_id": svc.default_remote_proxy_id,
"enabled": svc.enabled,
"priority": svc.priority,
}
@@ -204,6 +256,8 @@ async def update_sub2api_service(service_id: int, request: Sub2ApiServiceUpdate)
# api_key 留空则保持原值
if request.api_key:
update_data["api_key"] = request.api_key
if "default_remote_proxy_id" in request.model_fields_set:
update_data["default_remote_proxy_id"] = request.default_remote_proxy_id
if request.enabled is not None:
update_data["enabled"] = request.enabled
if request.priority is not None:
@@ -238,9 +292,8 @@ async def test_sub2api_service(service_id: int):
@router.post("/test-connection")
async def test_sub2api_connection_direct(request: Sub2ApiTestRequest):
"""直接测试 Sub2API 连接(用于添加前验证)"""
if not request.api_url or not request.api_key:
raise HTTPException(status_code=400, detail="api_url 和 api_key 不能为空")
success, message = test_sub2api_connection(request.api_url, request.api_key)
_svc, api_url, api_key = _resolve_temp_sub2api_request(request)
success, message = test_sub2api_connection(api_url, api_key)
return {"success": success, "message": message}
@@ -263,12 +316,14 @@ async def upload_accounts_to_sub2api(request: Sub2ApiUploadRequest):
api_url = svc.api_url
api_key = svc.api_key
selected_proxy_id = request.proxy_id if "proxy_id" in request.model_fields_set else svc.default_remote_proxy_id
results = batch_upload_to_sub2api(
request.account_ids,
api_url,
api_key,
concurrency=request.concurrency,
priority=request.priority,
proxy_id=request.proxy_id,
proxy_id=selected_proxy_id,
)
return results