diff --git a/src/database/crud.py b/src/database/crud.py index 67d827e..c1beb91 100644 --- a/src/database/crud.py +++ b/src/database/crud.py @@ -472,6 +472,13 @@ def delete_proxy(db: Session, proxy_id: int) -> bool: return True +def delete_disabled_proxies(db: Session) -> int: + """删除所有已禁用代理""" + deleted = db.query(Proxy).filter(Proxy.enabled == False).delete(synchronize_session=False) + db.commit() + return deleted + + def update_proxy_last_used(db: Session, proxy_id: int) -> bool: """更新代理最后使用时间""" db_proxy = get_proxy_by_id(db, proxy_id) @@ -713,4 +720,4 @@ def delete_tm_service(db: Session, service_id: int) -> bool: return False db.delete(svc) db.commit() - return True \ No newline at end of file + return True diff --git a/src/web/routes/settings.py b/src/web/routes/settings.py index d096fa4..d89a5f2 100644 --- a/src/web/routes/settings.py +++ b/src/web/routes/settings.py @@ -469,6 +469,60 @@ class ProxyUpdateRequest(BaseModel): priority: Optional[int] = None +def _test_proxy_connectivity(proxy_url: str) -> dict: + """测试代理连通性并返回统一结果。""" + import time + from curl_cffi import requests as cffi_requests + + test_url = "https://api.ipify.org?format=json" + start_time = time.time() + + proxies_dict = { + "http": proxy_url, + "https": proxy_url + } + + response = cffi_requests.get( + test_url, + proxies=proxies_dict, + 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')}" + } + + return { + "success": False, + "message": f"状态码: {response.status_code}" + } + + +def _auto_disable_proxy_on_failure(db, proxy, message: str) -> dict: + """代理测试失败时自动禁用,并返回统一提示。""" + auto_disabled = False + if proxy.enabled: + crud.update_proxy(db, proxy.id, enabled=False) + auto_disabled = True + + final_message = message + if auto_disabled: + final_message = f"{message},已自动禁用" + + return { + "success": False, + "auto_disabled": auto_disabled, + "message": final_message, + } + + @router.get("/proxies") async def get_proxies_list(enabled: Optional[bool] = None): """获取代理列表""" @@ -559,107 +613,59 @@ async def set_proxy_default(proxy_id: int): @router.post("/proxies/{proxy_id}/test") async def test_proxy_item(proxy_id: int): """测试单个代理""" - import time - from curl_cffi import requests as cffi_requests - with get_db() as db: proxy = crud.get_proxy_by_id(db, proxy_id) if not proxy: raise HTTPException(status_code=404, detail="代理不存在") - proxy_url = proxy.proxy_url - 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}" - } - + result = _test_proxy_connectivity(proxy.proxy_url) + if result["success"]: + return result + return _auto_disable_proxy_on_failure(db, proxy, f"代理返回错误状态码: {result['message'].removeprefix('状态码: ')}") except Exception as e: - return { - "success": False, - "message": f"代理连接失败: {str(e)}" - } + return _auto_disable_proxy_on_failure(db, proxy, f"代理连接失败: {str(e)}") @router.post("/proxies/test-all") async def test_all_proxies(): """测试所有启用的代理""" - import time - from curl_cffi import requests as cffi_requests - with get_db() as db: proxies = crud.get_enabled_proxies(db) results = [] + auto_disabled_count = 0 for proxy in proxies: - proxy_url = proxy.proxy_url - test_url = "https://api.ipify.org?format=json" - start_time = time.time() - try: - proxies_dict = { - "http": proxy_url, - "https": proxy_url - } - - response = cffi_requests.get( - test_url, - proxies=proxies_dict, - timeout=3, - impersonate="chrome110" - ) - - elapsed_time = time.time() - start_time - - if response.status_code == 200: - ip_info = response.json() + result = _test_proxy_connectivity(proxy.proxy_url) + if result["success"]: results.append({ "id": proxy.id, "name": proxy.name, "success": True, - "ip": ip_info.get("ip", ""), - "response_time": round(elapsed_time * 1000) + "ip": result.get("ip", ""), + "response_time": result.get("response_time"), + "auto_disabled": False, }) else: + failure_result = _auto_disable_proxy_on_failure( + db, + proxy, + f"代理返回错误状态码: {result['message'].removeprefix('状态码: ')}" + ) + auto_disabled_count += 1 if failure_result["auto_disabled"] else 0 results.append({ "id": proxy.id, "name": proxy.name, - "success": False, - "message": f"状态码: {response.status_code}" + **failure_result, }) - except Exception as e: + failure_result = _auto_disable_proxy_on_failure(db, proxy, f"代理连接失败: {str(e)}") + auto_disabled_count += 1 if failure_result["auto_disabled"] else 0 results.append({ "id": proxy.id, "name": proxy.name, - "success": False, - "message": str(e) + **failure_result, }) success_count = sum(1 for r in results if r["success"]) @@ -667,6 +673,7 @@ async def test_all_proxies(): "total": len(proxies), "success": success_count, "failed": len(proxies) - success_count, + "auto_disabled": auto_disabled_count, "results": results } @@ -691,6 +698,14 @@ async def disable_proxy(proxy_id: int): return {"success": True, "message": "代理已禁用"} +@router.delete("/proxies/disabled/batch-delete") +async def delete_disabled_proxies(): + """批量删除所有已禁用代理""" + with get_db() as db: + deleted = crud.delete_disabled_proxies(db) + return {"success": True, "deleted": deleted, "message": f"已删除 {deleted} 个禁用代理"} + + # ============== Outlook 设置 ============== class OutlookSettings(BaseModel): diff --git a/static/js/settings.js b/static/js/settings.js index 7835098..65eeffe 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -31,6 +31,7 @@ const elements = { proxiesTable: document.getElementById('proxies-table'), addProxyBtn: document.getElementById('add-proxy-btn'), testAllProxiesBtn: document.getElementById('test-all-proxies-btn'), + deleteDisabledProxiesBtn: document.getElementById('delete-disabled-proxies-btn'), addProxyModal: document.getElementById('add-proxy-modal'), proxyItemForm: document.getElementById('proxy-item-form'), closeProxyModal: document.getElementById('close-proxy-modal'), @@ -206,6 +207,10 @@ function initEventListeners() { elements.testAllProxiesBtn.addEventListener('click', handleTestAllProxies); } + if (elements.deleteDisabledProxiesBtn) { + elements.deleteDisabledProxiesBtn.addEventListener('click', handleDeleteDisabledProxies); + } + if (elements.closeProxyModal) { elements.closeProxyModal.addEventListener('click', closeProxyModal); } @@ -767,11 +772,13 @@ async function loadProxies() { try { const data = await api.get('/settings/proxies'); renderProxies(data.proxies); + updateProxyBulkActions(data.proxies || []); } catch (error) { console.error('加载代理列表失败:', error); + updateProxyBulkActions([]); elements.proxiesTable.innerHTML = `