add:系统配置增加支持proxy选项

This commit is contained in:
ddCat
2025-03-28 10:29:28 +08:00
parent 495dce3fd9
commit 36101daae3
6 changed files with 378 additions and 58 deletions

73
api.py
View File

@@ -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",

View File

@@ -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()

View File

@@ -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"))

View File

@@ -332,6 +332,56 @@
</div>
</div>
</div>
<!-- 在系统配置表单中添加代理设置部分 -->
<div class="config-section card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">代理服务器设置</h5>
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="use-proxy" disabled name="USE_PROXY">
<label class="form-check-label" for="use-proxy">启用代理服务器</label>
</div>
<div id="proxy-settings" class="mb-3">
<div class="row mb-3">
<div class="col-md-6">
<label for="proxy-type" class="form-label">代理类型</label>
<select class="form-select" id="proxy-type" disabled name="PROXY_TYPE">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
<option value="socks4">SOCKS4</option>
<option value="socks5">SOCKS5</option>
</select>
</div>
<div class="col-md-6">
<label for="proxy-host" class="form-label">代理服务器地址</label>
<input type="text" class="form-control" id="proxy-host" disabled name="PROXY_HOST" placeholder="例如: 127.0.0.1">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="proxy-port" class="form-label">代理服务器端口</label>
<input type="number" class="form-control" id="proxy-port" disabled name="PROXY_PORT" placeholder="例如: 7890">
</div>
<div class="col-md-6">
<label for="proxy-timeout" class="form-label">超时时间 (秒)</label>
<input type="number" class="form-control" id="proxy-timeout" disabled name="PROXY_TIMEOUT" value="10">
</div>
</div>
<div class="row">
<div class="col-md-6">
<label for="proxy-username" class="form-label">用户名 (可选)</label>
<input type="text" class="form-control" id="proxy-username" disabled name="PROXY_USERNAME" placeholder="认证用户名">
</div>
<div class="col-md-6">
<label for="proxy-password" class="form-label">密码 (可选)</label>
<input type="password" class="form-control" id="proxy-password" disabled name="PROXY_PASSWORD" placeholder="认证密码">
</div>
</div>
</div>
</div>
</div>
<!-- 将按钮容器从text-end改为新的栅格布局 -->
<div class="row mt-4 config-actions" id="config-actions" style="display:none">
<div class="col-6 pe-1">
@@ -351,6 +401,13 @@
</span>
</button>
</div>
<!-- 在系统配置区域底部添加重启按钮 -->
<div class="text-center mt-4">
<button id="restart-service-btn" class="btn btn-danger">
<i class="fas fa-sync-alt me-2"></i>重启服务
</button>
<p class="text-muted small mt-2">某些配置更改需要重启服务才能生效</p>
</div>
</div>
</div>
</div>

View File

@@ -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);
}

View File

@@ -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 = `
<div class="modal fade" id="confirm-dialog" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${title}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">${message}</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="confirm-yes">确认</button>
</div>
</div>
</div>
</div>
`;
$('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');
});
}