Files
ProxyPool/DESIGN.md
祀梦 209a744d94 全面架构重构:建立分层架构与高度可扩展的插件系统
后端重构:
- 新增分层架构: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
2026-04-02 11:55:05 +08:00

471 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ProxyPool 架构重构设计文档
> 目标:建立一个高度可扩展、分层清晰、易于维护的代理池系统。最关键的目标是**让添加新爬虫变得极其简单**。
---
## 1. 架构总览
采用经典的分层架构:
```
┌─────────────────────────────────────────┐
│ Frontend (Vue3 + Vite + Element Plus) │
└─────────────┬───────────────────────────┘
│ HTTP/REST
┌─────────────▼───────────────────────────┐
│ API Layer (FastAPI Routers) │ ← 只负责:校验输入、调用 Service、格式化输出
├─────────────────────────────────────────┤
│ Service Layer │ ← 业务逻辑编排:爬取策略、验证调度、导出逻辑
├─────────────────────────────────────────┤
│ Plugin System (Crawlers) │ ← 爬虫插件:实现统一接口,返回原始代理数据
├─────────────────────────────────────────┤
│ Task Queue & Workers │ ← 验证队列背压控制、Worker 池、削峰填谷
├─────────────────────────────────────────┤
│ Repository Layer │ ← 数据访问:所有 SQL 收敛于此
├─────────────────────────────────────────┤
│ Infrastructure (DB / Config / Log) │ ← 基础设施:连接池、配置、日志
└─────────────────────────────────────────┘
```
---
## 2. 后端核心设计原则
### 2.1 消灭全局单例,全面使用依赖注入 (DI)
当前 `scheduler = ValidationScheduler()` 是模块级全局变量,导致测试困难、隐式依赖。
重构后:
- 所有核心组件DB、Scheduler、PluginManager都通过 FastAPI `Depends` 注入
- 使用 `contextlib.asynccontextmanager` 在 lifespan 中初始化并挂载到 `app.state`
- 单元测试可以轻易 mock 任何一层
### 2.2 Repository 模式收敛所有 SQL
所有数据库操作从 `api_server.py``scheduler.py` 中彻底抽离到 `repositories/proxy_repo.py`
好处:
- 换数据库时只改 Repository
- 写单元测试直接 mock Repository
- SQL 语句集中管理,防止散落在各处
### 2.3 任务队列解耦爬取与验证
当前插件爬取后直接 `asyncio.gather(*10000_tasks)` 验证,存在内存和并发风险。
重构后引入轻量级内存队列:
- `ValidationQueue`:基于 `asyncio.Queue`
- `ValidationWorkerPool`:固定数量的 Worker 从队列消费
- 爬取结果 `put` 进队列即返回,验证在后台进行
- 天然支持背压backpressure防止内存爆炸
---
## 3. 插件系统设计(核心)
### 3.1 设计目标
**让添加一个新爬虫只需要做两件事:**
1. 创建一个类,继承 `BaseCrawlerPlugin`
2. 实现 `crawl()` 方法,返回 `list[ProxyRaw]`
### 3.2 插件接口
```python
from dataclasses import dataclass
from typing import List, AsyncIterator
@dataclass
class ProxyRaw:
ip: str
port: int
protocol: str # http | https | socks4 | socks5
class BaseCrawlerPlugin:
"""所有爬虫插件必须继承的基类"""
name: str = "" # 插件唯一标识
display_name: str = "" # 展示名称
description: str = "" # 描述
enabled: bool = True # 是否默认启用
async def crawl(self) -> List[ProxyRaw]:
"""
爬取代理的核心方法。
可以是纯同步逻辑,也可以包含异步 HTTP 请求。
返回原始代理列表,不要在这里做验证。
"""
raise NotImplementedError
async def health_check(self) -> bool:
"""可选:检查当前插件是否可用(如目标网站是否可访问)"""
return True
```
### 3.3 插件注册机制
采用**显式注册 + 装饰器**模式,抛弃运行时目录扫描。
```python
from core.plugin_system import registry
@registry.register
class MyNewPlugin(BaseCrawlerPlugin):
name = "my_new_plugin"
display_name = "我的新代理源"
async def crawl(self):
return [ProxyRaw("1.2.3.4", 8080, "http")]
```
优点:
- 类型安全IDE 可以自动补全、静态检查
- 可控:不会出现意外加载未预期模块的问题
- 测试友好:测试时只注册 mock 插件
同时提供一个兼容入口 `registry.auto_discover("plugins")`,用于兼容现有习惯。
### 3.4 插件元数据持久化
插件的 `enabled` 状态应该持久化到数据库(或 settings JSON而不是仅存在于内存。
新增 `plugin_settings` 表:
```sql
CREATE TABLE plugin_settings (
plugin_id TEXT PRIMARY KEY,
enabled INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
启动时:
1. 加载所有已注册插件
2.`plugin_settings` 读取持久化状态
3. 合并到插件实例中
---
## 4. 任务调度与验证队列
### 4.1 验证队列设计
```python
class ValidationQueue:
def __init__(self, worker_count: int = 50):
self.queue: asyncio.Queue[ProxyRaw] = asyncio.Queue()
self.worker_count = worker_count
self.workers: list[asyncio.Task] = []
self._running = False
async def start(self):
self._running = True
for _ in range(self.worker_count):
self.workers.append(asyncio.create_task(self._worker_loop()))
async def stop(self):
self._running = False
for _ in self.workers:
self.queue.put_nowait(None) # sentinel
await asyncio.gather(*self.workers, return_exceptions=True)
async def submit(self, proxies: list[ProxyRaw]):
for p in proxies:
await self.queue.put(p)
async def _worker_loop(self):
while True:
item = await self.queue.get()
if item is None:
break
await self._validate_and_save(item)
self.queue.task_done()
```
### 4.2 调度器设计
`SchedulerService` 负责:
- 启动/停止验证队列
- 定时从数据库拉取存量代理,重新投入验证队列
- 协调插件爬取后的验证流程
```python
class SchedulerService:
def __init__(self, queue: ValidationQueue, proxy_repo: ProxyRepository):
self.queue = queue
self.proxy_repo = proxy_repo
self.interval_minutes = 30
self._task: asyncio.Task | None = None
```
---
## 5. 数据库设计
保留 SQLite + aiosqlite但优化连接管理。
### 5.1 表结构
```sql
-- 代理表
CREATE TABLE proxies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
port INTEGER NOT NULL,
protocol TEXT DEFAULT 'http',
score INTEGER DEFAULT 10,
response_time_ms REAL,
last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(ip, port)
);
-- 插件设置表
CREATE TABLE plugin_settings (
plugin_id TEXT PRIMARY KEY,
enabled INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 系统设置表JSON 存储)
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 5.2 连接管理
- 使用 `asynccontextmanager` 管理连接生命周期
- 每个 HTTP 请求独立获取连接,请求结束后关闭
- 调度器/队列等长生命周期组件也定期重建连接(如每 1000 次操作)
---
## 6. API 设计调整
保持现有 API 路径基本不变,但路由按资源拆分。
### 6.1 路由拆分
```
apiv1/
├── __init__.py
├── proxies.py # /api/proxies/*
├── plugins.py # /api/plugins/*
├── scheduler.py # /api/scheduler/*
└── settings.py # /api/settings
```
### 6.2 新增/调整的 API
#### 插件相关
- `GET /api/plugins` — 获取插件列表(含持久化状态)
- `PUT /api/plugins/{plugin_id}/toggle` — 切换启用状态(持久化到 DB
- `POST /api/plugins/{plugin_id}/crawl` — 触发爬取(异步,返回任务 ID
- `POST /api/plugins/crawl-all` — 批量爬取
**关键变更**:爬取接口改为**异步触发**而不是同步等待。因为新爬虫可能爬取数万个代理,同步 HTTP 请求会超时。
返回示例:
```json
{
"code": 200,
"message": "爬取任务已启动",
"data": {
"task_id": "crawl-20250402-001",
"queued": 150
}
}
```
为了简化前端,第一阶段可以保留同步 API但内部通过 `asyncio.create_task` 包装并设置合理的超时30 秒)。在真正大规模使用时,再迁移到 WebSocket/SSE 推送进度。
---
## 7. 前端架构调整
### 7.1 新增 Service 层
从 Store 中剥离 API 调用逻辑:
```
frontend/src/
├── services/
│ ├── proxyService.js # 代理相关 API 调用
│ ├── pluginService.js # 插件相关 API 调用
│ ├── schedulerService.js
│ └── settingService.js
├── stores/
│ ├── proxy.js # 纯状态管理
│ └── plugin.js
```
### 7.2 Store 职责收敛
Store 只负责:
- 持有状态(`ref/reactive`
- 提供计算属性
- 调用 Service然后更新状态
### 7.3 API 适配
由于后端 API 路径保持不变前端改动主要是代码组织上的调整URL 和返回结构尽量兼容。
---
## 8. 目录结构(重构后)
```
ProxyPool/
├── api/ # FastAPI 入口和路由
│ ├── __init__.py
│ ├── main.py # 应用工厂
│ ├── lifespan.py # 生命周期管理
│ ├── deps.py # 依赖注入
│ ├── errors.py # 统一异常
│ └── routes/
│ ├── __init__.py
│ ├── proxies.py
│ ├── plugins.py
│ ├── scheduler.py
│ └── settings.py
├── core/ # 基础设施
│ ├── __init__.py
│ ├── config.py # Pydantic Settings
│ ├── log.py # 日志
│ ├── db.py # 数据库连接池/上下文
│ └── exceptions.py # 业务异常
├── models/ # 数据模型
│ ├── __init__.py
│ ├── schemas.py # Pydantic 模型
│ └── domain.py # 领域模型ProxyRaw, PluginInfo 等)
├── repositories/ # 数据访问层
│ ├── __init__.py
│ └── proxy_repo.py # ProxyRepository
├── services/ # 业务逻辑层
│ ├── __init__.py
│ ├── proxy_service.py
│ ├── plugin_service.py
│ ├── scheduler_service.py
│ └── validator_service.py
├── core/ # 任务与插件系统
│ ├── plugin_system/
│ │ ├── __init__.py
│ │ ├── base.py # BaseCrawlerPlugin
│ │ └── registry.py # 插件注册中心
│ └── tasks/
│ ├── __init__.py
│ ├── queue.py # ValidationQueue
│ └── workers.py # Worker Pool
├── plugins/ # 爬虫插件
│ ├── __init__.py
│ ├── base.py # 通用抓取基类HTTP 请求封装)
│ ├── fate0.py
│ ├── proxylist_download.py
│ └── ...
├── frontend/ # Vue3 前端
│ └── src/
│ ├── services/ # 新增
│ ├── stores/
│ ├── api/
│ └── ...
├── tests/ # 测试目录
│ ├── conftest.py
│ ├── unit/
│ └── integration/
├── script/
├── data/
├── db/
├── logs/
├── requirements.txt
├── .env.example
└── DESIGN.md # 本文档
```
---
## 9. 迁移计划
### Phase 1: 基础设施(今天完成)
1. 重写 `core/config.py` → Pydantic Settings
2. 重写 `core/db.py` → 带上下文管理的连接池
3. 创建 `models/`
### Phase 2: Repository + Service今天完成
1. 创建 `repositories/proxy_repo.py`
2. 创建 `services/` 下的业务类
3. 迁移现有逻辑
### Phase 3: 插件系统(今天完成,核心)
1. 创建 `core/plugin_system/base.py``registry.py`
2. 设计显式注册机制
3. 将所有现有插件迁移到新基类
### Phase 4: 任务队列(今天完成)
1. 创建 `ValidationQueue``WorkerPool`
2. 重写 `SchedulerService`
### Phase 5: API 路由(今天完成)
1. 拆分 `api_server.py``api/routes/`
2. 组装新的 `api/main.py`
### Phase 6: 前端调整(今天完成)
1. 拆分 Service 层
2. 适配 Store
3. 保留现有页面,只改代码组织
### Phase 7: 清理与验证
1. 删除旧的 `api_server.py`, `core/scheduler.py`, `core/sqlite.py`
2. 运行测试,确保所有功能正常
3. 提交代码
---
## 10. 添加新爬虫的标准流程(目标体验)
假设要添加一个名为 `mynewsource` 的爬虫:
**Step 1**: 创建文件 `plugins/mynewsource.py`
```python
from core.plugin_system import BaseCrawlerPlugin, ProxyRaw
from plugins.base import BaseHTTPPlugin # 可选:如果基于 HTTP 爬取
class MyNewSourcePlugin(BaseHTTPPlugin):
name = "mynewsource"
display_name = "我的新代理源"
description = "从 example.com 爬取免费代理"
def __init__(self):
super().__init__()
self.urls = ["https://example.com/proxies"]
async def crawl(self) -> list[ProxyRaw]:
results = []
for url in self.urls:
html = await self.fetch(url)
# ... 解析 html ...
results.append(ProxyRaw(ip="1.2.3.4", port=8080, protocol="http"))
return results
```
**Step 2**: 在 `plugins/__init__.py` 中注册
```python
from .mynewsource import MyNewSourcePlugin
from core.plugin_system import registry
registry.register(MyNewSourcePlugin)
```
**Step 3**: 重启后端服务,前端自动显示新插件。
无需修改任何路由、服务、数据库表。
---
*文档版本: 1.0*
*作者: Kimi Code*
*日期: 2026-04-02*