- 后端改为 config/app.json;pytest 使用 config/app.test.json 与 set_config_file,不再依赖环境变量;移除 pydantic-settings。 - 前端 API/WebSocket 由 config/webui.json 经 Vite define 注入。 - 代理分数按延迟与随机取用次数计算,新增 use_count 与 proxy_scoring;保存设置时同步调度器启停。 - 仪表盘双饼图(可用/待验证协议);设置页去掉调度器启停按钮并移动立即验证;爬取全部结束后自动提交全量验证。 - 删除 script/settings_maintain.py(此前已标记删除)。 Made-with: Cursor
85 lines
3.6 KiB
Python
85 lines
3.6 KiB
Python
"""设置相关路由"""
|
||
import asyncio
|
||
|
||
from fastapi import APIRouter, Request, Depends
|
||
from app.core.db import get_db
|
||
from app.repositories.settings_repo import SettingsRepository
|
||
from app.models.schemas import SettingsSchema
|
||
from app.api.common import success_response
|
||
from app.api.deps import get_settings_repo
|
||
from app.core.config import settings as app_settings
|
||
from app.core.exceptions import ProxyPoolException
|
||
from app.core.log import logger
|
||
|
||
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||
|
||
|
||
@router.get("")
|
||
async def get_settings(settings_repo: SettingsRepository = Depends(get_settings_repo)):
|
||
async with get_db() as db:
|
||
settings = await settings_repo.get_all(db)
|
||
return success_response("获取设置成功", settings)
|
||
|
||
|
||
@router.post("")
|
||
async def save_settings(
|
||
request: SettingsSchema,
|
||
http_request: Request,
|
||
settings_repo: SettingsRepository = Depends(get_settings_repo),
|
||
):
|
||
async with get_db() as db:
|
||
success = await settings_repo.save(db, request.model_dump())
|
||
if not success:
|
||
raise ProxyPoolException("保存设置失败", 500)
|
||
|
||
# 热更新运行中调度器的间隔时间
|
||
scheduler = getattr(http_request.app.state, "scheduler", None)
|
||
worker_pool = getattr(http_request.app.state, "worker_pool", None)
|
||
validator = getattr(http_request.app.state, "validator", None)
|
||
|
||
if scheduler:
|
||
new_interval = request.validate_interval_minutes
|
||
if scheduler.interval_minutes != new_interval:
|
||
scheduler.interval_minutes = new_interval
|
||
logger.info(f"Scheduler interval updated to {new_interval} minutes")
|
||
|
||
want_run = bool(request.auto_validate)
|
||
if want_run and not scheduler.running:
|
||
try:
|
||
await scheduler.start()
|
||
except Exception as e:
|
||
logger.error(f"Failed to start scheduler after settings save: {e}")
|
||
elif not want_run and scheduler.running:
|
||
try:
|
||
await scheduler.stop()
|
||
except Exception as e:
|
||
logger.error(f"Failed to stop scheduler after settings save: {e}")
|
||
|
||
# 热更新 Worker 池大小
|
||
if worker_pool and worker_pool.worker_count != request.default_concurrency:
|
||
await worker_pool.resize(request.default_concurrency)
|
||
logger.info(f"Worker pool resized to {request.default_concurrency}")
|
||
|
||
# 热更新验证器超时和并发(下次验证时生效)
|
||
if validator:
|
||
vt = float(request.validation_timeout)
|
||
validator._init_timeout = vt
|
||
# 连接阶段单独收紧:勿与 total 等同,否则死代理会在 connect 上耗满整段超时
|
||
validator._init_connect_timeout = min(
|
||
float(app_settings.validator_connect_timeout), vt
|
||
)
|
||
validator._init_max_concurrency = request.default_concurrency
|
||
if request.validation_targets is not None:
|
||
validator.update_test_urls(request.validation_targets)
|
||
# 延迟关闭旧 session:让正在验证的代理继续使用旧 session,
|
||
# 新请求会通过 _ensure_session() 自动创建使用新配置的 session
|
||
await validator.close_socks_sessions()
|
||
old_session = validator._http_session
|
||
validator._http_session = None
|
||
validator._http_connector = None
|
||
if old_session and not old_session.closed:
|
||
asyncio.create_task(old_session.close())
|
||
logger.info(f"Validator config updated: timeout={request.validation_timeout}, concurrency={request.default_concurrency}, targets={request.validation_targets}")
|
||
|
||
return success_response("保存设置成功", request.model_dump())
|