mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-06-29 03:01:34 +08:00
4
This commit is contained in:
@@ -78,6 +78,11 @@ def create_app() -> FastAPI:
|
||||
"""账号管理页面"""
|
||||
return templates.TemplateResponse("accounts.html", {"request": request})
|
||||
|
||||
@app.get("/email-services", response_class=HTMLResponse)
|
||||
async def email_services_page(request: Request):
|
||||
"""邮箱服务管理页面"""
|
||||
return templates.TemplateResponse("email_services.html", {"request": request})
|
||||
|
||||
@app.get("/settings", response_class=HTMLResponse)
|
||||
async def settings_page(request: Request):
|
||||
"""设置页面"""
|
||||
|
||||
@@ -26,10 +26,14 @@ class AccountResponse(BaseModel):
|
||||
"""账号响应模型"""
|
||||
id: int
|
||||
email: str
|
||||
password: Optional[str] = None
|
||||
client_id: Optional[str] = None
|
||||
email_service: str
|
||||
account_id: Optional[str] = None
|
||||
workspace_id: Optional[str] = None
|
||||
registered_at: Optional[str] = None
|
||||
last_refresh: Optional[str] = None
|
||||
expires_at: Optional[str] = None
|
||||
status: str
|
||||
proxy_used: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
@@ -69,10 +73,14 @@ def account_to_response(account: Account) -> AccountResponse:
|
||||
return AccountResponse(
|
||||
id=account.id,
|
||||
email=account.email,
|
||||
password=account.password,
|
||||
client_id=account.client_id,
|
||||
email_service=account.email_service,
|
||||
account_id=account.account_id,
|
||||
workspace_id=account.workspace_id,
|
||||
registered_at=account.registered_at.isoformat() if account.registered_at else None,
|
||||
last_refresh=account.last_refresh.isoformat() if account.last_refresh else None,
|
||||
expires_at=account.expires_at.isoformat() if account.expires_at else None,
|
||||
status=account.status,
|
||||
proxy_used=account.proxy_used,
|
||||
created_at=account.created_at.isoformat() if account.created_at else None,
|
||||
@@ -260,13 +268,18 @@ async def export_accounts_json(
|
||||
for acc in accounts:
|
||||
export_data.append({
|
||||
"email": acc.email,
|
||||
"password": acc.password,
|
||||
"client_id": acc.client_id,
|
||||
"account_id": acc.account_id,
|
||||
"workspace_id": acc.workspace_id,
|
||||
"access_token": acc.access_token,
|
||||
"refresh_token": acc.refresh_token,
|
||||
"id_token": acc.id_token,
|
||||
"session_token": acc.session_token,
|
||||
"email_service": acc.email_service,
|
||||
"registered_at": acc.registered_at.isoformat() if acc.registered_at else None,
|
||||
"last_refresh": acc.last_refresh.isoformat() if acc.last_refresh else None,
|
||||
"expires_at": acc.expires_at.isoformat() if acc.expires_at else None,
|
||||
"status": acc.status,
|
||||
})
|
||||
|
||||
@@ -310,9 +323,10 @@ async def export_accounts_csv(
|
||||
|
||||
# 写入表头
|
||||
writer.writerow([
|
||||
"ID", "Email", "Account ID", "Workspace ID",
|
||||
"Access Token", "Refresh Token", "ID Token",
|
||||
"Email Service", "Status", "Registered At"
|
||||
"ID", "Email", "Password", "Client ID",
|
||||
"Account ID", "Workspace ID",
|
||||
"Access Token", "Refresh Token", "ID Token", "Session Token",
|
||||
"Email Service", "Status", "Registered At", "Last Refresh", "Expires At"
|
||||
])
|
||||
|
||||
# 写入数据
|
||||
@@ -320,14 +334,19 @@ async def export_accounts_csv(
|
||||
writer.writerow([
|
||||
acc.id,
|
||||
acc.email,
|
||||
acc.password or "",
|
||||
acc.client_id or "",
|
||||
acc.account_id or "",
|
||||
acc.workspace_id or "",
|
||||
acc.access_token or "",
|
||||
acc.refresh_token or "",
|
||||
acc.id_token or "",
|
||||
acc.session_token or "",
|
||||
acc.email_service,
|
||||
acc.status,
|
||||
acc.registered_at.isoformat() if acc.registered_at else ""
|
||||
acc.registered_at.isoformat() if acc.registered_at else "",
|
||||
acc.last_refresh.isoformat() if acc.last_refresh else "",
|
||||
acc.expires_at.isoformat() if acc.expires_at else ""
|
||||
])
|
||||
|
||||
# 生成文件名
|
||||
@@ -367,3 +386,123 @@ async def get_accounts_stats():
|
||||
"by_status": {status: count for status, count in status_stats},
|
||||
"by_email_service": {service: count for service, count in service_stats}
|
||||
}
|
||||
|
||||
|
||||
# ============== Token 刷新相关 ==============
|
||||
|
||||
class TokenRefreshRequest(BaseModel):
|
||||
"""Token 刷新请求"""
|
||||
proxy: Optional[str] = None
|
||||
|
||||
|
||||
class BatchRefreshRequest(BaseModel):
|
||||
"""批量刷新请求"""
|
||||
ids: List[int]
|
||||
proxy: Optional[str] = None
|
||||
|
||||
|
||||
class TokenValidateRequest(BaseModel):
|
||||
"""Token 验证请求"""
|
||||
proxy: Optional[str] = None
|
||||
|
||||
|
||||
class BatchValidateRequest(BaseModel):
|
||||
"""批量验证请求"""
|
||||
ids: List[int]
|
||||
proxy: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/{account_id}/refresh")
|
||||
async def refresh_account_token(account_id: int, request: TokenRefreshRequest = None):
|
||||
"""刷新单个账号的 Token"""
|
||||
from ...core.token_refresh import refresh_account_token as do_refresh
|
||||
|
||||
proxy = request.proxy if request else None
|
||||
result = do_refresh(account_id, proxy)
|
||||
|
||||
if result.success:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Token 刷新成功",
|
||||
"expires_at": result.expires_at.isoformat() if result.expires_at else None
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": result.error_message
|
||||
}
|
||||
|
||||
|
||||
@router.post("/batch-refresh")
|
||||
async def batch_refresh_tokens(request: BatchRefreshRequest, background_tasks: BackgroundTasks):
|
||||
"""批量刷新账号 Token"""
|
||||
from ...core.token_refresh import refresh_account_token as do_refresh
|
||||
|
||||
results = {
|
||||
"success_count": 0,
|
||||
"failed_count": 0,
|
||||
"errors": []
|
||||
}
|
||||
|
||||
for account_id in request.ids:
|
||||
try:
|
||||
result = do_refresh(account_id, request.proxy)
|
||||
if result.success:
|
||||
results["success_count"] += 1
|
||||
else:
|
||||
results["failed_count"] += 1
|
||||
results["errors"].append({"id": account_id, "error": result.error_message})
|
||||
except Exception as e:
|
||||
results["failed_count"] += 1
|
||||
results["errors"].append({"id": account_id, "error": str(e)})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@router.post("/{account_id}/validate")
|
||||
async def validate_account_token(account_id: int, request: TokenValidateRequest = None):
|
||||
"""验证单个账号的 Token 有效性"""
|
||||
from ...core.token_refresh import validate_account_token as do_validate
|
||||
|
||||
proxy = request.proxy if request else None
|
||||
is_valid, error = do_validate(account_id, proxy)
|
||||
|
||||
return {
|
||||
"id": account_id,
|
||||
"valid": is_valid,
|
||||
"error": error
|
||||
}
|
||||
|
||||
|
||||
@router.post("/batch-validate")
|
||||
async def batch_validate_tokens(request: BatchValidateRequest):
|
||||
"""批量验证账号 Token 有效性"""
|
||||
from ...core.token_refresh import validate_account_token as do_validate
|
||||
|
||||
results = {
|
||||
"valid_count": 0,
|
||||
"invalid_count": 0,
|
||||
"details": []
|
||||
}
|
||||
|
||||
for account_id in request.ids:
|
||||
try:
|
||||
is_valid, error = do_validate(account_id, request.proxy)
|
||||
results["details"].append({
|
||||
"id": account_id,
|
||||
"valid": is_valid,
|
||||
"error": error
|
||||
})
|
||||
if is_valid:
|
||||
results["valid_count"] += 1
|
||||
else:
|
||||
results["invalid_count"] += 1
|
||||
except Exception as e:
|
||||
results["invalid_count"] += 1
|
||||
results["details"].append({
|
||||
"id": account_id,
|
||||
"valid": False,
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
@@ -43,6 +43,7 @@ class EmailServiceResponse(BaseModel):
|
||||
name: str
|
||||
enabled: bool
|
||||
priority: int
|
||||
config: Optional[Dict[str, Any]] = None # 过滤敏感信息后的配置
|
||||
last_used: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
updated_at: Optional[str] = None
|
||||
@@ -82,6 +83,29 @@ class OutlookBatchImportResponse(BaseModel):
|
||||
|
||||
# ============== Helper Functions ==============
|
||||
|
||||
# 敏感字段列表,返回响应时需要过滤
|
||||
SENSITIVE_FIELDS = {'password', 'api_key', 'refresh_token', 'access_token'}
|
||||
|
||||
def filter_sensitive_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""过滤敏感配置信息"""
|
||||
if not config:
|
||||
return {}
|
||||
|
||||
filtered = {}
|
||||
for key, value in config.items():
|
||||
if key in SENSITIVE_FIELDS:
|
||||
# 敏感字段不返回,但标记是否存在
|
||||
filtered[f"has_{key}"] = bool(value)
|
||||
else:
|
||||
filtered[key] = value
|
||||
|
||||
# 为 Outlook 计算是否有 OAuth
|
||||
if config.get('client_id') and config.get('refresh_token'):
|
||||
filtered['has_oauth'] = True
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def service_to_response(service: EmailServiceModel) -> EmailServiceResponse:
|
||||
"""转换服务模型为响应"""
|
||||
return EmailServiceResponse(
|
||||
@@ -90,6 +114,7 @@ def service_to_response(service: EmailServiceModel) -> EmailServiceResponse:
|
||||
name=service.name,
|
||||
enabled=service.enabled,
|
||||
priority=service.priority,
|
||||
config=filter_sensitive_config(service.config),
|
||||
last_used=service.last_used.isoformat() if service.last_used else None,
|
||||
created_at=service.created_at.isoformat() if service.created_at else None,
|
||||
updated_at=service.updated_at.isoformat() if service.updated_at else None,
|
||||
@@ -98,6 +123,39 @@ def service_to_response(service: EmailServiceModel) -> EmailServiceResponse:
|
||||
|
||||
# ============== API Endpoints ==============
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_email_services_stats():
|
||||
"""获取邮箱服务统计信息"""
|
||||
with get_db() as db:
|
||||
from sqlalchemy import func
|
||||
|
||||
# 按类型统计
|
||||
type_stats = db.query(
|
||||
EmailServiceModel.service_type,
|
||||
func.count(EmailServiceModel.id)
|
||||
).group_by(EmailServiceModel.service_type).all()
|
||||
|
||||
# 启用数量
|
||||
enabled_count = db.query(func.count(EmailServiceModel.id)).filter(
|
||||
EmailServiceModel.enabled == True
|
||||
).scalar()
|
||||
|
||||
stats = {
|
||||
'outlook_count': 0,
|
||||
'custom_count': 0,
|
||||
'tempmail_available': True, # 临时邮箱始终可用
|
||||
'enabled_count': enabled_count
|
||||
}
|
||||
|
||||
for service_type, count in type_stats:
|
||||
if service_type == 'outlook':
|
||||
stats['outlook_count'] = count
|
||||
elif service_type == 'custom_domain':
|
||||
stats['custom_count'] = count
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
@router.get("/types")
|
||||
async def get_service_types():
|
||||
"""获取支持的邮箱服务类型"""
|
||||
@@ -435,3 +493,36 @@ async def batch_delete_outlook(service_ids: List[int]):
|
||||
db.commit()
|
||||
|
||||
return {"success": True, "deleted": deleted, "message": f"已删除 {deleted} 个服务"}
|
||||
|
||||
|
||||
# ============== 临时邮箱测试 ==============
|
||||
|
||||
class TempmailTestRequest(BaseModel):
|
||||
"""临时邮箱测试请求"""
|
||||
api_url: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/test-tempmail")
|
||||
async def test_tempmail_service(request: TempmailTestRequest):
|
||||
"""测试临时邮箱服务是否可用"""
|
||||
try:
|
||||
from ...services import EmailServiceFactory, EmailServiceType
|
||||
from ...config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
base_url = request.api_url or settings.tempmail_base_url
|
||||
|
||||
config = {"base_url": base_url}
|
||||
tempmail = EmailServiceFactory.create(EmailServiceType.TEMPMAIL, config)
|
||||
|
||||
# 检查服务健康状态
|
||||
health = tempmail.check_health()
|
||||
|
||||
if health:
|
||||
return {"success": True, "message": "临时邮箱连接正常"}
|
||||
else:
|
||||
return {"success": False, "message": "临时邮箱连接失败"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"测试临时邮箱失败: {e}")
|
||||
return {"success": False, "message": f"测试失败: {str(e)}"}
|
||||
|
||||
@@ -7,14 +7,14 @@ import logging
|
||||
import uuid
|
||||
import random
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict
|
||||
from typing import List, Optional, Dict, Tuple
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, BackgroundTasks
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...database import crud
|
||||
from ...database.session import get_db
|
||||
from ...database.models import RegistrationTask
|
||||
from ...database.models import RegistrationTask, Proxy
|
||||
from ...core.register import RegistrationEngine, RegistrationResult
|
||||
from ...services import EmailServiceFactory, EmailServiceType
|
||||
from ...config.settings import get_settings
|
||||
@@ -28,6 +28,38 @@ running_tasks: dict = {}
|
||||
batch_tasks: Dict[str, dict] = {}
|
||||
|
||||
|
||||
# ============== Proxy Helper Functions ==============
|
||||
|
||||
def get_proxy_for_registration(db) -> Tuple[Optional[str], Optional[int]]:
|
||||
"""
|
||||
获取用于注册的代理
|
||||
|
||||
策略:
|
||||
1. 优先从代理列表中随机选择一个启用的代理
|
||||
2. 如果代理列表为空,使用系统设置中的默认代理
|
||||
|
||||
Returns:
|
||||
Tuple[proxy_url, proxy_id]: 代理 URL 和代理 ID(如果来自代理列表)
|
||||
"""
|
||||
# 先尝试从代理列表中获取
|
||||
proxy = crud.get_random_proxy(db)
|
||||
if proxy:
|
||||
return proxy.proxy_url, proxy.id
|
||||
|
||||
# 代理列表为空,使用系统设置中的默认代理
|
||||
settings = get_settings()
|
||||
if settings.proxy_enabled and settings.proxy_url:
|
||||
return settings.proxy_url, None
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def update_proxy_usage(db, proxy_id: Optional[int]):
|
||||
"""更新代理的使用时间"""
|
||||
if proxy_id:
|
||||
crud.update_proxy_last_used(db, proxy_id)
|
||||
|
||||
|
||||
# ============== Pydantic Models ==============
|
||||
|
||||
class RegistrationTaskCreate(BaseModel):
|
||||
@@ -114,6 +146,20 @@ async def run_registration_task(task_uuid: str, email_service_type: str, proxy:
|
||||
logger.error(f"任务不存在: {task_uuid}")
|
||||
return
|
||||
|
||||
# 确定使用的代理
|
||||
# 如果前端传入了代理参数,使用传入的
|
||||
# 否则从代理列表或系统设置中获取
|
||||
actual_proxy_url = proxy
|
||||
proxy_id = None
|
||||
|
||||
if not actual_proxy_url:
|
||||
actual_proxy_url, proxy_id = get_proxy_for_registration(db)
|
||||
if actual_proxy_url:
|
||||
logger.info(f"任务 {task_uuid} 使用代理: {actual_proxy_url[:50]}...")
|
||||
|
||||
# 更新任务的代理记录
|
||||
crud.update_registration_task(db, task_uuid, proxy=actual_proxy_url)
|
||||
|
||||
# 创建邮箱服务
|
||||
service_type = EmailServiceType(email_service_type)
|
||||
settings = get_settings()
|
||||
@@ -140,7 +186,7 @@ async def run_registration_task(task_uuid: str, email_service_type: str, proxy:
|
||||
"base_url": settings.tempmail_base_url,
|
||||
"timeout": settings.tempmail_timeout,
|
||||
"max_retries": settings.tempmail_max_retries,
|
||||
"proxy_url": proxy,
|
||||
"proxy_url": actual_proxy_url,
|
||||
}
|
||||
elif service_type == EmailServiceType.CUSTOM_DOMAIN:
|
||||
# 检查数据库中是否有可用的自定义域名服务
|
||||
@@ -158,7 +204,7 @@ async def run_registration_task(task_uuid: str, email_service_type: str, proxy:
|
||||
config = {
|
||||
"base_url": settings.custom_domain_base_url,
|
||||
"api_key": settings.custom_domain_api_key.get_secret_value() if settings.custom_domain_api_key else "",
|
||||
"proxy_url": proxy,
|
||||
"proxy_url": actual_proxy_url,
|
||||
}
|
||||
else:
|
||||
raise ValueError("没有可用的自定义域名邮箱服务,请先在设置中配置")
|
||||
@@ -188,7 +234,7 @@ async def run_registration_task(task_uuid: str, email_service_type: str, proxy:
|
||||
|
||||
engine = RegistrationEngine(
|
||||
email_service=email_service,
|
||||
proxy_url=proxy,
|
||||
proxy_url=actual_proxy_url,
|
||||
callback_logger=log_callback,
|
||||
task_uuid=task_uuid
|
||||
)
|
||||
@@ -197,6 +243,9 @@ async def run_registration_task(task_uuid: str, email_service_type: str, proxy:
|
||||
result = engine.run()
|
||||
|
||||
if result.success:
|
||||
# 更新代理使用时间
|
||||
update_proxy_usage(db, proxy_id)
|
||||
|
||||
# 保存到数据库
|
||||
engine.save_to_database(result)
|
||||
|
||||
|
||||
@@ -135,6 +135,66 @@ async def update_proxy_settings(request: ProxySettings):
|
||||
return {"success": True, "message": "代理设置已更新"}
|
||||
|
||||
|
||||
@router.post("/proxy/test")
|
||||
async def test_proxy_settings(request: ProxySettings):
|
||||
"""测试代理连接"""
|
||||
import time
|
||||
from curl_cffi import requests as cffi_requests
|
||||
|
||||
# 构建代理 URL
|
||||
if request.type == "http":
|
||||
scheme = "http"
|
||||
elif request.type == "socks5":
|
||||
scheme = "socks5"
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="不支持的代理类型")
|
||||
|
||||
auth = ""
|
||||
if request.username and request.password:
|
||||
auth = f"{request.username}:{request.password}@"
|
||||
|
||||
proxy_url = f"{scheme}://{auth}{request.host}:{request.port}"
|
||||
|
||||
# 测试连接
|
||||
test_url = "https://api.ipify.org?format=json"
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
proxies = {
|
||||
"http": proxy_url,
|
||||
"https": proxy_url
|
||||
}
|
||||
|
||||
response = cffi_requests.get(
|
||||
test_url,
|
||||
proxies=proxies,
|
||||
timeout=3,
|
||||
impersonate="chrome110"
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
if response.status_code == 200:
|
||||
ip_info = response.json()
|
||||
return {
|
||||
"success": True,
|
||||
"ip": ip_info.get("ip", ""),
|
||||
"response_time": round(elapsed_time * 1000), # 毫秒
|
||||
"message": f"代理连接成功,出口 IP: {ip_info.get('ip', 'unknown')}"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"代理返回错误状态码: {response.status_code}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"代理连接失败: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/registration")
|
||||
async def get_registration_settings():
|
||||
"""获取注册设置"""
|
||||
@@ -292,3 +352,275 @@ async def get_recent_logs(
|
||||
}
|
||||
except Exception as e:
|
||||
return {"logs": [], "error": str(e)}
|
||||
|
||||
|
||||
# ============== 临时邮箱设置 ==============
|
||||
|
||||
class TempmailSettings(BaseModel):
|
||||
"""临时邮箱设置"""
|
||||
api_url: Optional[str] = None
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
@router.get("/tempmail")
|
||||
async def get_tempmail_settings():
|
||||
"""获取临时邮箱设置"""
|
||||
settings = get_settings()
|
||||
|
||||
return {
|
||||
"api_url": settings.tempmail_base_url,
|
||||
"timeout": settings.tempmail_timeout,
|
||||
"max_retries": settings.tempmail_max_retries,
|
||||
"enabled": True # 临时邮箱默认可用
|
||||
}
|
||||
|
||||
|
||||
@router.post("/tempmail")
|
||||
async def update_tempmail_settings(request: TempmailSettings):
|
||||
"""更新临时邮箱设置"""
|
||||
update_dict = {}
|
||||
|
||||
if request.api_url:
|
||||
update_dict["tempmail_base_url"] = request.api_url
|
||||
|
||||
update_settings(**update_dict)
|
||||
|
||||
return {"success": True, "message": "临时邮箱设置已更新"}
|
||||
|
||||
|
||||
# ============== 代理列表 CRUD ==============
|
||||
|
||||
class ProxyCreateRequest(BaseModel):
|
||||
"""创建代理请求"""
|
||||
name: str
|
||||
type: str = "http" # http, socks5
|
||||
host: str
|
||||
port: int
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
enabled: bool = True
|
||||
priority: int = 0
|
||||
|
||||
|
||||
class ProxyUpdateRequest(BaseModel):
|
||||
"""更新代理请求"""
|
||||
name: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
host: Optional[str] = None
|
||||
port: Optional[int] = None
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
enabled: Optional[bool] = None
|
||||
priority: Optional[int] = None
|
||||
|
||||
|
||||
@router.get("/proxies")
|
||||
async def get_proxies_list(enabled: Optional[bool] = None):
|
||||
"""获取代理列表"""
|
||||
with get_db() as db:
|
||||
proxies = crud.get_proxies(db, enabled=enabled)
|
||||
return {
|
||||
"proxies": [p.to_dict() for p in proxies],
|
||||
"total": len(proxies)
|
||||
}
|
||||
|
||||
|
||||
@router.post("/proxies")
|
||||
async def create_proxy_item(request: ProxyCreateRequest):
|
||||
"""创建代理"""
|
||||
with get_db() as db:
|
||||
proxy = crud.create_proxy(
|
||||
db,
|
||||
name=request.name,
|
||||
type=request.type,
|
||||
host=request.host,
|
||||
port=request.port,
|
||||
username=request.username,
|
||||
password=request.password,
|
||||
enabled=request.enabled,
|
||||
priority=request.priority
|
||||
)
|
||||
return {"success": True, "proxy": proxy.to_dict()}
|
||||
|
||||
|
||||
@router.get("/proxies/{proxy_id}")
|
||||
async def get_proxy_item(proxy_id: int):
|
||||
"""获取单个代理"""
|
||||
with get_db() as db:
|
||||
proxy = crud.get_proxy_by_id(db, proxy_id)
|
||||
if not proxy:
|
||||
raise HTTPException(status_code=404, detail="代理不存在")
|
||||
return proxy.to_dict(include_password=True)
|
||||
|
||||
|
||||
@router.patch("/proxies/{proxy_id}")
|
||||
async def update_proxy_item(proxy_id: int, request: ProxyUpdateRequest):
|
||||
"""更新代理"""
|
||||
with get_db() as db:
|
||||
update_data = {}
|
||||
if request.name is not None:
|
||||
update_data["name"] = request.name
|
||||
if request.type is not None:
|
||||
update_data["type"] = request.type
|
||||
if request.host is not None:
|
||||
update_data["host"] = request.host
|
||||
if request.port is not None:
|
||||
update_data["port"] = request.port
|
||||
if request.username is not None:
|
||||
update_data["username"] = request.username
|
||||
if request.password is not None:
|
||||
update_data["password"] = request.password
|
||||
if request.enabled is not None:
|
||||
update_data["enabled"] = request.enabled
|
||||
if request.priority is not None:
|
||||
update_data["priority"] = request.priority
|
||||
|
||||
proxy = crud.update_proxy(db, proxy_id, **update_data)
|
||||
if not proxy:
|
||||
raise HTTPException(status_code=404, detail="代理不存在")
|
||||
return {"success": True, "proxy": proxy.to_dict()}
|
||||
|
||||
|
||||
@router.delete("/proxies/{proxy_id}")
|
||||
async def delete_proxy_item(proxy_id: int):
|
||||
"""删除代理"""
|
||||
with get_db() as db:
|
||||
success = crud.delete_proxy(db, proxy_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="代理不存在")
|
||||
return {"success": True, "message": "代理已删除"}
|
||||
|
||||
|
||||
@router.post("/proxies/{proxy_id}/test")
|
||||
async def test_proxy_item(proxy_id: int):
|
||||
"""测试单个代理"""
|
||||
import time
|
||||
from curl_cffi import requests as cffi_requests
|
||||
|
||||
with get_db() as db:
|
||||
proxy = crud.get_proxy_by_id(db, proxy_id)
|
||||
if not proxy:
|
||||
raise HTTPException(status_code=404, detail="代理不存在")
|
||||
|
||||
proxy_url = proxy.proxy_url
|
||||
test_url = "https://api.ipify.org?format=json"
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
proxies = {
|
||||
"http": proxy_url,
|
||||
"https": proxy_url
|
||||
}
|
||||
|
||||
response = cffi_requests.get(
|
||||
test_url,
|
||||
proxies=proxies,
|
||||
timeout=3,
|
||||
impersonate="chrome110"
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
if response.status_code == 200:
|
||||
ip_info = response.json()
|
||||
return {
|
||||
"success": True,
|
||||
"ip": ip_info.get("ip", ""),
|
||||
"response_time": round(elapsed_time * 1000),
|
||||
"message": f"代理连接成功,出口 IP: {ip_info.get('ip', 'unknown')}"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"代理返回错误状态码: {response.status_code}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"代理连接失败: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/proxies/test-all")
|
||||
async def test_all_proxies():
|
||||
"""测试所有启用的代理"""
|
||||
import time
|
||||
from curl_cffi import requests as cffi_requests
|
||||
|
||||
with get_db() as db:
|
||||
proxies = crud.get_enabled_proxies(db)
|
||||
|
||||
results = []
|
||||
for proxy in proxies:
|
||||
proxy_url = proxy.proxy_url
|
||||
test_url = "https://api.ipify.org?format=json"
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
proxies_dict = {
|
||||
"http": proxy_url,
|
||||
"https": proxy_url
|
||||
}
|
||||
|
||||
response = cffi_requests.get(
|
||||
test_url,
|
||||
proxies=proxies_dict,
|
||||
timeout=3,
|
||||
impersonate="chrome110"
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
if response.status_code == 200:
|
||||
ip_info = response.json()
|
||||
results.append({
|
||||
"id": proxy.id,
|
||||
"name": proxy.name,
|
||||
"success": True,
|
||||
"ip": ip_info.get("ip", ""),
|
||||
"response_time": round(elapsed_time * 1000)
|
||||
})
|
||||
else:
|
||||
results.append({
|
||||
"id": proxy.id,
|
||||
"name": proxy.name,
|
||||
"success": False,
|
||||
"message": f"状态码: {response.status_code}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"id": proxy.id,
|
||||
"name": proxy.name,
|
||||
"success": False,
|
||||
"message": str(e)
|
||||
})
|
||||
|
||||
success_count = sum(1 for r in results if r["success"])
|
||||
return {
|
||||
"total": len(proxies),
|
||||
"success": success_count,
|
||||
"failed": len(proxies) - success_count,
|
||||
"results": results
|
||||
}
|
||||
|
||||
|
||||
@router.post("/proxies/{proxy_id}/enable")
|
||||
async def enable_proxy(proxy_id: int):
|
||||
"""启用代理"""
|
||||
with get_db() as db:
|
||||
proxy = crud.update_proxy(db, proxy_id, enabled=True)
|
||||
if not proxy:
|
||||
raise HTTPException(status_code=404, detail="代理不存在")
|
||||
return {"success": True, "message": "代理已启用"}
|
||||
|
||||
|
||||
@router.post("/proxies/{proxy_id}/disable")
|
||||
async def disable_proxy(proxy_id: int):
|
||||
"""禁用代理"""
|
||||
with get_db() as db:
|
||||
proxy = crud.update_proxy(db, proxy_id, enabled=False)
|
||||
if not proxy:
|
||||
raise HTTPException(status_code=404, detail="代理不存在")
|
||||
return {"success": True, "message": "代理已禁用"}
|
||||
|
||||
Reference in New Issue
Block a user