feat: JSON 配置、质量分与仪表盘,及设置与爬取流程
- 后端改为 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
This commit is contained in:
@@ -1,77 +1,111 @@
|
||||
"""全局配置 - 使用 Pydantic Settings 支持环境变量和 .env 文件"""
|
||||
import os
|
||||
from typing import List
|
||||
from pydantic import AliasChoices, Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
"""全局配置:仅从 JSON 文件加载,不使用环境变量。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
)
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
# 数据库配置(环境变量 PROXYPOOL_DB_PATH 优先,供 pytest 与生产隔离)
|
||||
db_path: str = Field(
|
||||
default="db/proxies.sqlite",
|
||||
validation_alias=AliasChoices("PROXYPOOL_DB_PATH", "DB_PATH", "db_path"),
|
||||
)
|
||||
from app.core.config_paths import project_root, resolved_config_path
|
||||
|
||||
# API 服务配置
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 18080
|
||||
logger = logging.getLogger("ProxyPool")
|
||||
|
||||
# 验证器配置
|
||||
validator_timeout: int = 5
|
||||
validator_max_concurrency: int = 200
|
||||
validator_connect_timeout: int = 3
|
||||
|
||||
# 爬虫配置
|
||||
crawler_num_validators: int = 50
|
||||
crawler_max_queue_size: int = 500
|
||||
|
||||
# 日志配置
|
||||
log_level: str = "INFO"
|
||||
log_dir: str = "logs"
|
||||
|
||||
# WebSocket:统计广播间隔(秒);无连接时不查库
|
||||
ws_stats_interval_seconds: int = 1
|
||||
|
||||
# 导出配置
|
||||
export_max_records: int = 10000
|
||||
|
||||
# 代理评分配置
|
||||
score_valid: int = 10
|
||||
score_invalid: int = -5
|
||||
score_min: int = 0
|
||||
score_max: int = 100
|
||||
|
||||
# 验证目标配置
|
||||
validator_test_urls: List[str] = [
|
||||
_DEFAULTS: Dict[str, Any] = {
|
||||
"db_path": "db/proxies.sqlite",
|
||||
"host": "127.0.0.1",
|
||||
"port": 18080,
|
||||
"validator_timeout": 5,
|
||||
"validator_max_concurrency": 200,
|
||||
"validator_connect_timeout": 3,
|
||||
"crawler_num_validators": 50,
|
||||
"crawler_max_queue_size": 500,
|
||||
"log_level": "INFO",
|
||||
"log_dir": "logs",
|
||||
"ws_stats_interval_seconds": 1,
|
||||
"export_max_records": 10000,
|
||||
"score_valid": 10,
|
||||
"score_invalid": -5,
|
||||
"score_min": 0,
|
||||
"score_max": 100,
|
||||
"score_latency_ref_ms": 500.0,
|
||||
"score_use_penalty_per_pick": 2.5,
|
||||
"score_max_use_penalty": 70.0,
|
||||
"score_default_latency_ms": 1500.0,
|
||||
"validator_test_urls": [
|
||||
"http://httpbin.org/ip",
|
||||
"https://httpbin.org/ip",
|
||||
"http://api.ipify.org",
|
||||
"https://api.ipify.org",
|
||||
"http://www.baidu.com",
|
||||
"http://www.qq.com",
|
||||
]
|
||||
|
||||
# 插件配置
|
||||
plugins_dir: str = "plugins"
|
||||
|
||||
# CORS 配置 - Pydantic v2 会自动将逗号分隔的字符串解析为 List[str]
|
||||
cors_origins: List[str] = [
|
||||
],
|
||||
"plugins_dir": "plugins",
|
||||
"cors_origins": [
|
||||
"http://localhost:8080",
|
||||
"http://localhost:5173",
|
||||
"http://127.0.0.1:18081",
|
||||
"http://localhost:18081",
|
||||
]
|
||||
],
|
||||
"run_network_tests": False,
|
||||
}
|
||||
|
||||
|
||||
def _load_merged_dict() -> Dict[str, Any]:
|
||||
data = dict(_DEFAULTS)
|
||||
path = resolved_config_path()
|
||||
if not path.is_file():
|
||||
logger.warning("配置文件不存在,使用内置默认项: %s", path)
|
||||
return data
|
||||
try:
|
||||
with path.open(encoding="utf-8") as f:
|
||||
file_data = json.load(f)
|
||||
if not isinstance(file_data, dict):
|
||||
logger.error("配置文件须为 JSON 对象,已忽略: %s", path)
|
||||
return data
|
||||
data.update(file_data)
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
logger.error("读取配置文件失败,使用内置默认项: %s (%s)", path, e)
|
||||
return data
|
||||
|
||||
|
||||
class AppSettings(BaseModel):
|
||||
"""应用配置(与 config/app.json 字段一致)"""
|
||||
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
db_path: str
|
||||
host: str
|
||||
port: int
|
||||
validator_timeout: int
|
||||
validator_max_concurrency: int
|
||||
validator_connect_timeout: int
|
||||
crawler_num_validators: int
|
||||
crawler_max_queue_size: int
|
||||
log_level: str
|
||||
log_dir: str
|
||||
ws_stats_interval_seconds: int
|
||||
export_max_records: int
|
||||
score_valid: int
|
||||
score_invalid: int
|
||||
score_min: int
|
||||
score_max: int
|
||||
score_latency_ref_ms: float
|
||||
score_use_penalty_per_pick: float
|
||||
score_max_use_penalty: float
|
||||
score_default_latency_ms: float
|
||||
validator_test_urls: List[str]
|
||||
plugins_dir: str
|
||||
cors_origins: List[str]
|
||||
run_network_tests: bool = False
|
||||
|
||||
@property
|
||||
def base_dir(self) -> str:
|
||||
return os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
return str(project_root())
|
||||
|
||||
|
||||
# 全局配置实例(启动时加载一次)
|
||||
settings = Settings()
|
||||
# 全局单例(进程内首次导入时按当前 resolved_config_path() 加载)
|
||||
settings = AppSettings.model_validate(_load_merged_dict())
|
||||
|
||||
# 历史代码别名
|
||||
Settings = AppSettings
|
||||
|
||||
Reference in New Issue
Block a user