后端重构: - 新增分层架构:API Routes -> Services -> Repositories -> Infrastructure - 彻底移除全局单例,全面采用 FastAPI 依赖注入 - 新增 api/ 目录拆分路由(proxies, plugins, scheduler, settings, stats) - 新增 services/ 业务逻辑层:ProxyService, PluginService, SchedulerService, ValidatorService, SettingsService - 新增 repositories/ 数据访问层:ProxyRepository, SettingsRepository, PluginSettingsRepository - 新增 models/ 层:Pydantic Schemas + Domain Models - 重写 core/config.py:采用 Pydantic Settings 管理配置 - 新增 core/db.py:基于 asynccontextmanager 的连接管理,支持数据库迁移 - 新增 core/exceptions.py:统一业务异常体系 插件系统重构(核心): - 新增 core/plugin_system/:BaseCrawlerPlugin + PluginRegistry - 采用显式注册模式(装饰器 + plugins/__init__.py),类型安全、测试友好 - 新增 plugins/base.py:BaseHTTPPlugin 通用 HTTP 爬虫基类 - 迁移全部 7 个插件到新架构(fate0, proxylist_download, ip3366, ip89, kuaidaili, speedx, yundaili) - 插件状态持久化到 plugin_settings 表 任务调度重构: - 新增 core/tasks/queue.py:ValidationQueue + WorkerPool - 解耦爬取与验证:爬虫只负责爬取,代理提交队列后由 Worker 异步验证 - 调度器定时从数据库拉取存量代理并分批投入验证队列 前端调整: - 新增 frontend/src/services/ 层拆分 API 调用逻辑 - 调整 stores/ 和 views/ 使用 Service 层 - 保持 API 兼容性,页面无需大幅修改 其他: - 新增 main.py 作为新入口 - 新增 DESIGN.md 架构设计文档 - 更新 requirements.txt 增加 pydantic-settings
103 lines
3.4 KiB
Python
103 lines
3.4 KiB
Python
"""设置数据访问层"""
|
|
import json
|
|
import aiosqlite
|
|
from typing import Optional, Dict, Any
|
|
from core.log import logger
|
|
|
|
|
|
DEFAULT_SETTINGS = {
|
|
"crawl_timeout": 30,
|
|
"validation_timeout": 10,
|
|
"max_retries": 3,
|
|
"default_concurrency": 50,
|
|
"min_proxy_score": 0,
|
|
"proxy_expiry_days": 7,
|
|
"auto_validate": True,
|
|
"validate_interval_minutes": 30,
|
|
}
|
|
|
|
|
|
class SettingsRepository:
|
|
"""系统设置 Repository"""
|
|
|
|
@staticmethod
|
|
async def get_all(db: aiosqlite.Connection) -> Dict[str, Any]:
|
|
settings = DEFAULT_SETTINGS.copy()
|
|
try:
|
|
async with db.execute("SELECT key, value FROM settings") as cursor:
|
|
rows = await cursor.fetchall()
|
|
for key, value in rows:
|
|
# 类型转换
|
|
default = DEFAULT_SETTINGS.get(key)
|
|
if isinstance(default, bool):
|
|
settings[key] = value.lower() == "true"
|
|
elif isinstance(default, int):
|
|
settings[key] = int(value)
|
|
else:
|
|
settings[key] = value
|
|
except Exception as e:
|
|
logger.error(f"get_all settings failed: {e}")
|
|
return settings
|
|
|
|
@staticmethod
|
|
async def save(db: aiosqlite.Connection, settings: Dict[str, Any]) -> bool:
|
|
try:
|
|
for key, value in settings.items():
|
|
await db.execute(
|
|
"""
|
|
INSERT INTO settings (key, value, updated_at)
|
|
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(key) DO UPDATE SET
|
|
value = excluded.value,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
""",
|
|
(key, str(value)),
|
|
)
|
|
await db.commit()
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"save settings failed: {e}")
|
|
return False
|
|
|
|
|
|
class PluginSettingsRepository:
|
|
"""插件设置 Repository"""
|
|
|
|
@staticmethod
|
|
async def get_enabled(db: aiosqlite.Connection, plugin_id: str) -> Optional[bool]:
|
|
async with db.execute(
|
|
"SELECT enabled FROM plugin_settings WHERE plugin_id = ?", (plugin_id,)
|
|
) as cursor:
|
|
row = await cursor.fetchone()
|
|
if row:
|
|
return bool(row[0])
|
|
return None
|
|
|
|
@staticmethod
|
|
async def set_enabled(db: aiosqlite.Connection, plugin_id: str, enabled: bool) -> bool:
|
|
try:
|
|
await db.execute(
|
|
"""
|
|
INSERT INTO plugin_settings (plugin_id, enabled, created_at, updated_at)
|
|
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(plugin_id) DO UPDATE SET
|
|
enabled = excluded.enabled,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
""",
|
|
(plugin_id, int(enabled)),
|
|
)
|
|
await db.commit()
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"set_enabled failed for {plugin_id}: {e}")
|
|
return False
|
|
|
|
@staticmethod
|
|
async def list_all(db: aiosqlite.Connection) -> Dict[str, bool]:
|
|
result = {}
|
|
async with db.execute("SELECT plugin_id, enabled FROM plugin_settings") as cursor:
|
|
rows = await cursor.fetchall()
|
|
for plugin_id, enabled in rows:
|
|
result[plugin_id] = bool(enabled)
|
|
return result
|