feat(config): 采用列表模式

This commit is contained in:
cnlimiter
2026-03-18 14:42:10 +08:00
parent 931ea798cc
commit 23336e26b3
8 changed files with 67 additions and 436 deletions

View File

@@ -484,14 +484,31 @@ def update_proxy_last_used(db: Session, proxy_id: int) -> bool:
def get_random_proxy(db: Session) -> Optional[Proxy]:
"""随机获取一个启用的代理"""
"""随机获取一个启用的代理,优先返回 is_default=True 的代理"""
import random
# 优先返回默认代理
default_proxy = db.query(Proxy).filter(Proxy.enabled == True, Proxy.is_default == True).first()
if default_proxy:
return default_proxy
proxies = get_enabled_proxies(db)
if not proxies:
return None
return random.choice(proxies)
def set_proxy_default(db: Session, proxy_id: int) -> Optional[Proxy]:
"""将指定代理设为默认,同时清除其他代理的默认标记"""
# 清除所有默认标记
db.query(Proxy).filter(Proxy.is_default == True).update({"is_default": False})
# 设置新的默认代理
proxy = db.query(Proxy).filter(Proxy.id == proxy_id).first()
if proxy:
proxy.is_default = True
db.commit()
db.refresh(proxy)
return proxy
def get_proxies_count(db: Session, enabled: Optional[bool] = None) -> int:
"""获取代理数量"""
query = db.query(func.count(Proxy.id))

View File

@@ -156,6 +156,7 @@ class Proxy(Base):
username = Column(String(100))
password = Column(String(255))
enabled = Column(Boolean, default=True)
is_default = Column(Boolean, default=False) # 是否为默认代理
priority = Column(Integer, default=0) # 优先级(保留字段)
last_used = Column(DateTime) # 最后使用时间
created_at = Column(DateTime, default=datetime.utcnow)
@@ -171,6 +172,7 @@ class Proxy(Base):
'port': self.port,
'username': self.username,
'enabled': self.enabled,
'is_default': self.is_default or False,
'priority': self.priority,
'last_used': self.last_used.isoformat() if self.last_used else None,
'created_at': self.created_at.isoformat() if self.created_at else None,

View File

@@ -110,6 +110,7 @@ class DatabaseSessionManager:
("accounts", "subscription_type", "VARCHAR(20)"),
("accounts", "subscription_at", "DATETIME"),
("accounts", "cookies", "TEXT"),
("proxies", "is_default", "BOOLEAN DEFAULT 0"),
]
# 确保新表存在create_tables 已处理,此处兜底)

View File

@@ -347,18 +347,22 @@ def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy:
saved_account = db.query(AccountModel).filter_by(email=result.email).first()
if saved_account and saved_account.access_token:
token_data = generate_token_json(saved_account)
# 解析指定 CPA 服务
# 解析指定 CPA 服务,未指定则取第一个启用的服务
_cpa_api_url = None
_cpa_api_token = None
_svc = None
if cpa_service_id:
try:
_svc = crud.get_cpa_service_by_id(db, cpa_service_id)
if _svc:
_cpa_api_url = _svc.api_url
_cpa_api_token = _svc.api_token
log_callback(f"[CPA] 使用服务: {_svc.name}")
except Exception:
pass
if _svc is None:
svcs = crud.get_cpa_services(db, enabled=True)
_svc = svcs[0] if svcs else None
if _svc:
_cpa_api_url = _svc.api_url
_cpa_api_token = _svc.api_token
log_callback(f"[CPA] 使用服务: {_svc.name}")
cpa_success, cpa_msg = upload_to_cpa(token_data, api_url=_cpa_api_url, api_token=_cpa_api_token)
if cpa_success:
saved_account.cpa_uploaded = True

View File

@@ -111,101 +111,6 @@ async def get_all_settings():
}
@router.get("/proxy")
async def get_proxy_settings():
"""获取代理设置"""
settings = get_settings()
return {
"enabled": settings.proxy_enabled,
"type": settings.proxy_type,
"host": settings.proxy_host,
"port": settings.proxy_port,
"username": settings.proxy_username,
"has_password": bool(settings.proxy_password),
"proxy_url": settings.proxy_url,
}
@router.post("/proxy")
async def update_proxy_settings(request: ProxySettings):
"""更新代理设置"""
update_dict = {
"proxy_enabled": request.enabled,
"proxy_type": request.type,
"proxy_host": request.host,
"proxy_port": request.port,
"proxy_username": request.username,
}
if request.password:
update_dict["proxy_password"] = request.password
update_settings(**update_dict)
return {"success": True, "message": "代理设置已更新"}
@router.post("/proxy/test")
async def test_proxy_settings(request: ProxySettings):
"""测试代理连接"""
import time
from curl_cffi import requests as cffi_requests
# 构建代理 URL
if request.type == "http":
scheme = "http"
elif request.type == "socks5":
scheme = "socks5"
else:
raise HTTPException(status_code=400, detail="不支持的代理类型")
auth = ""
if request.username and request.password:
auth = f"{request.username}:{request.password}@"
proxy_url = f"{scheme}://{auth}{request.host}:{request.port}"
# 测试连接
test_url = "https://api.ipify.org?format=json"
start_time = time.time()
try:
proxies = {
"http": proxy_url,
"https": proxy_url
}
response = cffi_requests.get(
test_url,
proxies=proxies,
timeout=3,
impersonate="chrome110"
)
elapsed_time = time.time() - start_time
if response.status_code == 200:
ip_info = response.json()
return {
"success": True,
"ip": ip_info.get("ip", ""),
"response_time": round(elapsed_time * 1000), # 毫秒
"message": f"代理连接成功,出口 IP: {ip_info.get('ip', 'unknown')}"
}
else:
return {
"success": False,
"message": f"代理返回错误状态码: {response.status_code}"
}
except Exception as e:
return {
"success": False,
"message": f"代理连接失败: {str(e)}"
}
@router.get("/proxy/dynamic")
async def get_dynamic_proxy_settings():
"""获取动态代理设置"""
@@ -639,6 +544,16 @@ async def delete_proxy_item(proxy_id: int):
return {"success": True, "message": "代理已删除"}
@router.post("/proxies/{proxy_id}/set-default")
async def set_proxy_default(proxy_id: int):
"""将指定代理设为默认"""
with get_db() as db:
proxy = crud.set_proxy_default(db, proxy_id)
if not proxy:
raise HTTPException(status_code=404, detail="代理不存在")
return {"success": True, "proxy": proxy.to_dict()}
@router.post("/proxies/{proxy_id}/test")
async def test_proxy_item(proxy_id: int):
"""测试单个代理"""
@@ -774,77 +689,6 @@ async def disable_proxy(proxy_id: int):
return {"success": True, "message": "代理已禁用"}
# ============== CPA 设置 ==============
class CPASettings(BaseModel):
"""CPA 设置"""
enabled: bool = False
api_url: str = ""
api_token: str = ""
class CPATestRequest(BaseModel):
"""CPA 测试请求"""
api_url: str
api_token: str
@router.get("/cpa")
async def get_cpa_settings():
"""获取 CPA 设置"""
settings = get_settings()
return {
"enabled": settings.cpa_enabled,
"api_url": settings.cpa_api_url,
"has_token": bool(settings.cpa_api_token and settings.cpa_api_token.get_secret_value()),
}
@router.post("/cpa")
async def update_cpa_settings(request: CPASettings):
"""更新 CPA 设置"""
update_dict = {
"cpa_enabled": request.enabled,
"cpa_api_url": request.api_url,
}
# 只有提供了 token 才更新
if request.api_token:
update_dict["cpa_api_token"] = request.api_token
update_settings(**update_dict)
return {"success": True, "message": "CPA 设置已更新"}
@router.post("/cpa/test")
async def test_cpa_connection(request: CPATestRequest):
"""测试 CPA 连接"""
from ...core.cpa_upload import test_cpa_connection as do_test
settings = get_settings()
proxy = settings.proxy_url
# 如果传入 'use_saved_token',使用已保存的 token
api_token = request.api_token
if api_token == 'use_saved_token' or not api_token:
if settings.cpa_api_token:
api_token = settings.cpa_api_token.get_secret_value()
else:
return {
"success": False,
"message": "未配置 API Token"
}
success, message = do_test(request.api_url, api_token, proxy)
return {
"success": success,
"message": message
}
# ============== Outlook 设置 ==============
class OutlookSettings(BaseModel):