重构代理池系统:简化架构并增强核心功能
后端变更: - 移除 tasks_manager.py 和 core/auth.py,简化架构 - 新增 core/scheduler.py 验证调度器,替代原有任务管理 - 大幅优化 api_server.py:统一错误处理、增强参数验证、支持调度器控制 - validator.py 增强 SOCKS4/SOCKS5 代理验证支持 - config.py 清理废弃配置(WebSocket、API Key、认证开关) - SQLite 数据库操作性能优化 前端变更: - 移除任务管理页面 (CrawlerTasks) 和 WebSocket 相关代码 - 路由简化为 4 个核心页面:总览、代理列表、插件管理、设置 - 提取前端工具函数(clipboard、confirm、format)和 API 类型定义 - 优化 CSS 架构:完善 variables、utilities、element-plus 样式 - Dashboard、Plugins、ProxyList、Settings 页面 UI/UX 优化 - App.vue 响应式侧边栏和页面过渡动画优化 其他: - 移除 PowerShell 启动脚本,简化 Windows 批处理脚本 - 新增 README_SOCKS.md SOCKS 代理支持文档 - .env.example 和 .gitignore 更新
This commit is contained in:
@@ -1,35 +1,93 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<PageHeader title="系统设置" icon="⚙️" />
|
||||
<PageHeader title="系统设置" :icon="Setting" />
|
||||
|
||||
<!-- 验证调度器控制 -->
|
||||
<el-card class="settings-card scheduler-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="header-icon"><Timer /></el-icon>
|
||||
验证调度器
|
||||
</span>
|
||||
<div class="scheduler-status">
|
||||
<span class="status-dot" :class="{ active: schedulerRunning }"></span>
|
||||
<span class="status-text">{{ schedulerRunning ? '运行中' : '已停止' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="scheduler-actions">
|
||||
<el-button
|
||||
type="success"
|
||||
@click="handleStartScheduler"
|
||||
:disabled="schedulerRunning"
|
||||
:loading="schedulerLoading"
|
||||
>
|
||||
<el-icon class="btn-icon"><VideoPlay /></el-icon>
|
||||
启动自动验证
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
type="danger"
|
||||
@click="handleStopScheduler"
|
||||
:disabled="!schedulerRunning"
|
||||
:loading="schedulerLoading"
|
||||
>
|
||||
<el-icon class="btn-icon"><VideoPause /></el-icon>
|
||||
停止自动验证
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleValidateNow"
|
||||
:loading="validating"
|
||||
>
|
||||
<el-icon class="btn-icon"><Refresh /></el-icon>
|
||||
立即验证全部
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="scheduler-info">
|
||||
<el-alert
|
||||
:title="schedulerInfo"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 基础配置 -->
|
||||
<el-card class="settings-card" shadow="hover" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">🎨 基础配置</span>
|
||||
<el-button type="primary" @click="handleSave" size="large" :loading="saving">
|
||||
<span class="btn-icon">💾</span>
|
||||
<span class="card-title">
|
||||
<el-icon class="header-icon"><Tools /></el-icon>
|
||||
基础配置
|
||||
</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSave"
|
||||
size="large"
|
||||
:loading="saving"
|
||||
>
|
||||
<el-icon class="btn-icon"><DocumentChecked /></el-icon>
|
||||
保存配置
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="settings" label-width="150px" class="settings-form">
|
||||
<el-form-item label="管理员API Key">
|
||||
<el-input
|
||||
v-model="settings.api_key"
|
||||
placeholder="请输入管理员API Key"
|
||||
type="password"
|
||||
show-password
|
||||
class="setting-input"
|
||||
/>
|
||||
<div class="setting-hint">用于执行管理操作的API Key</div>
|
||||
</el-form-item>
|
||||
<el-form
|
||||
:model="settings"
|
||||
label-width="180px"
|
||||
class="settings-form"
|
||||
:rules="formRules"
|
||||
ref="formRef"
|
||||
>
|
||||
<el-divider content-position="left">爬虫配置</el-divider>
|
||||
|
||||
<el-form-item label="数据库路径">
|
||||
<el-input v-model="settings.db_path" placeholder="数据库文件路径" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="爬取超时">
|
||||
<el-form-item label="爬取超时" prop="crawl_timeout">
|
||||
<el-input-number
|
||||
v-model="settings.crawl_timeout"
|
||||
:min="5"
|
||||
@@ -40,7 +98,18 @@
|
||||
<span class="setting-suffix">秒</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="验证超时">
|
||||
<el-form-item label="最大重试次数" prop="max_retries">
|
||||
<el-input-number
|
||||
v-model="settings.max_retries"
|
||||
:min="0"
|
||||
:max="10"
|
||||
class="setting-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">验证配置</el-divider>
|
||||
|
||||
<el-form-item label="验证超时" prop="validation_timeout">
|
||||
<el-input-number
|
||||
v-model="settings.validation_timeout"
|
||||
:min="3"
|
||||
@@ -51,16 +120,7 @@
|
||||
<span class="setting-suffix">秒</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最大重试次数">
|
||||
<el-input-number
|
||||
v-model="settings.max_retries"
|
||||
:min="0"
|
||||
:max="10"
|
||||
class="setting-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="默认并发数">
|
||||
<el-form-item label="验证并发数" prop="default_concurrency">
|
||||
<el-input-number
|
||||
v-model="settings.default_concurrency"
|
||||
:min="10"
|
||||
@@ -70,17 +130,39 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最低代理分数">
|
||||
<el-form-item label="自动验证间隔" prop="validate_interval_minutes">
|
||||
<el-input-number
|
||||
v-model="settings.min_proxy_score"
|
||||
:min="0"
|
||||
:max="10"
|
||||
:step="1"
|
||||
v-model="settings.validate_interval_minutes"
|
||||
:min="5"
|
||||
:max="1440"
|
||||
:step="5"
|
||||
class="setting-input"
|
||||
/>
|
||||
<span class="setting-suffix">分钟</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用自动验证" prop="auto_validate">
|
||||
<el-switch
|
||||
v-model="settings.auto_validate"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="代理过期时间">
|
||||
<el-divider content-position="left">代理评分配置</el-divider>
|
||||
|
||||
<el-form-item label="最低代理分数" prop="min_proxy_score">
|
||||
<el-input-number
|
||||
v-model="settings.min_proxy_score"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
class="setting-input"
|
||||
/>
|
||||
<span class="setting-hint">分数低于此值的代理将被隐藏</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="代理过期时间" prop="proxy_expiry_days">
|
||||
<el-input-number
|
||||
v-model="settings.proxy_expiry_days"
|
||||
:min="1"
|
||||
@@ -89,6 +171,7 @@
|
||||
class="setting-input"
|
||||
/>
|
||||
<span class="setting-suffix">天</span>
|
||||
<span class="setting-hint">超过此时间未验证的代理将被清理</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -96,73 +179,207 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Setting,
|
||||
DocumentChecked,
|
||||
Tools,
|
||||
Timer,
|
||||
VideoPlay,
|
||||
VideoPause,
|
||||
Refresh
|
||||
} from '@element-plus/icons-vue'
|
||||
import { settingsAPI, schedulerAPI } from '../api'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
|
||||
// ==================== 状态 ====================
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const schedulerRunning = ref(false)
|
||||
const schedulerLoading = ref(false)
|
||||
const validating = ref(false)
|
||||
|
||||
const settings = reactive({
|
||||
api_key: '',
|
||||
db_path: '',
|
||||
crawl_timeout: 30,
|
||||
validation_timeout: 10,
|
||||
max_retries: 3,
|
||||
default_concurrency: 50,
|
||||
min_proxy_score: 5,
|
||||
proxy_expiry_days: 7
|
||||
min_proxy_score: 0,
|
||||
proxy_expiry_days: 7,
|
||||
auto_validate: true,
|
||||
validate_interval_minutes: 30
|
||||
})
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
const schedulerInfo = computed(() => {
|
||||
if (schedulerRunning.value) {
|
||||
return `验证调度器正在运行,每 ${settings.validate_interval_minutes} 分钟自动验证一次所有代理`
|
||||
} else {
|
||||
return '验证调度器已停止,代理不会自动验证,建议定期手动验证或开启自动验证'
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 表单验证规则 ====================
|
||||
const formRules = {
|
||||
crawl_timeout: [{ type: 'number', min: 5, max: 120, message: '范围 5-120 秒', trigger: 'blur' }],
|
||||
validation_timeout: [{ type: 'number', min: 3, max: 60, message: '范围 3-60 秒', trigger: 'blur' }],
|
||||
max_retries: [{ type: 'number', min: 0, max: 10, message: '范围 0-10', trigger: 'blur' }],
|
||||
default_concurrency: [{ type: 'number', min: 10, max: 200, message: '范围 10-200', trigger: 'blur' }],
|
||||
validate_interval_minutes: [{ type: 'number', min: 5, max: 1440, message: '范围 5-1440 分钟', trigger: 'blur' }],
|
||||
min_proxy_score: [{ type: 'number', min: 0, max: 100, message: '范围 0-100', trigger: 'blur' }],
|
||||
proxy_expiry_days: [{ type: 'number', min: 1, max: 30, message: '范围 1-30 天', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// ==================== 数据获取 ====================
|
||||
async function fetchSettings() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch('http://localhost:8923/api/settings')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
Object.assign(settings, data)
|
||||
const response = await settingsAPI.getSettings()
|
||||
if (response.code === 200) {
|
||||
Object.assign(settings, response.data)
|
||||
}
|
||||
settings.api_key = localStorage.getItem('api_key') || ''
|
||||
} catch (error) {
|
||||
console.error('获取设置失败:', error)
|
||||
ElMessage.error('获取设置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSchedulerStatus() {
|
||||
try {
|
||||
const response = await schedulerAPI.getStatus()
|
||||
if (response.code === 200) {
|
||||
schedulerRunning.value = response.data.running
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取调度器状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 调度器控制 ====================
|
||||
async function handleStartScheduler() {
|
||||
schedulerLoading.value = true
|
||||
try {
|
||||
const response = await schedulerAPI.start()
|
||||
if (response.code === 200) {
|
||||
schedulerRunning.value = true
|
||||
ElMessage.success('自动验证已启动')
|
||||
} else {
|
||||
ElMessage.error('启动失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('启动调度器失败:', error)
|
||||
ElMessage.error('启动失败')
|
||||
} finally {
|
||||
schedulerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStopScheduler() {
|
||||
schedulerLoading.value = true
|
||||
try {
|
||||
const response = await schedulerAPI.stop()
|
||||
if (response.code === 200) {
|
||||
schedulerRunning.value = false
|
||||
ElMessage.success('自动验证已停止')
|
||||
} else {
|
||||
ElMessage.error('停止失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('停止调度器失败:', error)
|
||||
ElMessage.error('停止失败')
|
||||
} finally {
|
||||
schedulerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleValidateNow() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要立即验证所有代理吗?这可能需要一些时间。',
|
||||
'确认验证',
|
||||
{
|
||||
confirmButtonText: '开始验证',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}
|
||||
)
|
||||
|
||||
validating.value = true
|
||||
const response = await schedulerAPI.validateNow()
|
||||
if (response.code === 200) {
|
||||
ElMessage.success('全量验证已启动,请在日志中查看进度')
|
||||
} else {
|
||||
ElMessage.error('启动验证失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('启动验证失败:', error)
|
||||
ElMessage.error('启动验证失败')
|
||||
}
|
||||
} finally {
|
||||
validating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 保存 ====================
|
||||
async function handleSave() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
if (settings.api_key) {
|
||||
localStorage.setItem('api_key', settings.api_key)
|
||||
} else {
|
||||
localStorage.removeItem('api_key')
|
||||
}
|
||||
const response = await settingsAPI.saveSettings(settings)
|
||||
|
||||
const { api_key, ...settingsToSend } = settings
|
||||
const response = await fetch('http://localhost:8923/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settingsToSend)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
ElMessage.success('配置保存成功啦~')
|
||||
if (response.code === 200) {
|
||||
ElMessage.success('配置保存成功')
|
||||
// 刷新调度器状态
|
||||
await fetchSchedulerStatus()
|
||||
} else {
|
||||
ElMessage.error('配置保存失败呢~')
|
||||
ElMessage.error('配置保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存设置失败:', error)
|
||||
ElMessage.error('配置保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
onMounted(() => {
|
||||
fetchSettings()
|
||||
fetchSchedulerStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-card {
|
||||
border-radius: var(--radius-xl);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-card:hover {
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
.scheduler-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
margin-right: 8px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -172,13 +389,49 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scheduler-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-muted);
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.status-dot.active {
|
||||
background: #67c23a;
|
||||
box-shadow: 0 0 8px #67c23a;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.scheduler-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.scheduler-info {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
@@ -188,18 +441,29 @@ onMounted(() => {
|
||||
|
||||
.setting-suffix {
|
||||
margin-left: 10px;
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.setting-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
margin-left: 10px;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
:deep(.el-form-item__label) {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
background: var(--surface);
|
||||
color: var(--primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-alert) {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user