Files
ProxyPool/api/routes/proxies.py
祀梦 b77641f059 全面清理冗余与过度分层
后端优化:
- 合并 api/routes/stats.py 到 api/routes/proxies.py,统计接口变更为 /api/proxies/stats
- 内联 services/settings_service.py:settings.py 和 scheduler.py 直接使用 SettingsRepository
- 简化 repositories/proxy_repo.py:提取 _row_to_proxy 辅助函数,消除重复构造代码
- 更新 api/lifespan.py 和 api/deps.py,移除对 settings_service 的依赖
- 从 requirements.txt 移除 websockets 依赖(已废弃的 WebSocket 功能残留)

前端适配:
- 更新 frontend/src/api/index.js:stats 接口路径同步为 /api/proxies/stats
- 清理 api/index.js 中未使用的 createRequestConfig 和多余 JSDoc 注释

脚本优化:
- 移除 script/stop.bat 末尾的 pause,避免自动化调用时挂起
2026-04-02 12:26:22 +08:00

131 lines
4.2 KiB
Python

"""代理相关路由(含统计信息)"""
from typing import Optional
from fastapi import APIRouter, Depends, Query
from services.proxy_service import ProxyService
from services.scheduler_service import SchedulerService
from models.schemas import ProxyListRequest, BatchDeleteRequest
from api.deps import get_proxy_service, get_scheduler_service
from core.log import logger
router = APIRouter(prefix="/api/proxies", tags=["proxies"])
def success_response(message: str, data=None):
return {"code": 200, "message": message, "data": data}
def error_response(message: str, code: int = 500):
return {"code": code, "message": message, "data": None}
@router.get("/stats")
async def get_stats(
proxy_service: ProxyService = Depends(get_proxy_service),
scheduler_service: SchedulerService = Depends(get_scheduler_service),
):
try:
stats = await proxy_service.get_stats()
stats["scheduler_running"] = scheduler_service.running
return success_response("获取统计信息成功", stats)
except Exception as e:
logger.error(f"Get stats failed: {e}")
return error_response("获取统计信息失败")
@router.post("")
async def list_proxies(
request: ProxyListRequest,
service: ProxyService = Depends(get_proxy_service),
):
proxies, total = await service.list_proxies(
page=request.page,
page_size=request.page_size,
protocol=request.protocol,
min_score=request.min_score,
max_score=request.max_score,
sort_by=request.sort_by,
sort_order=request.sort_order,
)
return success_response(
"获取代理列表成功",
{
"list": [
{
"ip": p.ip,
"port": p.port,
"protocol": p.protocol,
"score": p.score,
"last_check": p.last_check.isoformat() if p.last_check else None,
}
for p in proxies
],
"total": total,
"page": request.page,
"page_size": request.page_size,
},
)
@router.get("/random")
async def get_random_proxy(service: ProxyService = Depends(get_proxy_service)):
proxy = await service.get_random_proxy()
if not proxy:
return error_response("没有找到可用的代理", 404)
return success_response(
"获取随机代理成功",
{
"ip": proxy.ip,
"port": proxy.port,
"protocol": proxy.protocol,
"score": proxy.score,
"last_check": proxy.last_check.isoformat() if proxy.last_check else None,
},
)
@router.get("/export/{fmt}")
async def export_proxies(
fmt: str,
protocol: Optional[str] = None,
limit: int = Query(default=10000, ge=1, le=100000),
service: ProxyService = Depends(get_proxy_service),
):
if fmt not in ("csv", "txt", "json"):
return error_response("不支持的导出格式", 400)
from fastapi.responses import StreamingResponse
media_types = {"csv": "text/csv", "txt": "text/plain", "json": "application/json"}
async def generate():
async for chunk in service.export_proxies(fmt, protocol, limit):
yield chunk
return StreamingResponse(
generate(),
media_type=media_types[fmt],
headers={"Content-Disposition": f"attachment; filename=proxies.{fmt}"},
)
@router.delete("/{ip}/{port}")
async def delete_proxy(ip: str, port: int, service: ProxyService = Depends(get_proxy_service)):
await service.delete_proxy(ip, port)
return success_response("删除代理成功")
@router.post("/batch-delete")
async def batch_delete(
request: BatchDeleteRequest,
service: ProxyService = Depends(get_proxy_service),
):
proxies = [(item.ip, item.port) for item in request.proxies]
deleted = await service.batch_delete(proxies)
return success_response(f"批量删除 {deleted} 个代理成功", {"deleted_count": deleted})
@router.delete("/clean-invalid")
async def clean_invalid(service: ProxyService = Depends(get_proxy_service)):
count = await service.clean_invalid()
return success_response(f"清理了 {count} 个无效代理", {"deleted_count": count})