全面架构重构:建立分层架构与高度可扩展的插件系统
后端重构: - 新增分层架构: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
This commit is contained in:
102
repositories/settings_repo.py
Normal file
102
repositories/settings_repo.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""设置数据访问层"""
|
||||
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
|
||||
Reference in New Issue
Block a user