feat: fpw plugins, validation/crawl perf, WS stats, test DB isolation

- Add Free_Proxy_Website-style fpw_* plugins and register them
- Per-plugin crawl timeout (crawl_timeout_seconds=120); remove global crawl_timeout setting
- Validator: fix connect vs total timeout on save; SOCKS session LRU cache; drop redundant semaphore
- Validation handler uses single DB connection; batch upsert after crawl; WorkerPool put_nowait
- Remove unused max_retries from settings API/UI; settings maintenance SQL + init_db cleanup of deprecated keys
- WebSocket dashboard stats; ProxyList pool_filter and API alignment
- POST /api/proxies/delete-one for IPv6-safe deletes; task poll stops on 404
- pytest uses PROXYPOOL_DB_PATH=db/proxies.test.sqlite so tests do not wipe production DB
- .gitignore: explicit proxies.test.sqlite patterns; fix plugin_service ValidationException import

Made-with: Cursor
This commit is contained in:
祀梦
2026-04-05 13:39:19 +08:00
parent 92c7fa19e2
commit 0131c8b408
63 changed files with 2331 additions and 531 deletions

View File

@@ -5,7 +5,8 @@ from fastapi.responses import StreamingResponse
from app.services.proxy_service import ProxyService
from app.services.scheduler_service import SchedulerService
from app.models.schemas import ProxyListRequest, BatchDeleteRequest
from app.services.dashboard_stats import get_dashboard_stats
from app.models.schemas import ProxyListRequest, BatchDeleteRequest, ProxyDeleteItem
from app.api.deps import get_proxy_service, get_scheduler_service
from app.api.common import success_response, format_proxy
from app.core.exceptions import ProxyPoolException, ProxyNotFoundException
@@ -15,11 +16,9 @@ router = APIRouter(prefix="/api/proxies", tags=["proxies"])
@router.get("/stats")
async def get_stats(
proxy_service: ProxyService = Depends(get_proxy_service),
scheduler_service: SchedulerService = Depends(get_scheduler_service),
):
stats = await proxy_service.get_stats()
stats["scheduler_running"] = scheduler_service.running
stats = await get_dashboard_stats(scheduler_service.running)
return success_response("获取统计信息成功", stats)
@@ -36,6 +35,7 @@ async def list_proxies(
max_score=request.max_score,
sort_by=request.sort_by,
sort_order=request.sort_order,
pool_filter=request.pool_filter,
)
return success_response(
"获取代理列表成功",
@@ -75,6 +75,16 @@ async def export_proxies(
)
@router.post("/delete-one")
async def delete_proxy_one(
item: ProxyDeleteItem,
service: ProxyService = Depends(get_proxy_service),
):
"""JSON 删除推荐IPv6 等含冒号 IP 不受路径分段影响。"""
await service.delete_proxy(item.ip, item.port)
return success_response("删除代理成功")
@router.delete("/{ip}/{port}")
async def delete_proxy(ip: str, port: int, service: ProxyService = Depends(get_proxy_service)):
await service.delete_proxy(ip, port)