From 36101daae3af4cfd7ef439fd1498c8d412ad9008 Mon Sep 17 00:00:00 2001 From: ddCat Date: Fri, 28 Mar 2025 10:29:28 +0800 Subject: [PATCH] =?UTF-8?q?add=EF=BC=9A=E7=B3=BB=E7=BB=9F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=A2=9E=E5=8A=A0=E6=94=AF=E6=8C=81proxy=E9=80=89?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.py | 73 ++++++++++++- browser_utils.py | 23 +++- config.py | 16 +++ index.html | 57 ++++++++++ static/css/styles.css | 28 +++++ static/js/app.js | 239 +++++++++++++++++++++++++++++++++--------- 6 files changed, 378 insertions(+), 58 deletions(-) diff --git a/api.py b/api.py index 17c518c..52c97c8 100644 --- a/api.py +++ b/api.py @@ -28,6 +28,8 @@ from config import ( ) from fastapi.staticfiles import StaticFiles from dotenv import load_dotenv +import sys +import psutil # 全局状态追踪 registration_status = { @@ -1046,6 +1048,13 @@ class ConfigModel(BaseModel): EMAIL_PIN: str BROWSER_PATH: Optional[str] = None CURSOR_PATH: Optional[str] = None + USE_PROXY: bool = False + PROXY_TYPE: str = "http" + PROXY_HOST: str = "" + PROXY_PORT: str = "" + PROXY_TIMEOUT: int = 10 + PROXY_USERNAME: str = "" + PROXY_PASSWORD: str = "" # 获取配置端点 @@ -1065,9 +1074,17 @@ async def get_config(): "EMAIL_PIN": os.getenv("EMAIL_PIN", ""), "BROWSER_PATH": os.getenv("BROWSER_PATH", ""), "CURSOR_PATH": os.getenv("CURSOR_PATH", ""), - "DYNAMIC_USERAGENT": os.getenv("DYNAMIC_USERAGENT", "False").lower() == "true" + "DYNAMIC_USERAGENT": os.getenv("DYNAMIC_USERAGENT", "False").lower() == "true", + # 添加代理配置 + "USE_PROXY": os.getenv("USE_PROXY", "False"), + "PROXY_TYPE": os.getenv("PROXY_TYPE", "http"), + "PROXY_HOST": os.getenv("PROXY_HOST", ""), + "PROXY_PORT": os.getenv("PROXY_PORT", ""), + "PROXY_TIMEOUT": os.getenv("PROXY_TIMEOUT", "10"), + "PROXY_USERNAME": os.getenv("PROXY_USERNAME", ""), + "PROXY_PASSWORD": os.getenv("PROXY_PASSWORD", ""), } - + return {"success": True, "data": config} except Exception as e: error(f"获取配置失败: {str(e)}") @@ -1076,9 +1093,9 @@ async def get_config(): # 更新配置端点 -@app.put("/config", tags=["Config"]) +@app.post("/config", tags=["Config"]) async def update_config(config: ConfigModel): - """更新系统配置""" + """更新配置""" try: # 获取.env文件路径 env_path = Path(__file__).parent / ".env" @@ -1089,7 +1106,7 @@ async def update_config(config: ConfigModel): with open(env_path, "r", encoding="utf-8") as f: current_lines = f.readlines() - # 构建配置字典 + # 构建配置字典 - 修正:直接使用模型属性而非get方法 config_dict = { "BROWSER_HEADLESS": str(config.BROWSER_HEADLESS), "DYNAMIC_USERAGENT": str(config.DYNAMIC_USERAGENT), @@ -1098,6 +1115,14 @@ async def update_config(config: ConfigModel): "EMAIL_DOMAINS": config.EMAIL_DOMAINS, "EMAIL_USERNAME": config.EMAIL_USERNAME, "EMAIL_PIN": config.EMAIL_PIN, + # 添加代理配置 + "USE_PROXY": str(config.USE_PROXY), + "PROXY_TYPE": config.PROXY_TYPE, + "PROXY_HOST": config.PROXY_HOST, + "PROXY_PORT": config.PROXY_PORT, + "PROXY_TIMEOUT": str(config.PROXY_TIMEOUT), + "PROXY_USERNAME": config.PROXY_USERNAME, + "PROXY_PASSWORD": config.PROXY_PASSWORD, } # 添加可选配置(如果提供) @@ -1144,6 +1169,44 @@ async def update_config(config: ConfigModel): return {"success": False, "message": f"更新配置失败: {str(e)}"} +# 添加重启API端点 +@app.post("/restart", tags=["System"]) +async def restart_service(): + """重启应用服务""" + try: + info("收到重启服务请求") + + # 创建一个子进程来执行重启 + if sys.platform == 'win32': + cmd = 'powershell -Command "Start-Sleep -s 2; Start-Process python -ArgumentList \'api.py\'"' + else: + cmd = 'bash -c "sleep 2 && nohup python api.py > /dev/null 2>&1 &"' + + # 启动新进程 + import subprocess + subprocess.Popen(cmd, shell=True) + + # 准备在2秒后关闭当前进程 + async def shutdown(): + await asyncio.sleep(2) + info("执行服务重启...") + for proc in psutil.process_children(psutil.Process()): + try: + proc.terminate() + except: + pass + os._exit(0) # 强制退出当前进程 + + # 启动关闭任务 + asyncio.create_task(shutdown()) + + return {"success": True, "message": "服务正在重启,请稍候..."} + except Exception as e: + error(f"重启服务失败: {str(e)}") + error(traceback.format_exc()) + return {"success": False, "message": f"重启服务失败: {str(e)}"} + + if __name__ == "__main__": uvicorn.run( "api:app", diff --git a/browser_utils.py b/browser_utils.py index cc0c0c4..b0f6f9c 100644 --- a/browser_utils.py +++ b/browser_utils.py @@ -7,7 +7,14 @@ from config import ( BROWSER_PATH, BROWSER_HEADLESS, BROWSER_PROXY, - DYNAMIC_USERAGENT + DYNAMIC_USERAGENT, + USE_PROXY, + PROXY_TYPE, + PROXY_HOST, + PROXY_PORT, + PROXY_USERNAME, + PROXY_PASSWORD, + PROXY_TIMEOUT ) import random import time @@ -88,11 +95,25 @@ class BrowserManager: co.set_argument("--no-sandbox") co.set_argument("--disable-gpu") + # 添加代理设置 + if USE_PROXY and PROXY_HOST and PROXY_PORT: + proxy_string = f"{PROXY_TYPE}://" + + # 如果有认证信息 + if PROXY_USERNAME and PROXY_PASSWORD: + proxy_string += f"{PROXY_USERNAME}:{PROXY_PASSWORD}@" + + proxy_string += f"{PROXY_HOST}:{PROXY_PORT}" + + info(f"使用代理: {PROXY_TYPE} {PROXY_HOST}:{PROXY_PORT} 账号/密码: {PROXY_USERNAME}:{PROXY_PASSWORD}") + co.set_argument(f'--proxy-server={proxy_string}') + self.browser = Chromium(co) info("浏览器初始化成功") except Exception as e: error(f"浏览器初始化失败: {str(e)}") return self.browser + def _get_extension_path(self): """获取插件路径""" root_dir = os.getcwd() diff --git a/config.py b/config.py index 7792fc1..0677ae7 100644 --- a/config.py +++ b/config.py @@ -87,3 +87,19 @@ DATABASE_URL = os.getenv("DATABASE_URL", DATABASE_URL) # ===== Cursor main.js 配置 ===== # Cursor 主文件路径 CURSOR_PATH = os.getenv("CURSOR_PATH", None) + +# ===== 代理配置 ===== +# 是否启用代理 +USE_PROXY = os.getenv("USE_PROXY", "False").lower() == "true" +# 代理类型 +PROXY_TYPE = os.getenv("PROXY_TYPE", "http") +# 代理服务器地址 +PROXY_HOST = os.getenv("PROXY_HOST", "") +# 代理服务器端口 +PROXY_PORT = os.getenv("PROXY_PORT", "") +# 代理服务器用户名 +PROXY_USERNAME = os.getenv("PROXY_USERNAME", "") +# 代理服务器密码 +PROXY_PASSWORD = os.getenv("PROXY_PASSWORD", "") +# 代理服务器超时时间 +PROXY_TIMEOUT = int(os.getenv("PROXY_TIMEOUT", "10")) \ No newline at end of file diff --git a/index.html b/index.html index f8b98cf..067fc9c 100644 --- a/index.html +++ b/index.html @@ -332,6 +332,56 @@ + +
+
+
代理服务器设置
+
+
+
+ + +
+ +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+
diff --git a/static/css/styles.css b/static/css/styles.css index e061145..2163142 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -870,3 +870,31 @@ h2, h3, h4, h5 { content: '\f0dd'; color: #0d6efd; } + +/* 代理配置样式 */ +#proxy-settings { + background-color: #f8f9fa; + padding: 15px; + border-radius: 6px; + border: 1px solid #e9ecef; + margin-top: 10px; + transition: all 0.3s ease; +} + +.form-check-input:checked + .form-check-label { + font-weight: 600; + color: #0d6efd; +} + +/* 代理类型下拉框样式 */ +#proxy-type { + border-color: #ced4da; + border-radius: 4px; + padding: 0.375rem 0.75rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +#proxy-type:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} diff --git a/static/js/app.js b/static/js/app.js index 209bea1..6d12278 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -80,6 +80,22 @@ function bindEventHandlers() { e.preventDefault(); saveConfig(); }); + + // 代理设置切换事件 + $("#use-proxy").change(function() { + toggleProxySettings(); + }); + + // 重启服务按钮事件 + $("#restart-service-btn").click(function() { + showConfirmDialog( + '重启服务', + '确定要重启服务吗?重启过程可能需要几秒钟,期间服务将不可用。', + function() { + restartService(); + } + ); + }); } // 全局变量 @@ -589,13 +605,7 @@ function startTaskManually() { hideLoading(); if (data.success) { showAlert('定时任务已成功启动', 'success'); - - // 立即更新任务状态 - 添加这段代码 - fetch('/registration/status') - .then(res => res.json()) - .then(statusData => { - updateTaskStatusUI(statusData); - }); + checkTaskStatus(); } else { showAlert(`启动任务失败: ${data.message || '未知错误'}`, 'danger'); } @@ -1076,17 +1086,28 @@ function renderTokenColumn(token, accountId) { // 加载配置函数 function loadConfig() { showLoading(); - fetch('/config') - .then(res => res.json()) - .then(data => { - hideLoading(); - if (data.success) { - const config = data.data; - $("#browser-headless").val(config.BROWSER_HEADLESS.toString()); - $("#dynamic-useragent").prop('checked', config.DYNAMIC_USERAGENT || false); + $.ajax({ + url: '/config', + method: 'GET', + success: function(response) { + if (response.success) { + const config = response.data; + + // 现有字段设置... + + // 设置代理配置 + $("#use-proxy").prop("checked", config.USE_PROXY === "True"); + $("#proxy-type").val(config.PROXY_TYPE || "http"); + $("#proxy-host").val(config.PROXY_HOST || ""); + $("#proxy-port").val(config.PROXY_PORT || ""); + $("#proxy-timeout").val(config.PROXY_TIMEOUT || "10"); + $("#proxy-username").val(config.PROXY_USERNAME || ""); + $("#proxy-password").val(config.PROXY_PASSWORD || ""); // 触发动态UA的change事件 $("#dynamic-useragent").trigger('change'); + // 根据是否启用代理来显示/隐藏代理设置 + toggleProxySettings(); $("#browser-useragent").val(config.BROWSER_USER_AGENT); $("#accounts-limit").val(config.MAX_ACCOUNTS); @@ -1095,55 +1116,169 @@ function loadConfig() { $("#email-pin").val(config.EMAIL_PIN); $("#browser-path").val(config.BROWSER_PATH || ''); $("#cursor-path").val(config.CURSOR_PATH || ''); + hideLoading(); } else { - showAlert(`加载配置失败: ${data.message || '未知错误'}`, 'danger'); + showAlert('danger', '加载配置失败: ' + response.message); + hideLoading(); } - }) - .catch(error => { - console.error('加载配置时发生错误:', error); + }, + error: function(xhr) { hideLoading(); - showAlert('加载配置失败,请稍后重试', 'danger'); - }); + showAlert('danger', '加载配置失败: ' + xhr.statusText); + } + }); } -// 保存配置函数 +// 添加代理设置的显示/隐藏控制 +function toggleProxySettings() { + if ($("#use-proxy").is(":checked")) { + $("#proxy-settings").show(); + } else { + $("#proxy-settings").hide(); + } +} + +// 添加配置保存回调,支持重启 function saveConfig() { showLoading(); - const isDynamicUA = $("#dynamic-useragent").prop('checked'); - const config = { - BROWSER_HEADLESS: $("#browser-headless").val() === 'true', - DYNAMIC_USERAGENT: isDynamicUA, - BROWSER_USER_AGENT: isDynamicUA ? "" : $("#browser-useragent").val(), - MAX_ACCOUNTS: parseInt($("#accounts-limit").val()), - EMAIL_DOMAINS: $("#email-domains").val(), - EMAIL_USERNAME: $("#email-username").val(), - EMAIL_PIN: $("#email-pin").val(), - BROWSER_PATH: $("#browser-path").val(), - CURSOR_PATH: $("#cursor-path").val() + const configData = { + // 现有字段... + + // 代理设置(确保这些字段存在) + USE_PROXY: $("#use-proxy").is(":checked"), + PROXY_TYPE: $("#proxy-type").val(), + PROXY_HOST: $("#proxy-host").val(), + PROXY_PORT: $("#proxy-port").val(), + PROXY_TIMEOUT: parseInt($("#proxy-timeout").val()) || 10, + PROXY_USERNAME: $("#proxy-username").val(), + PROXY_PASSWORD: $("#proxy-password").val() }; - fetch('/config', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', + $.ajax({ + url: '/config', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(configData), + success: function(response) { + hideLoading(); + if (response.success) { + // 添加重启询问提示 + showConfirmDialog( + '配置已成功保存', + '需要重启服务才能使更改生效。是否立即重启服务?', + function() { + // 确认重启 + restartService(); + } + ); + enableConfigForm(false); + } else { + showAlert('danger', '保存配置失败: ' + response.message); + } }, - body: JSON.stringify(config) - }) - .then(res => res.json()) - .then(data => { - hideLoading(); - if (data.success) { - showAlert('配置已成功保存', 'success'); - enableConfigForm(false); // 禁用编辑状态 - } else { - showAlert(`保存配置失败: ${data.message || '未知错误'}`, 'danger'); + error: function(xhr) { + hideLoading(); + showAlert('danger', '保存配置失败: ' + xhr.statusText); + } + }); +} + +// 添加确认对话框函数 +function showConfirmDialog(title, message, confirmCallback) { + // 如果已存在对话框,先移除 + if ($("#confirm-dialog").length) { + $("#confirm-dialog").remove(); + } + + const dialogHTML = ` + + `; + + $('body').append(dialogHTML); + + const modal = new bootstrap.Modal(document.getElementById('confirm-dialog')); + modal.show(); + + $("#confirm-yes").click(function() { + modal.hide(); + if (typeof confirmCallback === 'function') { + confirmCallback(); + } + }); +} + +// 添加重启服务函数 +function restartService() { + showLoading(); + const loadingText = "服务正在重启,请稍候..."; + $("#loading-overlay p").text(loadingText); + + $.ajax({ + url: '/restart', + method: 'POST', + success: function(response) { + if (response.success) { + // 设置一个定时器,每3秒尝试检查服务是否已重启 + let checkCount = 0; + const maxChecks = 10; // 最多等待30秒 + + const checkInterval = setInterval(function() { + checkCount++; + if (checkCount > maxChecks) { + clearInterval(checkInterval); + hideLoading(); + showAlert('warning', '服务重启时间过长,请手动刷新页面'); + return; + } + + // 尝试请求API检查服务是否可用 + $.ajax({ + url: '/accounts?page=1&per_page=1', + method: 'GET', + timeout: 2000, + success: function() { + clearInterval(checkInterval); + hideLoading(); + showAlert('success', '服务已成功重启'); + // 重新加载页面以获取最新配置 + setTimeout(function() { + window.location.reload(); + }, 1000); + }, + error: function() { + // 服务未就绪,继续等待 + console.log('等待服务重启...'); + } + }); + }, 3000); + } else { + hideLoading(); + showAlert('danger', '重启服务失败: ' + response.message); + } + }, + error: function() { + // 请求失败可能意味着服务已开始重启 + // 设置检查间隔 + setTimeout(function() { + // 尝试重新加载页面 + window.location.reload(); + }, 5000); } - }) - .catch(error => { - console.error('保存配置时发生错误:', error); - hideLoading(); - showAlert('保存配置失败,请稍后重试', 'danger'); }); }