增加了代理平台和日志

This commit is contained in:
yml2213
2026-06-24 14:22:29 +08:00
parent 0d03b2c242
commit 83345dfdc5
21 changed files with 1134 additions and 444 deletions
+2 -2
View File
@@ -17,10 +17,10 @@ from utils import setup_logger
@asynccontextmanager
async def lifespan(app: FastAPI):
# 初始化日志(控制台 + 文件)
# 初始化日志(控制台 + 按天命名文件)
_log_level = os.getenv("LOG_LEVEL", "DEBUG")
_log_dir = Path(__file__).resolve().parents[2] / "logs"
setup_logger(level=_log_level, log_file=str(_log_dir / "app.log"))
setup_logger(level=_log_level, log_dir=str(_log_dir))
init_db()
yield
@@ -0,0 +1,33 @@
"""代理白名单平台抽象:新增 whitelist_platform / whitelist_credentials 字段
Revision ID: 20260624_0003
Revises: 20260624_0002
Create Date: 2026-06-24
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "20260624_0003"
down_revision: Union[str, None] = "20260624_0002"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
with op.batch_alter_table("proxy_config") as batch:
batch.add_column(
sa.Column("whitelist_platform", sa.String(32), server_default="xiequ")
)
batch.add_column(
sa.Column("whitelist_credentials", sa.JSON(), nullable=True)
)
def downgrade() -> None:
with op.batch_alter_table("proxy_config") as batch:
batch.drop_column("whitelist_credentials")
batch.drop_column("whitelist_platform")
+3
View File
@@ -81,6 +81,9 @@ class ProxyConfig(Base):
https = Column(EncryptedText(), default="")
# 白名单
whitelist_enabled = Column(Boolean, default=False)
whitelist_platform = Column(String(32), default="xiequ")
whitelist_credentials = Column(JSON, nullable=True)
# 旧字段保留(向后兼容双写)
whitelist_uid = Column(EncryptedText(), default="")
whitelist_ukey = Column(EncryptedText(), default="")
+26 -6
View File
@@ -7,9 +7,12 @@ from sqlalchemy.orm import Session
from ..database import get_db
from ..models import User
from ..schemas import ProxyConfigOut, ProxyConfigUpdate
from ..schemas import ProxyConfigOut, ProxyConfigUpdate, PlatformInfo, PlatformFieldDef
from ..deps import require_permission, authenticate_websocket
from ..services.proxy_service import proxy_service
from core.douyu.proxy_platforms import (
get_platform_names, get_platform_labels, get_credential_fields,
)
router = APIRouter(prefix="/api/proxy", tags=["代理与白名单"])
@@ -30,17 +33,34 @@ def update_proxy_config(
):
return proxy_service.update_config(
db,
enabled=req.enabled,
api_url=req.api_url,
http=req.http,
https=req.https,
whitelist_enabled=req.whitelist_enabled,
enabled=req.enabled if req.enabled is not None else False,
api_url=req.api_url if req.api_url is not None else "",
http=req.http if req.http is not None else "",
https=req.https if req.https is not None else "",
whitelist_enabled=req.whitelist_enabled if req.whitelist_enabled is not None else False,
whitelist_platform=req.whitelist_platform or "xiequ",
whitelist_credentials=req.whitelist_credentials,
whitelist_uid=req.whitelist_uid,
whitelist_ukey=req.whitelist_ukey,
current_user=current,
)
@router.get("/platforms", response_model=list[PlatformInfo])
def list_platforms():
"""获取所有可用的代理白名单平台及其凭据字段定义。"""
labels = get_platform_labels()
result = []
for name in get_platform_names():
fields = get_credential_fields(name)
result.append(PlatformInfo(
name=name,
label=labels.get(name, name),
credential_fields=[PlatformFieldDef(**f) for f in fields],
))
return result
# ---- WebSocket 日志推送 ----
@router.websocket("/ws/test/{test_id}")
+29 -2
View File
@@ -167,12 +167,39 @@ class ProxyConfigOut(BaseModel):
http: str = ""
https: str = ""
whitelist_enabled: bool = False
whitelist_platform: str = "xiequ"
whitelist_credentials: Optional[dict] = None
# 旧字段保留(向后兼容)
whitelist_uid: str = ""
whitelist_ukey: str = ""
class ProxyConfigUpdate(ProxyConfigOut):
pass
class ProxyConfigUpdate(BaseModel):
enabled: Optional[bool] = None
api_url: Optional[str] = None
http: Optional[str] = None
https: Optional[str] = None
whitelist_enabled: Optional[bool] = None
whitelist_platform: Optional[str] = "xiequ"
whitelist_credentials: Optional[dict] = None
# 旧字段保留(向后兼容)
whitelist_uid: Optional[str] = None
whitelist_ukey: Optional[str] = None
# ---- 代理平台元信息 ----
class PlatformFieldDef(BaseModel):
"""平台凭据字段定义。"""
key: str
label: str
placeholder: str = ""
class PlatformInfo(BaseModel):
"""平台元信息。"""
name: str
label: str
credential_fields: list[PlatformFieldDef]
# ---- 通用 ----
+12 -4
View File
@@ -55,12 +55,20 @@ class LoginBatchRunner:
# 共享代理管理器(带锁,避免并发白名单限流;极验失败时可刷新代理)
self._shared_proxy_manager = None
if proxy_config and proxy_config.enabled and proxy_config.api_url:
wl_uid = proxy_config.whitelist_uid or "" if proxy_config.whitelist_enabled else ""
wl_ukey = proxy_config.whitelist_ukey or "" if proxy_config.whitelist_enabled else ""
wl_platform = "xiequ"
wl_credentials = None
if proxy_config.whitelist_enabled:
wl_platform = getattr(proxy_config, 'whitelist_platform', None) or "xiequ"
wl_credentials = getattr(proxy_config, 'whitelist_credentials', None)
# 向后兼容:旧字段有值但新字段为空
if not wl_credentials and proxy_config.whitelist_uid and proxy_config.whitelist_ukey:
wl_platform = "xiequ"
wl_credentials = {"uid": proxy_config.whitelist_uid, "ukey": proxy_config.whitelist_ukey}
self._shared_proxy_manager = get_proxy_manager(
proxy_config.api_url,
whitelist_uid=wl_uid,
whitelist_ukey=wl_ukey,
whitelist_platform=wl_platform,
whitelist_credentials=wl_credentials,
)
def stop(self):
+89 -42
View File
@@ -9,9 +9,31 @@ from typing import Optional
from sqlalchemy.orm import Session
from core.douyu.proxy import resolve_working_proxy, verify_proxy_url, parse_proxy_response
from core.douyu.proxy_platforms import create_adapter, get_platform_labels
from core.douyu.proxy_platforms.base import _get_local_exit_ip
from ..models import ProxyConfig as ProxyConfigModel, AuditLog
def _build_whitelist_params(cfg: ProxyConfigModel) -> dict:
"""从 ProxyConfig 构建白名单参数,自动处理新旧字段兼容。
Returns:
{"whitelist_platform": str, "whitelist_credentials": dict|None}
"""
platform = getattr(cfg, 'whitelist_platform', None) or "xiequ"
credentials = getattr(cfg, 'whitelist_credentials', None)
# 向后兼容:旧字段有值但新字段为空时,自动迁移
if not credentials and cfg.whitelist_uid and cfg.whitelist_ukey:
platform = "xiequ"
credentials = {"uid": cfg.whitelist_uid, "ukey": cfg.whitelist_ukey}
return {
"whitelist_platform": platform,
"whitelist_credentials": credentials if cfg.whitelist_enabled else None,
}
class ProxyService:
"""代理 & 白名单服务:管理代理配置、执行测试。"""
@@ -31,12 +53,26 @@ class ProxyService:
db.add(cfg)
db.commit()
db.refresh(cfg)
# 应用层自动迁移:旧字段有值但新字段为空时,填充新字段
if cfg.whitelist_uid and cfg.whitelist_ukey and not cfg.whitelist_credentials:
cfg.whitelist_platform = "xiequ"
cfg.whitelist_credentials = {"uid": cfg.whitelist_uid, "ukey": cfg.whitelist_ukey}
db.commit()
return cfg
@staticmethod
def update_config(db: Session, enabled, api_url, http, https,
whitelist_enabled, whitelist_uid, whitelist_ukey,
current_user) -> ProxyConfigModel:
def update_config(
db: Session,
enabled, api_url, http, https,
whitelist_enabled,
whitelist_platform="xiequ",
whitelist_credentials=None,
whitelist_uid=None,
whitelist_ukey=None,
current_user=None,
) -> ProxyConfigModel:
"""更新代理配置并记录审计日志。"""
cfg = ProxyService.get_or_create(db)
cfg.enabled = enabled
@@ -44,18 +80,29 @@ class ProxyService:
cfg.http = http
cfg.https = https
cfg.whitelist_enabled = whitelist_enabled
cfg.whitelist_uid = whitelist_uid
cfg.whitelist_ukey = whitelist_ukey
cfg.whitelist_platform = whitelist_platform
cfg.whitelist_credentials = whitelist_credentials
# 双写:协固平台同步到旧字段,其他平台清空旧字段
if whitelist_platform == "xiequ" and whitelist_credentials:
cfg.whitelist_uid = whitelist_credentials.get("uid", "")
cfg.whitelist_ukey = whitelist_credentials.get("ukey", "")
else:
# 非协固平台,旧字段使用传入值或清空
cfg.whitelist_uid = whitelist_uid or ""
cfg.whitelist_ukey = whitelist_ukey or ""
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()
if current_user:
db.add(AuditLog(
user_id=current_user.id,
username=current_user.username,
action="proxy:update",
target="proxy_config",
))
db.commit()
return cfg
# ---- 测试任务管理 ----
@@ -112,13 +159,12 @@ class ProxyService:
# 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 ""
wl_params = _build_whitelist_params(cfg)
proxy_url, msg = resolve_working_proxy(
api_url=cfg.api_url,
whitelist_uid=whitelist_uid,
whitelist_ukey=whitelist_ukey,
whitelist_platform=wl_params["whitelist_platform"],
whitelist_credentials=wl_params["whitelist_credentials"],
max_attempts=3,
log_func=push,
)
@@ -145,7 +191,6 @@ class ProxyService:
):
"""在线程中执行白名单测试。"""
import requests as req_lib
from core.douyu.whitelist import WhitelistManager
def push(level, message):
asyncio.run_coroutine_threadsafe(
@@ -158,16 +203,28 @@ class ProxyService:
push("error", "白名单未启用")
push("result", "")
return
if not cfg.whitelist_uid or not cfg.whitelist_ukey:
push("error", "未配置白名单UID/UKEY")
# 构建适配器
wl_params = _build_whitelist_params(cfg)
credentials = wl_params["whitelist_credentials"]
platform = wl_params["whitelist_platform"]
if not credentials:
push("error", "未配置白名单凭据")
push("result", "")
return
manager = WhitelistManager(cfg.whitelist_uid, cfg.whitelist_ukey)
adapter = create_adapter(platform, credentials)
if not adapter:
push("error", f"不支持的白名单平台: {platform}")
push("result", "")
return
push("info", f"当前白名单平台: {adapter.platform_label}")
# 1. 测试API连接
push("info", "测试白名单API连接...")
ok, msg = manager.test_connection()
ok, msg = adapter.test_connection()
push("info" if ok else "error", f"白名单API: {msg}")
if not ok:
push("result", "")
@@ -190,19 +247,7 @@ class ProxyService:
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
local_ip = _get_local_exit_ip()
if not local_ip:
push("error", "无法获取本机公网IP")
@@ -212,22 +257,24 @@ class ProxyService:
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)} 条记录")
records = adapter.get_whitelist()
in_list = any(r.get('ip') == local_ip for r in records)
if 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:
record = next((r for r in records if r.get('ip') == local_ip), {})
memo = record.get('memo', '')
if memo == adapter.memo:
push("success", f"本机IP {local_ip} 已在白名单中 (备注正确)")
else:
push("warning", f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...')
sync_ok, sync_msg = manager.sync_ip(local_ip)
sync_ok, sync_msg = adapter.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)
sync_ok, sync_msg = adapter.sync_ip(local_ip)
push("success" if sync_ok else "error", f"白名单同步: {sync_msg}")
push("result", "")