- proxy_service.py: 将requests和WhitelistManager改为函数内延迟import, 避免启动时加载不需要的依赖;移除未使用的get_exit_ip_via_proxy导入 - account_service.py: 移除未使用的func、joinedload、AuditLog、 user_has_permission顶层导入
241 lines
8.4 KiB
Python
241 lines
8.4 KiB
Python
"""代理 & 白名单服务层:封装 core/ 代理/白名单逻辑,供路由调用。"""
|
|
|
|
import asyncio
|
|
import re
|
|
import threading
|
|
import uuid
|
|
from typing import Optional
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from core.douyu.proxy import resolve_working_proxy, verify_proxy_url, parse_proxy_response
|
|
from ..models import ProxyConfig as ProxyConfigModel, AuditLog
|
|
|
|
|
|
class ProxyService:
|
|
"""代理 & 白名单服务:管理代理配置、执行测试。"""
|
|
|
|
def __init__(self):
|
|
# 运行中的测试: test_id -> {log_queue, loop}
|
|
self._active_tests: dict[str, dict] = {}
|
|
self._active_tests_lock = threading.Lock()
|
|
|
|
# ---- 配置 CRUD ----
|
|
|
|
@staticmethod
|
|
def get_or_create(db: Session) -> ProxyConfigModel:
|
|
"""获取或创建全局代理配置(单条记录)。"""
|
|
cfg = db.query(ProxyConfigModel).first()
|
|
if not cfg:
|
|
cfg = ProxyConfigModel()
|
|
db.add(cfg)
|
|
db.commit()
|
|
db.refresh(cfg)
|
|
return cfg
|
|
|
|
@staticmethod
|
|
def update_config(db: Session, enabled, api_url, http, https,
|
|
whitelist_enabled, whitelist_uid, whitelist_ukey,
|
|
current_user) -> ProxyConfigModel:
|
|
"""更新代理配置并记录审计日志。"""
|
|
cfg = ProxyService.get_or_create(db)
|
|
cfg.enabled = enabled
|
|
cfg.api_url = api_url
|
|
cfg.http = http
|
|
cfg.https = https
|
|
cfg.whitelist_enabled = whitelist_enabled
|
|
cfg.whitelist_uid = whitelist_uid
|
|
cfg.whitelist_ukey = whitelist_ukey
|
|
db.commit()
|
|
db.refresh(cfg)
|
|
|
|
db.add(AuditLog(
|
|
user_id=current_user.id,
|
|
username=current_user.username,
|
|
action="proxy:update",
|
|
target="proxy_config",
|
|
))
|
|
db.commit()
|
|
return cfg
|
|
|
|
# ---- 测试任务管理 ----
|
|
|
|
def register_test(self) -> tuple[str, asyncio.Queue, asyncio.AbstractEventLoop]:
|
|
"""注册一个新测试任务,返回 (test_id, log_queue, loop)。"""
|
|
test_id = uuid.uuid4().hex[:12]
|
|
log_queue = asyncio.Queue()
|
|
loop = asyncio.get_running_loop()
|
|
with self._active_tests_lock:
|
|
self._active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
|
|
return test_id, log_queue, loop
|
|
|
|
def get_test(self, test_id: str) -> Optional[dict]:
|
|
"""获取测试任务信息。"""
|
|
with self._active_tests_lock:
|
|
return self._active_tests.get(test_id)
|
|
|
|
def remove_test(self, test_id: str) -> None:
|
|
"""移除测试任务。"""
|
|
with self._active_tests_lock:
|
|
self._active_tests.pop(test_id, None)
|
|
|
|
# ---- 代理测试 ----
|
|
|
|
def run_proxy_test(
|
|
self,
|
|
cfg: ProxyConfigModel,
|
|
log_queue: asyncio.Queue,
|
|
loop: asyncio.AbstractEventLoop,
|
|
):
|
|
"""在线程中执行代理测试。"""
|
|
|
|
def push(level, message):
|
|
asyncio.run_coroutine_threadsafe(
|
|
log_queue.put({"level": level, "message": message}),
|
|
loop,
|
|
)
|
|
|
|
try:
|
|
if not cfg.enabled:
|
|
push("error", "代理未启用")
|
|
push("result", "")
|
|
return
|
|
|
|
# 静态代理
|
|
if cfg.http or cfg.https:
|
|
proxy_url = cfg.http or cfg.https
|
|
push("info", f"验证静态代理: {proxy_url}")
|
|
ok, msg = verify_proxy_url(proxy_url)
|
|
push("success" if ok else "error", msg)
|
|
push("result", "")
|
|
return
|
|
|
|
# API代理
|
|
if cfg.api_url:
|
|
whitelist_uid = cfg.whitelist_uid if cfg.whitelist_enabled else ""
|
|
whitelist_ukey = cfg.whitelist_ukey if cfg.whitelist_enabled else ""
|
|
|
|
proxy_url, msg = resolve_working_proxy(
|
|
api_url=cfg.api_url,
|
|
whitelist_uid=whitelist_uid,
|
|
whitelist_ukey=whitelist_ukey,
|
|
max_attempts=3,
|
|
log_func=push,
|
|
)
|
|
if proxy_url:
|
|
push("success", f"代理可用: {proxy_url}")
|
|
else:
|
|
push("error", msg)
|
|
push("result", "")
|
|
return
|
|
|
|
push("error", "未配置代理地址或API")
|
|
push("result", "")
|
|
except Exception as e:
|
|
push("error", f"测试异常: {e}")
|
|
push("result", "")
|
|
|
|
# ---- 白名单测试 ----
|
|
|
|
def run_whitelist_test(
|
|
self,
|
|
cfg: ProxyConfigModel,
|
|
log_queue: asyncio.Queue,
|
|
loop: asyncio.AbstractEventLoop,
|
|
):
|
|
"""在线程中执行白名单测试。"""
|
|
import requests as req_lib
|
|
from core.douyu.whitelist import WhitelistManager
|
|
|
|
def push(level, message):
|
|
asyncio.run_coroutine_threadsafe(
|
|
log_queue.put({"level": level, "message": message}),
|
|
loop,
|
|
)
|
|
|
|
try:
|
|
if not cfg.whitelist_enabled:
|
|
push("error", "白名单未启用")
|
|
push("result", "")
|
|
return
|
|
if not cfg.whitelist_uid or not cfg.whitelist_ukey:
|
|
push("error", "未配置白名单UID/UKEY")
|
|
push("result", "")
|
|
return
|
|
|
|
manager = WhitelistManager(cfg.whitelist_uid, cfg.whitelist_ukey)
|
|
|
|
# 1. 测试API连接
|
|
push("info", "测试白名单API连接...")
|
|
ok, msg = manager.test_connection()
|
|
push("info" if ok else "error", f"白名单API: {msg}")
|
|
if not ok:
|
|
push("result", "")
|
|
return
|
|
|
|
# 2. 获取本机公网IP
|
|
push("info", "正在获取本机公网IP...")
|
|
local_ip = None
|
|
if cfg.api_url:
|
|
try:
|
|
resp = req_lib.get(cfg.api_url, timeout=10)
|
|
text = resp.text.strip()
|
|
push("info", f"代理API响应: {text[:80]}")
|
|
_, whitelist_ip = parse_proxy_response(text)
|
|
if whitelist_ip:
|
|
local_ip = whitelist_ip
|
|
push("info", f"从代理API获取到本机IP: {local_ip}")
|
|
except Exception as e:
|
|
push("warning", f"代理API请求失败: {e}")
|
|
|
|
if not local_ip:
|
|
push("info", "通过IP检测服务获取本机公网IP...")
|
|
for url in [
|
|
'https://qifu-api.baidubce.com/ip/local/geo/v1/district',
|
|
'https://myip.ipip.net',
|
|
'https://4.ipw.cn',
|
|
]:
|
|
try:
|
|
resp = req_lib.get(url, timeout=6, headers={"User-Agent": "Mozilla/5.0"})
|
|
match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', resp.text)
|
|
if match:
|
|
local_ip = match.group(1)
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
if not local_ip:
|
|
push("error", "无法获取本机公网IP")
|
|
push("result", "")
|
|
return
|
|
|
|
push("info", f"本机公网IP: {local_ip}")
|
|
|
|
# 3. 检查并同步白名单
|
|
records = manager.get_whitelist_json()
|
|
in_list = any(r.get('IP') == local_ip for r in records)
|
|
push("info", f"白名单共 {len(records)} 条记录")
|
|
|
|
if in_list:
|
|
record = next((r for r in records if r.get('IP') == local_ip), {})
|
|
memo = record.get('MEMO', '')
|
|
if memo == manager.memo:
|
|
push("success", f"本机IP {local_ip} 已在白名单中 (备注正确)")
|
|
else:
|
|
push("warning", f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...')
|
|
sync_ok, sync_msg = manager.sync_ip(local_ip)
|
|
push("success" if sync_ok else "error", f"白名单更新: {sync_msg}")
|
|
else:
|
|
push("info", f"正在将 {local_ip} 添加到白名单...")
|
|
sync_ok, sync_msg = manager.sync_ip(local_ip)
|
|
push("success" if sync_ok else "error", f"白名单同步: {sync_msg}")
|
|
|
|
push("result", "")
|
|
except Exception as e:
|
|
push("error", f"测试异常: {e}")
|
|
push("result", "")
|
|
|
|
|
|
# 模块级单例
|
|
proxy_service = ProxyService()
|