Files
ProxyPool/app/models/schemas.py
祀梦 0131c8b408 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
2026-04-05 13:39:19 +08:00

137 lines
4.0 KiB
Python
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.

"""Pydantic 模型 - 用于 API 请求/响应校验"""
from pydantic import BaseModel, Field, field_validator, ConfigDict
from typing import Optional, List
class ProxyCreate(BaseModel):
ip: str
port: int = Field(ge=1, le=65535)
protocol: str = "http"
score: int = Field(default=10, ge=0, le=100)
@field_validator("protocol")
@classmethod
def validate_protocol(cls, v: str):
v = v.lower().strip()
if v not in ("http", "https", "socks4", "socks5"):
raise ValueError("protocol must be http, https, socks4 or socks5")
return v
class ProxyResponse(BaseModel):
ip: str
port: int
protocol: str
score: int
response_time_ms: Optional[float] = None
last_check: Optional[str] = None
validated: int = 0
class PluginResponse(BaseModel):
id: str
name: str
display_name: str
description: str
enabled: bool
last_run: Optional[str] = None
success_count: int = 0
failure_count: int = 0
class SettingsSchema(BaseModel):
model_config = ConfigDict(extra="ignore")
validation_timeout: int = Field(default=6, ge=3, le=60)
default_concurrency: int = Field(default=120, ge=10, le=400)
min_proxy_score: int = Field(default=0, ge=0, le=100)
proxy_expiry_days: int = Field(default=7, ge=1, le=30)
auto_validate: bool = True
auto_validate_after_crawl: bool = False
validate_interval_minutes: int = Field(default=30, ge=5, le=1440)
validation_targets: List[str] = Field(
default=[
"http://httpbin.org/ip",
"https://httpbin.org/ip",
"http://api.ipify.org",
"https://api.ipify.org",
"http://www.baidu.com",
"http://www.qq.com",
]
)
class CrawlSummarySchema(BaseModel):
"""单次爬取任务结果(与 CrawlJob 返回的 result 对齐)"""
plugin_id: str
proxy_count: int
crawl_failed: bool = False
error: Optional[str] = None
success_count: int = 0 # 与 proxy_count 相同,兼容旧前端
failure_count: int = 0
class ProxyListRequest(BaseModel):
page: int = Field(default=1, ge=1)
page_size: int = Field(default=20, ge=1, le=100)
protocol: Optional[str] = None
min_score: int = Field(default=0, ge=0)
max_score: Optional[int] = Field(default=None, ge=0)
sort_by: str = "last_check"
sort_order: str = "DESC"
pool_filter: Optional[str] = Field(
default=None,
description="all 或不传=全部pending=待验证available=已验证且可用",
)
@field_validator("pool_filter")
@classmethod
def validate_pool_filter(cls, v: Optional[str]):
if v is None or v == "" or v == "all":
return None
allowed = ("pending", "available")
if v not in allowed:
raise ValueError(f"pool_filter 必须是 {allowed} 之一或 all")
return v
@field_validator("protocol")
@classmethod
def validate_protocol(cls, v):
if v is not None and v.lower() not in ("http", "https", "socks4", "socks5"):
raise ValueError("协议类型必须是 http, https, socks4 或 socks5")
return v.lower() if v else v
@field_validator("sort_by")
@classmethod
def validate_sort_by(cls, v):
if v not in ("ip", "port", "protocol", "score", "last_check"):
raise ValueError("排序字段必须是 ip, port, protocol, score 或 last_check")
return v
@field_validator("sort_order")
@classmethod
def validate_sort_order(cls, v):
if v.upper() not in ("ASC", "DESC"):
raise ValueError("排序方式必须是 ASC 或 DESC")
return v.upper()
class ProxyDeleteItem(BaseModel):
ip: str
port: int = Field(ge=1, le=65535)
class BatchDeleteRequest(BaseModel):
proxies: List[ProxyDeleteItem] = Field(max_length=1000)
class PluginToggleRequest(BaseModel):
enabled: bool
class ExportRequest(BaseModel):
format: str = Field(pattern=r"^(csv|txt|json)$")
protocol: Optional[str] = None
limit: int = Field(default=10000, ge=1, le=100000)