重构代理池系统:简化架构并增强核心功能

后端变更:
- 移除 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:
祀梦
2026-04-02 11:23:23 +08:00
parent b5932a95b2
commit a79f78b338
47 changed files with 3748 additions and 3190 deletions

View File

@@ -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>