1403 lines
53 KiB
Python
1403 lines
53 KiB
Python
# -*- coding: UTF-8 -*-
|
||
'''
|
||
@Project :domainScanDemo
|
||
@File :system_settings.py
|
||
@IDE :PyCharm
|
||
@Author :梦伴
|
||
@Date :2026/4/10 15:00
|
||
@explain : 系统设置页面
|
||
'''
|
||
|
||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QGroupBox, QCheckBox, QListWidget, QListWidgetItem, QTextEdit, QApplication
|
||
from PySide6.QtCore import QTimer, QThreadPool, QRunnable, Slot
|
||
from PySide6.QtCore import Qt, QThread, Signal
|
||
from loguru import logger
|
||
import os
|
||
import json
|
||
import redis
|
||
import base64
|
||
|
||
from detect.juming import JM
|
||
from detect.juziseo import Juziseo
|
||
from detect.jucha import JC
|
||
from app.config import config
|
||
|
||
|
||
class RedisTask(QRunnable):
|
||
"""
|
||
Redis操作后台任务
|
||
"""
|
||
def __init__(self, func, *args, **kwargs):
|
||
"""
|
||
初始化Redis任务
|
||
|
||
:param func: 要执行的函数
|
||
:param args: 函数参数
|
||
:param kwargs: 函数关键字参数
|
||
"""
|
||
super().__init__()
|
||
self._loading_settings = True
|
||
self.func = func
|
||
self.args = args
|
||
self.kwargs = kwargs
|
||
|
||
@Slot()
|
||
def run(self):
|
||
"""
|
||
执行Redis操作
|
||
"""
|
||
try:
|
||
self.func(*self.args, **self.kwargs)
|
||
except Exception as e:
|
||
logger.error(f"Redis操作失败: {e}")
|
||
|
||
|
||
class LoginThread(QThread):
|
||
"""
|
||
登录线程
|
||
"""
|
||
finished = Signal(bool, str)
|
||
|
||
def __init__(self, email, password, platform):
|
||
"""
|
||
初始化登录线程
|
||
|
||
:param email: 账号
|
||
:param password: 密码
|
||
:param platform: 平台 (juming: 聚名, juziseo: 桔子SEO)
|
||
"""
|
||
super().__init__()
|
||
self.email = email
|
||
self.password = password
|
||
self.platform = platform
|
||
|
||
def run(self):
|
||
"""
|
||
运行登录线程
|
||
"""
|
||
try:
|
||
if self.platform == "juming":
|
||
# 初始化聚名客户端
|
||
jm = JM()
|
||
|
||
# 加载 Cookie
|
||
jm.load_cookies()
|
||
logger.info("已加载聚名 Cookie")
|
||
|
||
# 登录
|
||
logger.info(f"开始登录聚名网: {self.email}")
|
||
login_result = jm.user_zh_p_login(self.email, self.password)
|
||
if not login_result[0]:
|
||
self.finished.emit(False, f"登录失败: {login_result[1]}")
|
||
return
|
||
logger.info("登录成功")
|
||
|
||
# 保存cookies
|
||
jm.save_cookies()
|
||
logger.info("已保存聚名 Cookie")
|
||
|
||
self.finished.emit(True, "登录成功")
|
||
elif self.platform == "juziseo":
|
||
# 初始化桔子SEO客户端
|
||
juziseo = Juziseo()
|
||
|
||
# 加载 Cookie
|
||
juziseo.load_cookies()
|
||
logger.info("已加载桔子SEO Cookie")
|
||
|
||
# 登录
|
||
logger.info(f"开始登录桔子SEO: {self.email}")
|
||
login_result = juziseo.login(self.email, self.password)
|
||
if not login_result[0]:
|
||
self.finished.emit(False, f"登录失败: {login_result[1]}")
|
||
return
|
||
logger.info("登录成功")
|
||
|
||
# 保存cookies
|
||
juziseo.save_cookies()
|
||
logger.info("已保存桔子SEO Cookie")
|
||
|
||
self.finished.emit(True, "登录成功")
|
||
elif self.platform == "jucha":
|
||
# 初始化聚查客户端
|
||
jc = JC()
|
||
|
||
# 加载聚名 Cookie
|
||
jc.load_juming_cookies()
|
||
logger.info("已加载聚名 Cookie")
|
||
|
||
# 登录
|
||
logger.info("开始登录聚查网")
|
||
login_result = jc.auth_login()
|
||
if not login_result[0]:
|
||
self.finished.emit(False, f"登录失败: {login_result[1]}")
|
||
return
|
||
logger.info("登录成功")
|
||
|
||
# 保存cookies
|
||
jc.save_cookies()
|
||
logger.info("已保存聚查 Cookie")
|
||
|
||
self.finished.emit(True, "登录成功")
|
||
except Exception as e:
|
||
logger.error(f"登录失败: {e}")
|
||
self.finished.emit(False, f"登录失败: {str(e)}")
|
||
|
||
|
||
class SystemSettingsWidget(QWidget):
|
||
"""
|
||
系统设置页面
|
||
"""
|
||
|
||
def __init__(self):
|
||
"""
|
||
初始化系统设置页面
|
||
"""
|
||
super().__init__()
|
||
self._loading_settings = True
|
||
|
||
# 初始化Redis客户端
|
||
self.redis_client = None
|
||
self.use_redis = False
|
||
self.thread_pool = QThreadPool()
|
||
self.thread_pool.setMaxThreadCount(5) # 最大线程数
|
||
self.init_redis()
|
||
|
||
# 创建布局
|
||
layout = QVBoxLayout(self)
|
||
|
||
# 创建登录区域容器
|
||
login_container = QGroupBox("登录设置")
|
||
login_container.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
}
|
||
""")
|
||
login_layout = QVBoxLayout(login_container)
|
||
|
||
# 聚名登录区域
|
||
juming_subgroup = QGroupBox("聚名网登录")
|
||
juming_subgroup.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 13px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
margin-bottom: 15px;
|
||
padding: 10px;
|
||
}
|
||
""")
|
||
juming_layout = QVBoxLayout(juming_subgroup)
|
||
|
||
# 聚名输入区域(两列布局)
|
||
juming_input_layout = QHBoxLayout()
|
||
juming_input_layout.setSpacing(20)
|
||
|
||
# 邮箱输入
|
||
email_layout = QVBoxLayout()
|
||
email_label = QLabel("邮箱:")
|
||
email_label.setStyleSheet("font-size: 14px; color: #666; margin-bottom: 8px;")
|
||
self.juming_email_input = QLineEdit()
|
||
self.juming_email_input.setPlaceholderText("请输入聚名网账号")
|
||
self.juming_email_input.setStyleSheet("""
|
||
QLineEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
min-width: 250px;
|
||
min-height: 22px;
|
||
}
|
||
QLineEdit:focus {
|
||
border-color: #4CAF50;
|
||
outline: none;
|
||
}
|
||
""")
|
||
email_layout.addWidget(email_label)
|
||
email_layout.addWidget(self.juming_email_input)
|
||
juming_input_layout.addLayout(email_layout)
|
||
|
||
# 密码输入
|
||
password_layout = QVBoxLayout()
|
||
password_label = QLabel("密码:")
|
||
password_label.setStyleSheet("font-size: 14px; color: #666; margin-bottom: 8px;")
|
||
self.juming_password_input = QLineEdit()
|
||
self.juming_password_input.setPlaceholderText("请输入聚名网密码")
|
||
self.juming_password_input.setEchoMode(QLineEdit.Password)
|
||
self.juming_password_input.setStyleSheet("""
|
||
QLineEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
min-width: 250px;
|
||
min-height: 22px;
|
||
}
|
||
QLineEdit:focus {
|
||
border-color: #4CAF50;
|
||
outline: none;
|
||
}
|
||
""")
|
||
password_layout.addWidget(password_label)
|
||
password_layout.addWidget(self.juming_password_input)
|
||
juming_input_layout.addLayout(password_layout)
|
||
juming_layout.addLayout(juming_input_layout)
|
||
|
||
# 登录说明
|
||
juming_note = QLabel("聚名聚查联名登录:登录聚名后会自动登录聚查")
|
||
juming_note.setStyleSheet("font-size: 12px; color: #666; margin-top: 12px; margin-bottom: 12px;")
|
||
juming_layout.addWidget(juming_note)
|
||
|
||
# 登录按钮布局
|
||
juming_btn_layout = QHBoxLayout()
|
||
juming_btn_layout.addStretch() # 添加弹性空间,将按钮推到右侧
|
||
juming_login_btn = QPushButton("登录聚名网")
|
||
juming_login_btn.clicked.connect(self.login_juming)
|
||
juming_login_btn.setStyleSheet("""
|
||
QPushButton {
|
||
font-size: 14px;
|
||
padding: 8px 16px;
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #45a049;
|
||
}
|
||
QPushButton:disabled {
|
||
background-color: #cccccc;
|
||
}
|
||
""")
|
||
juming_btn_layout.addWidget(juming_login_btn)
|
||
juming_layout.addLayout(juming_btn_layout)
|
||
|
||
login_layout.addWidget(juming_subgroup)
|
||
|
||
# 桔子SEO登录区域
|
||
juziseo_subgroup = QGroupBox("桔子SEO登录")
|
||
juziseo_subgroup.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 13px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
margin-bottom: 15px;
|
||
padding: 10px;
|
||
}
|
||
""")
|
||
juziseo_layout = QVBoxLayout(juziseo_subgroup)
|
||
|
||
# 桔子SEO输入区域(两列布局)
|
||
juziseo_input_layout = QHBoxLayout()
|
||
juziseo_input_layout.setSpacing(20)
|
||
|
||
# 账号输入
|
||
juziseo_email_layout = QVBoxLayout()
|
||
juziseo_email_label = QLabel("账号:")
|
||
juziseo_email_label.setStyleSheet("font-size: 14px; color: #666; margin-bottom: 8px;")
|
||
self.juziseo_email_input = QLineEdit()
|
||
self.juziseo_email_input.setPlaceholderText("请输入桔子SEO账号")
|
||
self.juziseo_email_input.setStyleSheet("""
|
||
QLineEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
min-width: 250px;
|
||
min-height: 22px;
|
||
}
|
||
QLineEdit:focus {
|
||
border-color: #2196F3;
|
||
outline: none;
|
||
}
|
||
""")
|
||
juziseo_email_layout.addWidget(juziseo_email_label)
|
||
juziseo_email_layout.addWidget(self.juziseo_email_input)
|
||
juziseo_input_layout.addLayout(juziseo_email_layout)
|
||
|
||
# 密码输入
|
||
juziseo_password_layout = QVBoxLayout()
|
||
juziseo_password_label = QLabel("密码:")
|
||
juziseo_password_label.setStyleSheet("font-size: 14px; color: #666; margin-bottom: 8px;")
|
||
self.juziseo_password_input = QLineEdit()
|
||
self.juziseo_password_input.setPlaceholderText("请输入桔子SEO密码")
|
||
self.juziseo_password_input.setEchoMode(QLineEdit.Password)
|
||
self.juziseo_password_input.setStyleSheet("""
|
||
QLineEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
min-width: 250px;
|
||
min-height: 22px;
|
||
}
|
||
QLineEdit:focus {
|
||
border-color: #2196F3;
|
||
outline: none;
|
||
}
|
||
""")
|
||
juziseo_password_layout.addWidget(juziseo_password_label)
|
||
juziseo_password_layout.addWidget(self.juziseo_password_input)
|
||
juziseo_input_layout.addLayout(juziseo_password_layout)
|
||
juziseo_layout.addLayout(juziseo_input_layout)
|
||
|
||
# 登录按钮布局
|
||
juziseo_btn_layout = QHBoxLayout()
|
||
juziseo_btn_layout.addStretch() # 添加弹性空间,将按钮推到右侧
|
||
juziseo_login_btn = QPushButton("登录桔子SEO")
|
||
juziseo_login_btn.clicked.connect(self.login_juziseo)
|
||
juziseo_login_btn.setStyleSheet("""
|
||
QPushButton {
|
||
font-size: 14px;
|
||
padding: 8px 16px;
|
||
background-color: #2196F3;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #0b7dda;
|
||
}
|
||
QPushButton:disabled {
|
||
background-color: #cccccc;
|
||
}
|
||
""")
|
||
juziseo_btn_layout.addWidget(juziseo_login_btn)
|
||
juziseo_layout.addLayout(juziseo_btn_layout)
|
||
|
||
login_layout.addWidget(juziseo_subgroup)
|
||
|
||
layout.addWidget(login_container)
|
||
|
||
# 创建代理IP配置区域
|
||
proxy_group = QGroupBox("代理IP配置")
|
||
proxy_group.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
}
|
||
""")
|
||
proxy_layout = QVBoxLayout(proxy_group)
|
||
|
||
# 启用代理IP复选框
|
||
proxy_enable_layout = QHBoxLayout()
|
||
proxy_enable_label = QLabel("启用代理IP:")
|
||
proxy_enable_label.setStyleSheet("font-size: 14px; color: #333; min-width: 100px; font-weight: bold;")
|
||
self.proxy_enable_checkbox = QCheckBox()
|
||
self.proxy_enable_checkbox.setStyleSheet("""
|
||
QCheckBox {
|
||
font-size: 14px;
|
||
spacing: 8px;
|
||
}
|
||
QCheckBox::indicator {
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
QCheckBox::indicator:unchecked {
|
||
border: 2px solid #999;
|
||
background-color: white;
|
||
border-radius: 4px;
|
||
}
|
||
QCheckBox::indicator:checked {
|
||
border: 2px solid #9c27b0;
|
||
background-color: #9c27b0;
|
||
border-radius: 4px;
|
||
}
|
||
QCheckBox::indicator:checked::after {
|
||
content: '✓';
|
||
color: white;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
position: absolute;
|
||
top: 0px;
|
||
left: 3px;
|
||
}
|
||
""")
|
||
self.proxy_enable_checkbox.stateChanged.connect(self.save_proxy_config)
|
||
proxy_enable_layout.addWidget(proxy_enable_label)
|
||
proxy_enable_layout.addWidget(self.proxy_enable_checkbox)
|
||
proxy_enable_layout.setContentsMargins(0, 0, 0, 10)
|
||
proxy_layout.addLayout(proxy_enable_layout)
|
||
|
||
# 代理池链接输入
|
||
proxy_url_layout = QVBoxLayout()
|
||
proxy_url_label = QLabel("代理池链接:")
|
||
proxy_url_label.setStyleSheet("font-size: 14px; color: #666; min-width: 100px;")
|
||
self.proxy_url_input = QTextEdit()
|
||
self.proxy_url_input.setPlaceholderText("每行一个代理池链接,支持多个代理池")
|
||
self.proxy_url_input.setFixedHeight(100)
|
||
self.proxy_url_input.setStyleSheet("""
|
||
QTextEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background: #fff;
|
||
}
|
||
QTextEdit:focus {
|
||
border-color: #9c27b0;
|
||
outline: none;
|
||
}
|
||
""")
|
||
self.proxy_url_input.textChanged.connect(self.save_proxy_config)
|
||
proxy_url_layout.addWidget(proxy_url_label)
|
||
proxy_url_layout.addWidget(self.proxy_url_input)
|
||
proxy_url_layout.setContentsMargins(0, 0, 0, 10)
|
||
proxy_layout.addLayout(proxy_url_layout)
|
||
|
||
proxy_direct_layout = QHBoxLayout()
|
||
proxy_direct_label = QLabel("允许直连兜底:")
|
||
proxy_direct_label.setStyleSheet("font-size: 14px; color: #666; min-width: 100px;")
|
||
self.proxy_allow_direct_checkbox = QCheckBox()
|
||
self.proxy_allow_direct_checkbox.setStyleSheet(self.proxy_enable_checkbox.styleSheet())
|
||
self.proxy_allow_direct_checkbox.stateChanged.connect(self.save_proxy_config)
|
||
proxy_direct_layout.addWidget(proxy_direct_label)
|
||
proxy_direct_layout.addWidget(self.proxy_allow_direct_checkbox)
|
||
proxy_direct_layout.setContentsMargins(0, 0, 0, 10)
|
||
proxy_layout.addLayout(proxy_direct_layout)
|
||
|
||
proxy_note = QLabel("提示:默认不允许直连。启用代理后会优先汇总多个代理池,只有勾选“允许直连兜底”时才会在无代理可用时走本机出口。")
|
||
proxy_note.setStyleSheet("font-size: 12px; color: #999; margin-bottom: 10px;")
|
||
proxy_note.setWordWrap(True)
|
||
proxy_layout.addWidget(proxy_note)
|
||
|
||
proxy_test_layout = QHBoxLayout()
|
||
proxy_test_layout.addStretch()
|
||
self.proxy_test_button = QPushButton("测试代理池")
|
||
self.proxy_test_button.setStyleSheet("""
|
||
QPushButton {
|
||
font-size: 13px;
|
||
padding: 8px 16px;
|
||
background-color: #9c27b0;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #7b1fa2;
|
||
}
|
||
QPushButton:disabled {
|
||
background-color: #cccccc;
|
||
}
|
||
""")
|
||
self.proxy_test_button.clicked.connect(self.test_proxy_pools)
|
||
proxy_test_layout.addWidget(self.proxy_test_button)
|
||
proxy_layout.addLayout(proxy_test_layout)
|
||
|
||
self.proxy_test_result = QTextEdit()
|
||
self.proxy_test_result.setReadOnly(True)
|
||
self.proxy_test_result.setFixedHeight(150)
|
||
self.proxy_test_result.setPlaceholderText("代理池测试结果会显示在这里")
|
||
self.proxy_test_result.setStyleSheet("""
|
||
QTextEdit {
|
||
font-size: 12px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background: #fafafa;
|
||
color: #333;
|
||
}
|
||
""")
|
||
proxy_layout.addWidget(self.proxy_test_result)
|
||
|
||
layout.addWidget(proxy_group)
|
||
|
||
# 创建域名后缀配置区域
|
||
domain_suffix_group = QGroupBox("域名后缀配置")
|
||
domain_suffix_group.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
}
|
||
""")
|
||
domain_suffix_layout = QVBoxLayout(domain_suffix_group)
|
||
|
||
# 域名后缀输入
|
||
suffix_layout = QHBoxLayout()
|
||
suffix_label = QLabel("保留后缀:")
|
||
suffix_label.setStyleSheet("font-size: 14px; color: #666; min-width: 80px;")
|
||
self.suffix_input = QLineEdit()
|
||
self.suffix_input.setPlaceholderText("请输入需要保留的域名后缀,多个用逗号分隔,如:.com,.net")
|
||
self.suffix_input.setStyleSheet("""
|
||
QLineEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
QLineEdit:focus {
|
||
border-color: #ff9800;
|
||
outline: none;
|
||
}
|
||
""")
|
||
# 输入时自动保存
|
||
self.suffix_input.textChanged.connect(self.save_suffixes)
|
||
suffix_layout.addWidget(suffix_label)
|
||
suffix_layout.addWidget(self.suffix_input)
|
||
suffix_layout.setContentsMargins(0, 0, 0, 5)
|
||
domain_suffix_layout.addLayout(suffix_layout)
|
||
|
||
# 添加分隔符提示
|
||
suffix_note = QLabel("提示:多个域名后缀请用英文逗号分割")
|
||
suffix_note.setStyleSheet("font-size: 12px; color: #999; margin-bottom: 10px;")
|
||
domain_suffix_layout.addWidget(suffix_note)
|
||
|
||
layout.addWidget(domain_suffix_group)
|
||
|
||
# 创建检测选项配置区域
|
||
detect_group = QGroupBox("检测选项配置")
|
||
detect_group.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
}
|
||
""")
|
||
detect_layout = QVBoxLayout(detect_group)
|
||
|
||
self.detect_option_defs = [
|
||
("detect_register", "检查注册"),
|
||
("detect_baidu_site", "百度site查询"),
|
||
("detect_360_site", "360的site查询"),
|
||
("detect_chinaz", "站长之家查询"),
|
||
("detect_aizhan", "爱站网查询"),
|
||
("detect_wayback", "时光机检测"),
|
||
("detect_jucha", "聚查查询"),
|
||
("detect_juziseo", "桔子查询"),
|
||
]
|
||
self.detect_option_labels = {key: label for key, label in self.detect_option_defs}
|
||
|
||
detect_list_container = QHBoxLayout()
|
||
detect_list_container.setSpacing(12)
|
||
detect_list_container.setContentsMargins(0, 0, 0, 0)
|
||
|
||
self.detect_option_list = QListWidget()
|
||
self.detect_option_list.setStyleSheet("""
|
||
QListWidget {
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background: #fff;
|
||
font-size: 14px;
|
||
padding: 6px;
|
||
}
|
||
QListWidget::item {
|
||
padding: 6px 8px;
|
||
}
|
||
QListWidget::item:selected {
|
||
background: #e8f4ff;
|
||
color: #333;
|
||
}
|
||
""")
|
||
self.detect_option_list.itemChanged.connect(self.save_detect_options)
|
||
|
||
detect_button_column = QVBoxLayout()
|
||
detect_button_column.setSpacing(8)
|
||
self.detect_move_up_btn = QPushButton("上移")
|
||
self.detect_move_down_btn = QPushButton("下移")
|
||
self.detect_move_up_btn.clicked.connect(self.move_detect_option_up)
|
||
self.detect_move_down_btn.clicked.connect(self.move_detect_option_down)
|
||
detect_button_column.addWidget(self.detect_move_up_btn)
|
||
detect_button_column.addWidget(self.detect_move_down_btn)
|
||
detect_button_column.addStretch()
|
||
|
||
detect_list_container.addWidget(self.detect_option_list, 1)
|
||
detect_list_container.addLayout(detect_button_column)
|
||
detect_layout.addLayout(detect_list_container)
|
||
|
||
detect_note = QLabel("提示:勾选控制是否执行;顺序从上到下代表默认执行优先级,运营可自行调整。")
|
||
detect_note.setStyleSheet("font-size: 12px; color: #999; margin-top: 8px;")
|
||
detect_layout.addWidget(detect_note)
|
||
|
||
# 创建检测配置容器
|
||
detect_config_container = QHBoxLayout()
|
||
detect_config_container.setSpacing(20)
|
||
detect_config_container.setContentsMargins(0, 0, 0, 20)
|
||
|
||
# 添加检测选项配置
|
||
detect_config_container.addWidget(detect_group, 2) # 占2份空间
|
||
|
||
# 创建检测线程配置区域
|
||
thread_group = QGroupBox("检测线程配置")
|
||
thread_group.setStyleSheet("""
|
||
QGroupBox {
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
}
|
||
""")
|
||
thread_layout = QVBoxLayout(thread_group)
|
||
|
||
# 检测线程数输入
|
||
thread_count_layout = QHBoxLayout()
|
||
thread_count_label = QLabel("线程数量:")
|
||
thread_count_label.setStyleSheet("font-size: 14px; color: #666; min-width: 80px;")
|
||
self.thread_count_input = QLineEdit()
|
||
self.thread_count_input.setPlaceholderText("请输入检测线程数量,默认为10")
|
||
self.thread_count_input.setStyleSheet("""
|
||
QLineEdit {
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
QLineEdit:focus {
|
||
border-color: #2196F3;
|
||
outline: none;
|
||
}
|
||
""")
|
||
# 输入时自动保存
|
||
self.thread_count_input.textChanged.connect(self.save_thread_count)
|
||
thread_count_layout.addWidget(thread_count_label)
|
||
thread_count_layout.addWidget(self.thread_count_input)
|
||
thread_count_layout.setContentsMargins(0, 0, 0, 5)
|
||
thread_layout.addLayout(thread_count_layout)
|
||
|
||
# 添加线程数提示
|
||
thread_note = QLabel("提示:线程数量不宜设置过大,建议根据服务器性能调整")
|
||
thread_note.setStyleSheet("font-size: 12px; color: #999; margin-bottom: 10px;")
|
||
thread_layout.addWidget(thread_note)
|
||
|
||
# 添加检测线程配置到容器
|
||
detect_config_container.addWidget(thread_group, 1) # 占1份空间
|
||
|
||
# 将容器添加到主布局
|
||
layout.addLayout(detect_config_container)
|
||
|
||
# 创建状态显示区域,支持复制
|
||
status_container = QVBoxLayout()
|
||
status_container.setSpacing(6)
|
||
|
||
status_title_layout = QHBoxLayout()
|
||
status_title = QLabel("状态信息")
|
||
status_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #333;")
|
||
status_title_layout.addWidget(status_title)
|
||
status_title_layout.addStretch()
|
||
|
||
self.copy_status_button = QPushButton("复制状态")
|
||
self.copy_status_button.setStyleSheet("""
|
||
QPushButton {
|
||
font-size: 12px;
|
||
padding: 6px 12px;
|
||
background-color: #607d8b;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #546e7a;
|
||
}
|
||
""")
|
||
self.copy_status_button.clicked.connect(self.copy_status_message)
|
||
status_title_layout.addWidget(self.copy_status_button)
|
||
status_container.addLayout(status_title_layout)
|
||
|
||
self.status_label = QTextEdit()
|
||
self.status_label.setReadOnly(True)
|
||
self.status_label.setFixedHeight(88)
|
||
self.status_label.setPlaceholderText("登录、代理测试、保存配置等结果会显示在这里,可直接选择复制")
|
||
self.status_label.setStyleSheet("""
|
||
QTextEdit {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-top: 4px;
|
||
padding: 10px;
|
||
background-color: #f0f8ff;
|
||
border-radius: 4px;
|
||
border: 1px solid #d8e6f3;
|
||
}
|
||
""")
|
||
status_container.addWidget(self.status_label)
|
||
layout.addLayout(status_container)
|
||
|
||
# 加载保存的账号密码
|
||
self.load_credentials()
|
||
|
||
# 加载域名后缀
|
||
self.load_suffixes()
|
||
|
||
# 加载检测线程数
|
||
self.load_thread_count()
|
||
|
||
# 加载cookie
|
||
self.load_cookies()
|
||
|
||
# 加载检测选项
|
||
self.load_detect_options()
|
||
|
||
# 加载代理IP配置
|
||
self.load_proxy_config()
|
||
self._loading_settings = False
|
||
|
||
logger.info("系统设置页面创建完成")
|
||
|
||
def _encrypt_secret(self, value):
|
||
if not value:
|
||
return ''
|
||
try:
|
||
import win32crypt
|
||
encrypted = win32crypt.CryptProtectData(value.encode('utf-8'), None, None, None, None, 0)
|
||
return "dpapi:" + base64.b64encode(encrypted).decode('ascii')
|
||
except Exception:
|
||
return value
|
||
|
||
def _decrypt_secret(self, value):
|
||
if not value:
|
||
return ''
|
||
if not isinstance(value, str) or not value.startswith("dpapi:"):
|
||
return value
|
||
try:
|
||
import win32crypt
|
||
raw = base64.b64decode(value[6:])
|
||
return win32crypt.CryptUnprotectData(raw, None, None, None, 0)[1].decode('utf-8')
|
||
except Exception:
|
||
return ''
|
||
|
||
def init_redis(self):
|
||
"""
|
||
初始化Redis客户端连接
|
||
"""
|
||
try:
|
||
# 使用连接池
|
||
pool = redis.ConnectionPool(
|
||
host=config.REDIS_HOST,
|
||
port=config.REDIS_PORT,
|
||
password=config.REDIS_PASSWORD,
|
||
db=config.REDIS_DB,
|
||
decode_responses=True,
|
||
socket_connect_timeout=3, # 减少连接超时时间
|
||
socket_timeout=3, # 减少操作超时时间
|
||
max_connections=10 # 连接池大小
|
||
)
|
||
self.redis_client = redis.Redis(connection_pool=pool)
|
||
# 测试连接
|
||
self.redis_client.ping()
|
||
logger.info(f"Redis 连接成功: {config.REDIS_HOST}:{config.REDIS_PORT}")
|
||
self.use_redis = True
|
||
except Exception as e:
|
||
logger.warning(f"Redis 连接失败: {e},将只保存到本地文件")
|
||
self.redis_client = None
|
||
self.use_redis = False
|
||
|
||
def check_redis_connection(self):
|
||
"""
|
||
检查Redis连接状态,如果连接已断开则重新连接
|
||
"""
|
||
if not self.use_redis or self.redis_client is None:
|
||
return False
|
||
|
||
try:
|
||
# 测试连接
|
||
self.redis_client.ping()
|
||
return True
|
||
except Exception as e:
|
||
logger.warning(f"Redis 连接已断开,尝试重新连接: {e}")
|
||
# 重新连接
|
||
self.init_redis()
|
||
return self.use_redis
|
||
|
||
def save_credentials(self):
|
||
"""
|
||
保存账号密码到本地文件和Redis
|
||
"""
|
||
credentials = {
|
||
"juming": {
|
||
"email": self.juming_email_input.text().strip(),
|
||
"password": self._encrypt_secret(self.juming_password_input.text().strip())
|
||
},
|
||
"juziseo": {
|
||
"email": self.juziseo_email_input.text().strip(),
|
||
"password": self._encrypt_secret(self.juziseo_password_input.text().strip())
|
||
}
|
||
}
|
||
|
||
try:
|
||
# 保存到本地文件(UI线程执行)
|
||
with open('credentials.json', 'w', encoding='utf-8') as f:
|
||
json.dump(credentials, f, indent=2, ensure_ascii=False)
|
||
logger.info("账号密码保存成功")
|
||
|
||
# 保存到Redis(后台线程执行)
|
||
def save_to_redis():
|
||
if self.check_redis_connection():
|
||
self.redis_client.set('domain_tool:credentials', json.dumps(credentials))
|
||
logger.info("账号密码已同步到Redis")
|
||
# 发布配置更新消息
|
||
self.redis_client.publish('domain_tool:config_update', 'credentials')
|
||
|
||
# 提交到线程池执行
|
||
task = RedisTask(save_to_redis)
|
||
self.thread_pool.start(task)
|
||
except Exception as e:
|
||
logger.error(f"保存账号密码失败: {e}")
|
||
|
||
def save_suffixes(self):
|
||
"""
|
||
保存域名后缀到本地文件和Redis
|
||
"""
|
||
if self._loading_settings:
|
||
return
|
||
suffixes = self.suffix_input.text().strip()
|
||
|
||
try:
|
||
# 保存到本地文件(UI线程执行)
|
||
with open('domain_suffixes.json', 'w', encoding='utf-8') as f:
|
||
json.dump({"suffixes": suffixes}, f, indent=2, ensure_ascii=False)
|
||
logger.info("域名后缀保存成功")
|
||
|
||
# 保存到Redis(后台线程执行)
|
||
def save_to_redis():
|
||
if self.check_redis_connection():
|
||
self.redis_client.set('domain_tool:domain_suffixes', suffixes)
|
||
logger.info("域名后缀已同步到Redis")
|
||
# 发布配置更新消息
|
||
self.redis_client.publish('domain_tool:config_update', 'domain_suffixes')
|
||
|
||
# 提交到线程池执行
|
||
task = RedisTask(save_to_redis)
|
||
self.thread_pool.start(task)
|
||
except Exception as e:
|
||
logger.error(f"保存域名后缀失败: {e}")
|
||
|
||
def load_suffixes(self):
|
||
"""
|
||
从本地文件加载域名后缀
|
||
"""
|
||
try:
|
||
if os.path.exists('domain_suffixes.json'):
|
||
with open('domain_suffixes.json', 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
suffixes = data.get('suffixes', '.com,.net')
|
||
self.suffix_input.setText(suffixes)
|
||
logger.info("域名后缀加载成功")
|
||
else:
|
||
# 默认值
|
||
self.suffix_input.setText('.com,.net')
|
||
self.save_suffixes()
|
||
logger.info("使用默认域名后缀")
|
||
except Exception as e:
|
||
logger.error(f"加载域名后缀失败: {e}")
|
||
# 使用默认值
|
||
self.suffix_input.setText('.com,.net')
|
||
|
||
def load_credentials(self):
|
||
"""
|
||
从本地文件加载账号密码
|
||
"""
|
||
try:
|
||
if os.path.exists('credentials.json'):
|
||
with open('credentials.json', 'r', encoding='utf-8') as f:
|
||
credentials = json.load(f)
|
||
|
||
# 加载聚名账号密码
|
||
if 'juming' in credentials:
|
||
self.juming_email_input.setText(credentials['juming'].get('email', ''))
|
||
self.juming_password_input.setText(self._decrypt_secret(credentials['juming'].get('password', '')))
|
||
|
||
# 加载桔子SEO账号密码
|
||
if 'juziseo' in credentials:
|
||
self.juziseo_email_input.setText(credentials['juziseo'].get('email', ''))
|
||
self.juziseo_password_input.setText(self._decrypt_secret(credentials['juziseo'].get('password', '')))
|
||
|
||
logger.info("账号密码加载成功")
|
||
except Exception as e:
|
||
logger.error(f"加载账号密码失败: {e}")
|
||
|
||
def load_cookies(self):
|
||
"""
|
||
加载cookie
|
||
"""
|
||
try:
|
||
# 加载聚名cookie
|
||
jm = JM()
|
||
jm.load_cookies()
|
||
logger.info("聚名cookie加载成功")
|
||
|
||
# 加载桔子SEO cookie
|
||
juziseo = Juziseo()
|
||
juziseo.load_cookies()
|
||
logger.info("桔子SEO cookie加载成功")
|
||
|
||
# 加载聚查cookie
|
||
jc = JC()
|
||
jc.load_cookies()
|
||
logger.info("聚查cookie加载成功")
|
||
except Exception as e:
|
||
logger.error(f"加载cookie失败: {e}")
|
||
|
||
def login_juming(self):
|
||
"""
|
||
登录聚名网
|
||
"""
|
||
email = self.juming_email_input.text().strip()
|
||
password = self.juming_password_input.text().strip()
|
||
|
||
if not email or not password:
|
||
self.status_label.setPlainText("请输入账号和密码")
|
||
return
|
||
|
||
self.status_label.setPlainText("正在登录聚名网...")
|
||
|
||
# 创建并启动登录线程
|
||
self.login_thread = LoginThread(email, password, "juming")
|
||
self.login_thread.finished.connect(self.login_finished)
|
||
self.login_thread.start()
|
||
|
||
logger.info(f"开始登录聚名网: {email}")
|
||
|
||
def login_juziseo(self):
|
||
"""
|
||
登录桔子SEO
|
||
"""
|
||
email = self.juziseo_email_input.text().strip()
|
||
password = self.juziseo_password_input.text().strip()
|
||
|
||
if not email or not password:
|
||
self.status_label.setPlainText("请输入账号和密码")
|
||
return
|
||
|
||
self.status_label.setPlainText("正在登录桔子SEO...")
|
||
|
||
# 创建并启动登录线程
|
||
self.login_thread = LoginThread(email, password, "juziseo")
|
||
self.login_thread.finished.connect(self.login_finished)
|
||
self.login_thread.start()
|
||
|
||
logger.info(f"开始登录桔子SEO: {email}")
|
||
|
||
def login_jucha(self):
|
||
"""
|
||
登录聚查网
|
||
"""
|
||
# 检查聚名Cookie是否存在
|
||
if not os.path.exists('juming_cookies.pkl'):
|
||
self.status_label.setPlainText("请先登录聚名网")
|
||
return
|
||
|
||
self.status_label.setPlainText("正在登录聚查网...")
|
||
|
||
# 创建并启动登录线程
|
||
self.login_thread = LoginThread("", "", "jucha")
|
||
self.login_thread.finished.connect(self.login_finished)
|
||
self.login_thread.start()
|
||
|
||
logger.info("开始登录聚查网")
|
||
|
||
def populate_detect_option_list(self, order, options):
|
||
self.detect_option_list.blockSignals(True)
|
||
self.detect_option_list.clear()
|
||
for index, key in enumerate(order, start=1):
|
||
label = self.detect_option_labels.get(key, key)
|
||
item = QListWidgetItem(f"{index}. {label}")
|
||
item.setData(Qt.UserRole, key)
|
||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||
item.setCheckState(Qt.Checked if options.get(key, False) else Qt.Unchecked)
|
||
self.detect_option_list.addItem(item)
|
||
if self.detect_option_list.count() > 0:
|
||
self.detect_option_list.setCurrentRow(0)
|
||
self.detect_option_list.blockSignals(False)
|
||
|
||
def refresh_detect_option_labels(self):
|
||
for index in range(self.detect_option_list.count()):
|
||
item = self.detect_option_list.item(index)
|
||
key = item.data(Qt.UserRole)
|
||
item.setText(f"{index + 1}. {self.detect_option_labels.get(key, key)}")
|
||
|
||
def get_detect_option_payload(self):
|
||
payload = {key: False for key, _ in self.detect_option_defs}
|
||
order = []
|
||
for index in range(self.detect_option_list.count()):
|
||
item = self.detect_option_list.item(index)
|
||
key = item.data(Qt.UserRole)
|
||
payload[key] = item.checkState() == Qt.Checked
|
||
order.append(key)
|
||
payload['detect_order'] = order
|
||
return payload
|
||
|
||
def move_detect_option_up(self):
|
||
current_row = self.detect_option_list.currentRow()
|
||
if current_row <= 0:
|
||
return
|
||
item = self.detect_option_list.takeItem(current_row)
|
||
self.detect_option_list.insertItem(current_row - 1, item)
|
||
self.detect_option_list.setCurrentRow(current_row - 1)
|
||
self.refresh_detect_option_labels()
|
||
self.save_detect_options()
|
||
|
||
def move_detect_option_down(self):
|
||
current_row = self.detect_option_list.currentRow()
|
||
if current_row < 0 or current_row >= self.detect_option_list.count() - 1:
|
||
return
|
||
item = self.detect_option_list.takeItem(current_row)
|
||
self.detect_option_list.insertItem(current_row + 1, item)
|
||
self.detect_option_list.setCurrentRow(current_row + 1)
|
||
self.refresh_detect_option_labels()
|
||
self.save_detect_options()
|
||
|
||
def save_detect_options(self):
|
||
"""
|
||
保存检测选项到本地文件和Redis
|
||
"""
|
||
if self._loading_settings:
|
||
return
|
||
detect_options = self.get_detect_option_payload()
|
||
|
||
try:
|
||
# 保存到本地文件(UI线程执行)
|
||
with open('detect_options.json', 'w', encoding='utf-8') as f:
|
||
json.dump(detect_options, f, indent=2, ensure_ascii=False)
|
||
logger.info("检测选项保存成功")
|
||
|
||
# 保存到Redis(后台线程执行)
|
||
def save_to_redis():
|
||
if self.check_redis_connection():
|
||
self.redis_client.set('domain_tool:detect_options', json.dumps(detect_options))
|
||
logger.info("检测选项已同步到Redis")
|
||
# 发布配置更新消息
|
||
self.redis_client.publish('domain_tool:config_update', 'detect_options')
|
||
|
||
# 提交到线程池执行
|
||
task = RedisTask(save_to_redis)
|
||
self.thread_pool.start(task)
|
||
except Exception as e:
|
||
logger.error(f"保存检测选项失败: {e}")
|
||
|
||
def load_detect_options(self):
|
||
"""
|
||
从本地文件加载检测选项
|
||
"""
|
||
try:
|
||
default_order = [key for key, _ in self.detect_option_defs]
|
||
defaults = {
|
||
"detect_register": True,
|
||
"detect_wayback": True,
|
||
"detect_chinaz": True,
|
||
"detect_aizhan": True,
|
||
"detect_baidu_site": True,
|
||
"detect_360_site": True,
|
||
"detect_jucha": False,
|
||
"detect_juziseo": False,
|
||
"detect_order": default_order,
|
||
}
|
||
if os.path.exists('detect_options.json'):
|
||
with open('detect_options.json', 'r', encoding='utf-8') as f:
|
||
defaults.update(json.load(f))
|
||
if defaults.get('detect_whois') or defaults.get('detect_beian') or defaults.get('detect_intercept'):
|
||
defaults['detect_jucha'] = True
|
||
if defaults.get('detect_juziseo_outlink'):
|
||
defaults['detect_juziseo'] = True
|
||
order = defaults.get('detect_order') or []
|
||
normalized_order = [key for key in order if key in self.detect_option_labels]
|
||
for key in default_order:
|
||
if key not in normalized_order:
|
||
normalized_order.append(key)
|
||
self.populate_detect_option_list(normalized_order, defaults)
|
||
|
||
logger.info("检测选项加载成功")
|
||
except Exception as e:
|
||
logger.error(f"加载检测选项失败: {e}")
|
||
fallback = {
|
||
"detect_register": True,
|
||
"detect_wayback": True,
|
||
"detect_chinaz": True,
|
||
"detect_aizhan": True,
|
||
"detect_baidu_site": True,
|
||
"detect_360_site": True,
|
||
"detect_jucha": False,
|
||
"detect_juziseo": False,
|
||
}
|
||
self.populate_detect_option_list([key for key, _ in self.detect_option_defs], fallback)
|
||
|
||
def save_proxy_config(self):
|
||
"""
|
||
保存代理IP配置到本地文件和Redis
|
||
"""
|
||
if self._loading_settings:
|
||
return
|
||
proxy_enable = self.proxy_enable_checkbox.isChecked()
|
||
proxy_urls = [line.strip() for line in self.proxy_url_input.toPlainText().splitlines() if line.strip()]
|
||
allow_direct = self.proxy_allow_direct_checkbox.isChecked()
|
||
payload = {
|
||
"proxy_enable": proxy_enable,
|
||
"proxy_urls": proxy_urls,
|
||
"proxy_url": proxy_urls[0] if proxy_urls else "",
|
||
"allow_direct": allow_direct,
|
||
}
|
||
|
||
try:
|
||
# 保存到本地文件(UI线程执行)
|
||
with open('proxy_config.json', 'w', encoding='utf-8') as f:
|
||
json.dump(payload, f, indent=2, ensure_ascii=False)
|
||
logger.info("代理IP配置保存成功")
|
||
|
||
# 保存到Redis(后台线程执行)
|
||
def save_to_redis():
|
||
if self.check_redis_connection():
|
||
self.redis_client.set('domain_tool:proxy_config', json.dumps(payload))
|
||
logger.info("代理IP配置已同步到Redis")
|
||
# 发布配置更新消息
|
||
self.redis_client.publish('domain_tool:config_update', 'proxy_config')
|
||
|
||
# 提交到线程池执行
|
||
task = RedisTask(save_to_redis)
|
||
self.thread_pool.start(task)
|
||
except Exception as e:
|
||
logger.error(f"保存代理IP配置失败: {e}")
|
||
|
||
def load_proxy_config(self):
|
||
"""
|
||
从本地文件加载代理IP配置
|
||
"""
|
||
try:
|
||
if os.path.exists('proxy_config.json'):
|
||
with open('proxy_config.json', 'r', encoding='utf-8') as f:
|
||
proxy_config = json.load(f)
|
||
proxy_enable = proxy_config.get('proxy_enable', False)
|
||
proxy_urls = proxy_config.get('proxy_urls') or []
|
||
if not proxy_urls and proxy_config.get('proxy_url'):
|
||
proxy_urls = [proxy_config.get('proxy_url', '')]
|
||
allow_direct = proxy_config.get('allow_direct', False)
|
||
self.proxy_enable_checkbox.setChecked(proxy_enable)
|
||
self.proxy_url_input.setPlainText("\n".join(proxy_urls))
|
||
self.proxy_allow_direct_checkbox.setChecked(allow_direct)
|
||
logger.info("代理IP配置加载成功")
|
||
else:
|
||
# 默认值:未启用,不允许直连
|
||
self.proxy_enable_checkbox.setChecked(False)
|
||
self.proxy_url_input.setPlainText('')
|
||
self.proxy_allow_direct_checkbox.setChecked(False)
|
||
self.save_proxy_config()
|
||
logger.info("使用默认代理IP配置")
|
||
except Exception as e:
|
||
logger.error(f"加载代理IP配置失败: {e}")
|
||
self.proxy_enable_checkbox.setChecked(False)
|
||
self.proxy_url_input.setPlainText('')
|
||
self.proxy_allow_direct_checkbox.setChecked(False)
|
||
|
||
def test_proxy_pools(self):
|
||
proxy_urls = [line.strip() for line in self.proxy_url_input.toPlainText().splitlines() if line.strip()]
|
||
if not proxy_urls:
|
||
self.status_label.setPlainText("请先填写至少一个代理池链接")
|
||
return
|
||
|
||
self.status_label.setPlainText("正在测试代理池...")
|
||
self.proxy_test_button.setEnabled(False)
|
||
self.proxy_test_result.setPlainText("正在逐个测试代理池,请稍候...")
|
||
|
||
def run_test():
|
||
import requests
|
||
import random
|
||
results = []
|
||
total_items = 0
|
||
success_count = 0
|
||
sampled_total = 0
|
||
sampled_success = 0
|
||
|
||
def build_proxy_url(proxy_item):
|
||
ip = proxy_item.get('ip')
|
||
port = proxy_item.get('port')
|
||
if not ip or not port:
|
||
return None
|
||
username = proxy_item.get('username', '')
|
||
password = proxy_item.get('password', '')
|
||
if username and password:
|
||
return f"http://{username}:{password}@{ip}:{port}"
|
||
return f"http://{ip}:{port}"
|
||
|
||
for url in proxy_urls:
|
||
try:
|
||
response = requests.get(url, timeout=10)
|
||
if response.status_code != 200:
|
||
results.append(f"[异常] {url}\n状态: HTTP {response.status_code}")
|
||
continue
|
||
data = response.json()
|
||
count = 0
|
||
if isinstance(data, dict):
|
||
if isinstance(data.get('list'), list):
|
||
count = len(data.get('list'))
|
||
proxy_items = data.get('list')
|
||
elif data.get('ip') and data.get('port'):
|
||
count = 1
|
||
proxy_items = [data]
|
||
else:
|
||
proxy_items = []
|
||
elif isinstance(data, list):
|
||
count = len(data)
|
||
proxy_items = data
|
||
else:
|
||
proxy_items = []
|
||
total_items += count
|
||
success_count += 1
|
||
|
||
sample_size = min(5, len(proxy_items))
|
||
sample_candidates = random.sample(proxy_items, sample_size) if sample_size else []
|
||
sample_ok = 0
|
||
for item in sample_candidates:
|
||
proxy_url = build_proxy_url(item)
|
||
if not proxy_url:
|
||
continue
|
||
sampled_total += 1
|
||
try:
|
||
test_response = requests.get(
|
||
'https://m.baidu.com',
|
||
proxies={'http': proxy_url, 'https': proxy_url},
|
||
timeout=3
|
||
)
|
||
if test_response.status_code == 200:
|
||
sample_ok += 1
|
||
sampled_success += 1
|
||
except Exception:
|
||
pass
|
||
|
||
results.append(
|
||
f"[成功] {url}\n返回代理数: {count}\n"
|
||
f"抽样测试: {sample_ok}/{sample_size} 可用"
|
||
)
|
||
except Exception as e:
|
||
results.append(f"[失败] {url}\n原因: {e}")
|
||
|
||
final_text = (
|
||
f"代理池测试完成:成功 {success_count}/{len(proxy_urls)} 个,"
|
||
f"累计返回 {total_items} 条代理,抽样可用 {sampled_success}/{sampled_total}"
|
||
)
|
||
detail_text = "\n\n".join(results) if results else "未返回任何测试结果"
|
||
|
||
def update_ui():
|
||
self.status_label.setPlainText(final_text)
|
||
self.proxy_test_result.setPlainText(detail_text)
|
||
self.proxy_test_button.setEnabled(True)
|
||
|
||
QTimer.singleShot(0, update_ui)
|
||
|
||
task = RedisTask(run_test)
|
||
self.thread_pool.start(task)
|
||
|
||
def copy_status_message(self):
|
||
text = self.status_label.toPlainText().strip()
|
||
if not text:
|
||
self.status_label.setPlainText("当前没有可复制的状态信息")
|
||
return
|
||
QApplication.clipboard().setText(text)
|
||
self.status_label.setPlainText(text + "\n\n[已复制到剪贴板]")
|
||
|
||
def toggle_password_visibility(self, password_input):
|
||
"""
|
||
切换密码输入框的可见性
|
||
|
||
:param password_input: 密码输入框
|
||
"""
|
||
if password_input.echoMode() == QLineEdit.Password:
|
||
password_input.setEchoMode(QLineEdit.Normal)
|
||
# 找到对应的按钮并修改图标
|
||
if password_input == self.juming_password_input:
|
||
self.juming_password_visibility_btn.setText("🙈")
|
||
elif password_input == self.juziseo_password_input:
|
||
self.juziseo_password_visibility_btn.setText("🙈")
|
||
else:
|
||
password_input.setEchoMode(QLineEdit.Password)
|
||
# 找到对应的按钮并修改图标
|
||
if password_input == self.juming_password_input:
|
||
self.juming_password_visibility_btn.setText("👁")
|
||
elif password_input == self.juziseo_password_input:
|
||
self.juziseo_password_visibility_btn.setText("👁")
|
||
|
||
def login_finished(self, success, message):
|
||
"""
|
||
登录完成
|
||
|
||
:param success: 是否成功
|
||
:param message: 消息
|
||
"""
|
||
self.status_label.setPlainText(message)
|
||
|
||
# 登录成功后保存账号密码
|
||
if success:
|
||
self.save_credentials()
|
||
# 聚名登录成功后自动登录聚查
|
||
if self.login_thread.platform == "juming":
|
||
self.status_label.setPlainText("聚名登录成功,正在自动登录聚查...")
|
||
# 延迟一秒后登录聚查,确保聚名cookie已保存
|
||
QTimer.singleShot(1000, self.login_jucha)
|
||
|
||
logger.info(f"登录完成: {message}")
|
||
|
||
def save_thread_count(self):
|
||
"""
|
||
保存检测线程数到本地文件和Redis
|
||
"""
|
||
if self._loading_settings:
|
||
return
|
||
raw_value = self.thread_count_input.text().strip()
|
||
try:
|
||
thread_count = str(min(20, max(1, int(raw_value or '10'))))
|
||
except Exception:
|
||
thread_count = '10'
|
||
if self.thread_count_input.text().strip() != thread_count:
|
||
self.thread_count_input.setText(thread_count)
|
||
return
|
||
|
||
try:
|
||
# 保存到本地文件(UI线程执行)
|
||
with open('thread_count.json', 'w', encoding='utf-8') as f:
|
||
json.dump({"thread_count": thread_count}, f, indent=2, ensure_ascii=False)
|
||
logger.info("检测线程数配置保存成功")
|
||
|
||
# 保存到Redis(后台线程执行)
|
||
def save_to_redis():
|
||
if self.check_redis_connection():
|
||
self.redis_client.set('domain_tool:thread_count', thread_count)
|
||
logger.info("检测线程数配置已同步到Redis")
|
||
# 发布配置更新消息
|
||
self.redis_client.publish('domain_tool:config_update', 'thread_count')
|
||
|
||
# 提交到线程池执行
|
||
task = RedisTask(save_to_redis)
|
||
self.thread_pool.start(task)
|
||
except Exception as e:
|
||
logger.error(f"保存检测线程数配置失败: {e}")
|
||
|
||
def load_thread_count(self):
|
||
"""
|
||
从本地文件加载检测线程数
|
||
"""
|
||
try:
|
||
if os.path.exists('thread_count.json'):
|
||
with open('thread_count.json', 'r', encoding='utf-8') as f:
|
||
thread_config = json.load(f)
|
||
thread_count = str(min(20, max(1, int(thread_config.get('thread_count', '10') or '10'))))
|
||
self.thread_count_input.setText(thread_count)
|
||
logger.info("检测线程数配置加载成功")
|
||
else:
|
||
self.thread_count_input.setText('10')
|
||
self.save_thread_count()
|
||
logger.info("使用默认检测线程数配置")
|
||
except Exception as e:
|
||
logger.error(f"加载检测线程数配置失败: {e}")
|
||
self.thread_count_input.setText('10')
|