"""代理业务服务""" import csv import json import io from datetime import datetime from typing import List, Optional, Tuple, AsyncIterator from app.core.db import get_db from app.repositories.proxy_repo import ProxyRepository from app.repositories.settings_repo import SettingsRepository from app.models.domain import Proxy from app.core.log import logger from app.core.config import settings as app_settings from app.services.proxy_scoring import compute_proxy_quality_score class ProxyService: def __init__(self, proxy_repo: ProxyRepository = ProxyRepository()): self.proxy_repo = proxy_repo async def get_stats(self) -> dict: async with get_db() as db: s = await SettingsRepository.get_all(db) floor = int(s.get("min_proxy_score", 0)) stats = await self.proxy_repo.get_stats(db, low_score_threshold=floor) stats["today_new"] = await self.proxy_repo.get_today_new_count(db) return stats async def list_proxies( self, page: int = 1, page_size: int = 20, protocol: Optional[str] = None, min_score: int = 0, max_score: Optional[int] = None, sort_by: str = "last_check", sort_order: str = "DESC", pool_filter: Optional[str] = None, ) -> Tuple[List[Proxy], int]: async with get_db() as db: return await self.proxy_repo.list_paginated( db, page, page_size, protocol, min_score, max_score, sort_by, sort_order, pool_filter=pool_filter, ) async def get_random_proxy(self) -> Optional[Proxy]: async with get_db() as db: s = await SettingsRepository.get_all(db) floor = int(s.get("min_proxy_score", 0)) ms = max(1, floor) p = await self.proxy_repo.get_random(db, min_score=ms) if not p: return None new_uc = int(getattr(p, "use_count", 0) or 0) + 1 q_score = compute_proxy_quality_score( p.response_time_ms, new_uc, app_settings ) await self.proxy_repo.set_use_count_and_score( db, p.ip, p.port, new_uc, q_score ) p.use_count = new_uc p.score = q_score return p async def delete_proxy(self, ip: str, port: int) -> None: async with get_db() as db: await self.proxy_repo.delete(db, ip, port) async def batch_delete(self, proxies: List[Tuple[str, int]]) -> int: async with get_db() as db: return await self.proxy_repo.batch_delete(db, proxies) async def clean_invalid(self) -> int: async with get_db() as db: s = await SettingsRepository.get_all(db) floor = int(s.get("min_proxy_score", 0)) return await self.proxy_repo.clean_invalid(db, low_score_threshold=floor) async def clean_expired(self, days: int) -> int: async with get_db() as db: return await self.proxy_repo.clean_expired(db, days) async def export_proxies( self, fmt: str, protocol: Optional[str] = None, limit: Optional[int] = None, ) -> AsyncIterator[str]: cap = int(app_settings.export_max_records) if limit is None else int(limit) if cap < 1: cap = 1 if fmt == "csv": yield "\ufeffIP,Port,Protocol,Score,Last Check\n" elif fmt == "txt": pass elif fmt == "json": yield "[" first = True exported = 0 async with get_db() as db: s = await SettingsRepository.get_all(db) floor = max(1, int(s.get("min_proxy_score", 0))) async for batch in self.proxy_repo.iter_batches( db, protocol=protocol, batch_size=1000, only_usable=True, usable_min_score=floor, ): for p in batch: if exported >= cap: break if fmt == "csv": yield f"{p.ip},{p.port},{p.protocol},{p.score},{self._fmt_time(p.last_check)}\n" elif fmt == "txt": yield f"{p.ip}:{p.port}\n" elif fmt == "json": item = { "ip": p.ip, "port": p.port, "protocol": p.protocol, "score": p.score, "last_check": self._fmt_time(p.last_check), } prefix = "" if first else "," yield prefix + json.dumps(item, ensure_ascii=False) first = False exported += 1 if exported >= cap: break if fmt == "json": yield "]" @staticmethod def _fmt_time(dt: Optional[datetime]) -> str: if not dt: return "" if isinstance(dt, str): return dt return dt.isoformat()