From 23336e26b368b571c4ecafdd904a3fd56a1d5dd0 Mon Sep 17 00:00:00 2001 From: cnlimiter Date: Wed, 18 Mar 2026 14:42:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E9=87=87=E7=94=A8=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/crud.py | 19 +++- src/database/models.py | 2 + src/database/session.py | 1 + src/web/routes/registration.py | 14 ++- src/web/routes/settings.py | 176 ++------------------------------ static/js/app.js | 13 ++- static/js/settings.js | 181 ++++----------------------------- templates/settings.html | 97 +----------------- 8 files changed, 67 insertions(+), 436 deletions(-) diff --git a/src/database/crud.py b/src/database/crud.py index 6a29a3c..2e11508 100644 --- a/src/database/crud.py +++ b/src/database/crud.py @@ -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)) diff --git a/src/database/models.py b/src/database/models.py index 87926c6..7e4f52f 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -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, diff --git a/src/database/session.py b/src/database/session.py index 1d3a36c..08fd1ae 100644 --- a/src/database/session.py +++ b/src/database/session.py @@ -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 已处理,此处兜底) diff --git a/src/web/routes/registration.py b/src/web/routes/registration.py index a7297f6..f3d0aa0 100644 --- a/src/web/routes/registration.py +++ b/src/web/routes/registration.py @@ -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 diff --git a/src/web/routes/settings.py b/src/web/routes/settings.py index 0762ab3..63bc107 100644 --- a/src/web/routes/settings.py +++ b/src/web/routes/settings.py @@ -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): diff --git a/static/js/app.js b/static/js/app.js index 7069223..bdac53d 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -102,19 +102,19 @@ document.addEventListener('DOMContentLoaded', () => { // 检查 CPA 是否启用,未启用则禁用复选框;同时加载 CPA 服务列表 async function checkCpaEnabled() { if (!elements.autoUploadCpa) return; + // 加载 CPA 服务列表,列表为空则禁用复选框 + await loadCpaServiceOptions(); try { - const data = await api.get('/settings/cpa'); - if (!data.enabled) { + const services = await api.get('/cpa-services?enabled=true'); + if (!services || services.length === 0) { elements.autoUploadCpa.disabled = true; - elements.autoUploadCpa.title = '请先在设置中启用 CPA 上传'; + elements.autoUploadCpa.title = '请先在设置中添加 CPA 服务'; const label = elements.autoUploadCpa.closest('label'); if (label) label.style.opacity = '0.5'; } } catch (e) { elements.autoUploadCpa.disabled = true; } - // 加载 CPA 服务列表 - await loadCpaServiceOptions(); // 复选框联动显示/隐藏服务选择器 if (elements.autoUploadCpa) { elements.autoUploadCpa.addEventListener('change', () => { @@ -130,8 +130,7 @@ async function loadCpaServiceOptions() { if (!elements.cpaServiceSelect) return; try { const services = await api.get('/cpa-services?enabled=true'); - // 保留「使用全局配置」选项 - const defaultOpt = ''; + const defaultOpt = ''; const opts = services.map(s => `