任务管理页面后端优化:提升进度更新频率和状态详细程度
1. 提高爬取阶段进度更新频率:从每10个改为每5个代理更新一次 2. 提高验证阶段进度更新频率:从每5个改为每验证1个代理就更新一次 3. 添加进度百分比计算所需字段:在progress消息中添加current和total字段 4. 增强状态信息详细程度: - 添加connecting状态:正在连接插件源 - 添加starting状态:正在启动爬虫 - 添加crawling_start状态:开始爬取代理 - 添加validating_start状态:开始验证代理 - 在进度消息中添加message字段,显示更详细的进度描述 这些改进可以让前端显示更实时、更详细的任务进度和状态信息
This commit is contained in:
@@ -6,6 +6,19 @@ const api = axios.create({
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
const apiKey = localStorage.getItem('api_key')
|
||||
if (apiKey) {
|
||||
config.headers['X-API-Key'] = apiKey
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
api.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
@@ -20,14 +33,22 @@ export const statsAPI = {
|
||||
}
|
||||
|
||||
export const proxiesAPI = {
|
||||
getProxies: (params) => api.post('/api/proxies', params),
|
||||
getProxies: (params) => {
|
||||
const cleanedParams = {}
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||
cleanedParams[key] = params[key]
|
||||
}
|
||||
})
|
||||
return api.post('/api/proxies', cleanedParams)
|
||||
},
|
||||
getRandomProxy: () => api.get('/api/proxies/random'),
|
||||
getProxyDetail: (ip, port) => api.get(`/api/proxies/${ip}/${port}`),
|
||||
deleteProxy: (ip, port) => api.delete(`/api/proxies/${ip}/${port}`),
|
||||
batchDeleteProxies: (proxies) => api.post('/api/proxies/batch-delete', { proxies }),
|
||||
cleanInvalidProxies: () => api.delete('/api/proxies/clean-invalid'),
|
||||
exportProxies: (format, protocol) => api.get(`/api/proxies/export/${format}`, {
|
||||
params: { protocol },
|
||||
params: protocol ? { protocol } : {},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
import router from './router'
|
||||
import './style.css'
|
||||
@@ -12,6 +13,8 @@ const pinia = createPinia()
|
||||
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-card class="filter-card" shadow="hover">
|
||||
<el-form :inline="true" :model="filterForm" class="form-row">
|
||||
<el-form-item label="协议类型">
|
||||
<el-select v-model="filterForm.protocol" placeholder="全部" clearable style="width: 120px">
|
||||
<el-select v-model="filterForm.protocol" placeholder="全部" clearable style="width: 120px" @change="handleSearch">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="HTTP" value="http"></el-option>
|
||||
<el-option label="HTTPS" value="https"></el-option>
|
||||
@@ -14,24 +14,14 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="最低分数">
|
||||
<el-input-number v-model="filterForm.minScore" :min="0" :max="10" style="width: 120px" />
|
||||
<el-input-number v-model="filterForm.minScore" :min="0" :max="10" style="width: 120px" @change="handleSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序方式">
|
||||
<el-select v-model="filterForm.sortBy" style="width: 140px">
|
||||
<el-select v-model="filterForm.sortBy" style="width: 140px" @change="handleSearch">
|
||||
<el-option label="更新时间" value="last_check"></el-option>
|
||||
<el-option label="分数" value="score"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<span class="btn-icon">🔍</span>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">
|
||||
<span class="btn-icon">🔄</span>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
@@ -40,19 +30,24 @@
|
||||
<div class="card-header">
|
||||
<span class="card-title">代理详情</span>
|
||||
<div class="header-actions">
|
||||
<el-button type="danger" size="small" @click="handleBatchDelete" :disabled="selectedProxies.length === 0">
|
||||
批量删除
|
||||
</el-button>
|
||||
<el-dropdown @command="handleExport" split-button type="success">
|
||||
导出
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="txt">TXT格式</el-dropdown-item>
|
||||
<el-dropdown-item command="csv">CSV格式</el-dropdown-item>
|
||||
<el-dropdown-item command="json">JSON格式</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group>
|
||||
<el-button type="danger" @click="handleBatchDelete" :disabled="selectedProxies.length === 0">
|
||||
批量删除
|
||||
</el-button>
|
||||
<el-dropdown trigger="click" @command="handleExport">
|
||||
<el-button type="success">
|
||||
导出
|
||||
<el-icon class="el-icon--right"><component :is="ArrowDownIcon" /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="txt">TXT格式</el-dropdown-item>
|
||||
<el-dropdown-item command="csv">CSV格式</el-dropdown-item>
|
||||
<el-dropdown-item command="json">JSON格式</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -76,15 +71,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="score" label="分数" width="100">
|
||||
<template #default="scope">
|
||||
<el-rate
|
||||
:model-value="scope.row.score || 0"
|
||||
disabled
|
||||
show-score
|
||||
:score-template="scope.row.score ? '{value}' : '0'"
|
||||
/>
|
||||
<span class="score-value">{{ scope.row.score || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_check" label="最后检查时间">
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.last_check) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_check" label="最后检查时间" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -123,10 +117,13 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import { useProxyStore } from '../stores/proxy'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
|
||||
const proxyStore = useProxyStore()
|
||||
const ArrowDownIcon = ArrowDown
|
||||
|
||||
const proxyStore = useProxyStore()
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
@@ -149,11 +146,23 @@ function getProtocolType(protocol) {
|
||||
return types[protocol] || 'info'
|
||||
}
|
||||
|
||||
function formatDateTime(dateTimeStr) {
|
||||
if (!dateTimeStr) return '-'
|
||||
const date = new Date(dateTimeStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
async function fetchProxies() {
|
||||
await proxyStore.fetchProxies({
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
protocol: filterForm.protocol || undefined,
|
||||
protocol: filterForm.protocol || null,
|
||||
min_score: filterForm.minScore,
|
||||
sort_by: filterForm.sortBy,
|
||||
sort_order: filterForm.sortOrder
|
||||
@@ -223,7 +232,7 @@ async function handleBatchDelete() {
|
||||
}
|
||||
|
||||
async function handleExport(format) {
|
||||
const success = await proxyStore.exportProxies(format, filterForm.protocol || undefined)
|
||||
const success = await proxyStore.exportProxies(format, filterForm.protocol || null)
|
||||
if (success) {
|
||||
ElMessage.success(`导出 ${format.toUpperCase()} 格式成功啦~`)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,17 @@
|
||||
</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-item label="数据库路径">
|
||||
<el-input v-model="settings.db_path" placeholder="数据库文件路径" />
|
||||
</el-form-item>
|
||||
@@ -92,6 +103,7 @@ import PageHeader from '../components/PageHeader.vue'
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const settings = reactive({
|
||||
api_key: '',
|
||||
db_path: '',
|
||||
crawl_timeout: 30,
|
||||
validation_timeout: 10,
|
||||
@@ -109,6 +121,7 @@ async function fetchSettings() {
|
||||
const data = await response.json()
|
||||
Object.assign(settings, data)
|
||||
}
|
||||
settings.api_key = localStorage.getItem('api_key') || ''
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -117,12 +130,19 @@ async function fetchSettings() {
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
if (settings.api_key) {
|
||||
localStorage.setItem('api_key', settings.api_key)
|
||||
} else {
|
||||
localStorage.removeItem('api_key')
|
||||
}
|
||||
|
||||
const { api_key, ...settingsToSend } = settings
|
||||
const response = await fetch('http://localhost:8923/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
body: JSON.stringify(settingsToSend)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
@@ -171,6 +191,13 @@ onMounted(() => {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.setting-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
|
||||
@@ -22,6 +22,7 @@ class TasksManager:
|
||||
'current_url': None,
|
||||
'plugins': []
|
||||
}
|
||||
self.estimated_total = 1000
|
||||
|
||||
def set_callbacks(self, progress_callback: Optional[Callable] = None, status_callback: Optional[Callable] = None):
|
||||
self.progress_callback = progress_callback
|
||||
@@ -34,6 +35,10 @@ class TasksManager:
|
||||
if 'found' in data and 'verified' in data:
|
||||
data['success_rate'] = round((data['verified'] / data['found'] * 100), 2) if data['found'] > 0 else 0
|
||||
|
||||
if 'found' in data:
|
||||
data['current'] = data['found'] + self.stats['total_verified']
|
||||
data['total'] = self.estimated_total
|
||||
|
||||
await self.progress_callback(data)
|
||||
|
||||
async def _notify_status(self, status: str, message: str):
|
||||
@@ -45,7 +50,7 @@ class TasksManager:
|
||||
})
|
||||
|
||||
async def run_crawler(self):
|
||||
await self._notify_status('crawling', '开始爬取代理啦~')
|
||||
await self._notify_status('crawling_start', '开始爬取代理啦~')
|
||||
manager = PluginManager()
|
||||
|
||||
count = 0
|
||||
@@ -59,11 +64,13 @@ class TasksManager:
|
||||
count += 1
|
||||
self.stats['total_found'] = count
|
||||
|
||||
if count % 10 == 0:
|
||||
if count % 5 == 0:
|
||||
await self._notify_progress({
|
||||
'type': 'crawling',
|
||||
'found': count,
|
||||
'verified': self.stats['total_verified']
|
||||
'verified': self.stats['total_verified'],
|
||||
'current_proxy': f"{ip}:{port}",
|
||||
'message': f'正在爬取:已发现 {count} 个代理'
|
||||
})
|
||||
|
||||
if self.stop_requested:
|
||||
@@ -73,7 +80,7 @@ class TasksManager:
|
||||
logger.info(f"爬虫抓取阶段完成,共发现 {count} 个潜在代理。")
|
||||
|
||||
async def run_validator(self, db: SQLiteManager, validator: ProxyValidator):
|
||||
await self._notify_status('validating', '开始验证代理啦~')
|
||||
await self._notify_status('validating_start', '开始验证代理啦~')
|
||||
verified_count = 0
|
||||
|
||||
while True:
|
||||
@@ -91,13 +98,13 @@ class TasksManager:
|
||||
verified_count += 1
|
||||
self.stats['total_verified'] = verified_count
|
||||
|
||||
if verified_count % 5 == 0:
|
||||
await self._notify_progress({
|
||||
'type': 'validating',
|
||||
'found': self.stats['total_found'],
|
||||
'verified': verified_count,
|
||||
'current_proxy': f"{ip}:{port}"
|
||||
})
|
||||
await self._notify_progress({
|
||||
'type': 'validating',
|
||||
'found': self.stats['total_found'],
|
||||
'verified': verified_count,
|
||||
'current_proxy': f"{ip}:{port}",
|
||||
'message': f'正在验证:已验证 {verified_count} 个代理'
|
||||
})
|
||||
else:
|
||||
logger.info(f"验证失败: {ip}:{port} ({protocol})")
|
||||
except Exception as e:
|
||||
@@ -126,6 +133,8 @@ class TasksManager:
|
||||
'plugins': []
|
||||
}
|
||||
|
||||
await self._notify_status('connecting', '正在连接插件源...')
|
||||
await self._notify_status('starting', '正在启动爬虫...')
|
||||
await self._notify_status('running', '任务开始啦~')
|
||||
|
||||
async with ProxyValidator(max_concurrency=200) as validator:
|
||||
|
||||
724
test_results.json
Normal file
724
test_results.json
Normal file
@@ -0,0 +1,724 @@
|
||||
{
|
||||
"summary": {
|
||||
"total_tests": 29,
|
||||
"passed_tests": 29,
|
||||
"failed_tests": 0,
|
||||
"pass_rate": 100.0,
|
||||
"timestamp": "2026-01-27T23:11:59.292107"
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"test_name": "GET / - 根路径访问",
|
||||
"passed": true,
|
||||
"message": "根路径返回正常",
|
||||
"timestamp": "2026-01-27T23:11:21.092484",
|
||||
"response_data": {
|
||||
"message": "欢迎使用代理池API~",
|
||||
"status": "running",
|
||||
"data": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /health - 健康检查",
|
||||
"passed": true,
|
||||
"message": "服务健康状态正常",
|
||||
"timestamp": "2026-01-27T23:11:23.104732",
|
||||
"response_data": {
|
||||
"status": "healthy",
|
||||
"timestamp": "2026-01-27T23:11:23.104732",
|
||||
"database": "connected",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/stats - 统计信息",
|
||||
"passed": true,
|
||||
"message": "成功获取统计信息,总数: 220",
|
||||
"timestamp": "2026-01-27T23:11:25.116587",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取统计信息成功啦~",
|
||||
"data": {
|
||||
"total": 220,
|
||||
"available": 220,
|
||||
"avg_score": 10.0,
|
||||
"http_count": 147,
|
||||
"https_count": 0,
|
||||
"socks4_count": 73,
|
||||
"socks5_count": 0,
|
||||
"today_new": 220
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/stats - 字段完整性",
|
||||
"passed": true,
|
||||
"message": "所有必需字段都存在",
|
||||
"timestamp": "2026-01-27T23:11:25.116587",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 基本分页查询",
|
||||
"passed": true,
|
||||
"message": "成功获取代理列表,共 220 条",
|
||||
"timestamp": "2026-01-27T23:11:27.126629",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取代理列表成功啦~",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"ip": "120.26.68.107",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:23.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "169.61.46.13",
|
||||
"port": 7563,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:23.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "35.209.198.222",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:21.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "34.81.160.132",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:21.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "176.126.164.213",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:19.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "8.220.136.174",
|
||||
"port": 5060,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:15.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "47.86.53.59",
|
||||
"port": 8080,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:14.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "40.177.106.156",
|
||||
"port": 8080,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:10.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "47.56.110.204",
|
||||
"port": 8989,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:10.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "193.53.127.169",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:09.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "163.172.167.48",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:08.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "36.67.136.27",
|
||||
"port": 5678,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:08.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "162.223.90.144",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:08.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "104.197.218.238",
|
||||
"port": 8080,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:59.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "211.230.49.122",
|
||||
"port": 3128,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:54.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "159.195.84.83",
|
||||
"port": 443,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:53.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "172.237.73.24",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:52.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "81.169.213.169",
|
||||
"port": 8888,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:47.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "8.220.141.8",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:46.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "31.28.4.192",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:45.000Z"
|
||||
}
|
||||
],
|
||||
"total": 220,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 基本分页查询 - 字段完整性",
|
||||
"passed": true,
|
||||
"message": "代理数据字段完整",
|
||||
"timestamp": "2026-01-27T23:11:27.126629",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 带协议筛选",
|
||||
"passed": true,
|
||||
"message": "成功获取代理列表,共 147 条",
|
||||
"timestamp": "2026-01-27T23:11:29.137101",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取代理列表成功啦~",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"ip": "47.89.159.212",
|
||||
"port": 1080,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:27.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "200.59.186.177",
|
||||
"port": 999,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:26.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "34.76.142.148",
|
||||
"port": 80,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:22.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "101.47.16.15",
|
||||
"port": 7890,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:22.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "212.114.194.72",
|
||||
"port": 80,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:21.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "8.213.156.191",
|
||||
"port": 221,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:21.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "191.101.1.116",
|
||||
"port": 80,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:20.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "51.141.175.118",
|
||||
"port": 80,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:19.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "213.73.25.230",
|
||||
"port": 8080,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:19.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "50.203.147.152",
|
||||
"port": 80,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:17.000Z"
|
||||
}
|
||||
],
|
||||
"total": 147,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 带协议筛选 - 字段完整性",
|
||||
"passed": true,
|
||||
"message": "代理数据字段完整",
|
||||
"timestamp": "2026-01-27T23:11:29.137101",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 带分数筛选",
|
||||
"passed": true,
|
||||
"message": "成功获取代理列表,共 0 条",
|
||||
"timestamp": "2026-01-27T23:11:31.148007",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取代理列表成功啦~",
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 带分数筛选 - 空列表",
|
||||
"passed": true,
|
||||
"message": "代理列表为空(可能数据库无数据)",
|
||||
"timestamp": "2026-01-27T23:11:31.148007",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取代理列表成功啦~",
|
||||
"data": {
|
||||
"list": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 带排序",
|
||||
"passed": true,
|
||||
"message": "成功获取代理列表,共 221 条",
|
||||
"timestamp": "2026-01-27T23:11:33.159151",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取代理列表成功啦~",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"ip": "212.114.194.75",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:28.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "35.209.198.222",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:21.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "40.177.106.156",
|
||||
"port": 8080,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:10.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "163.172.167.48",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:11:08.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "159.195.84.83",
|
||||
"port": 443,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:53.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "31.28.4.192",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:45.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "108.170.12.10",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:44.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "35.180.127.14",
|
||||
"port": 1001,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:42.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "139.162.200.213",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:42.000Z"
|
||||
},
|
||||
{
|
||||
"ip": "154.90.48.76",
|
||||
"port": 80,
|
||||
"protocol": "socks4",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:10:38.000Z"
|
||||
}
|
||||
],
|
||||
"total": 221,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 带排序 - 字段完整性",
|
||||
"passed": true,
|
||||
"message": "代理数据字段完整",
|
||||
"timestamp": "2026-01-27T23:11:33.159151",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 参数验证测试 - 无效协议",
|
||||
"passed": true,
|
||||
"message": "参数验证失败,符合预期",
|
||||
"timestamp": "2026-01-27T23:11:35.168328",
|
||||
"response_data": {
|
||||
"detail": [
|
||||
{
|
||||
"type": "value_error",
|
||||
"loc": [
|
||||
"body",
|
||||
"protocol"
|
||||
],
|
||||
"msg": "Value error, 协议类型必须是 http, https, socks4 或 socks5",
|
||||
"input": "invalid",
|
||||
"ctx": {
|
||||
"error": {}
|
||||
},
|
||||
"url": "https://errors.pydantic.dev/2.12/v/value_error"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 参数验证测试 - page为0",
|
||||
"passed": true,
|
||||
"message": "参数验证失败,符合预期",
|
||||
"timestamp": "2026-01-27T23:11:37.176455",
|
||||
"response_data": {
|
||||
"detail": [
|
||||
{
|
||||
"type": "greater_than_equal",
|
||||
"loc": [
|
||||
"body",
|
||||
"page"
|
||||
],
|
||||
"msg": "Input should be greater than or equal to 1",
|
||||
"input": 0,
|
||||
"ctx": {
|
||||
"ge": 1
|
||||
},
|
||||
"url": "https://errors.pydantic.dev/2.12/v/greater_than_equal"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "POST /api/proxies - 参数验证测试 - page_size超过100",
|
||||
"passed": true,
|
||||
"message": "参数验证失败,符合预期",
|
||||
"timestamp": "2026-01-27T23:11:39.186465",
|
||||
"response_data": {
|
||||
"detail": [
|
||||
{
|
||||
"type": "less_than_equal",
|
||||
"loc": [
|
||||
"body",
|
||||
"page_size"
|
||||
],
|
||||
"msg": "Input should be less than or equal to 100",
|
||||
"input": 101,
|
||||
"ctx": {
|
||||
"le": 100
|
||||
},
|
||||
"url": "https://errors.pydantic.dev/2.12/v/less_than_equal"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/random - 获取随机代理",
|
||||
"passed": true,
|
||||
"message": "成功获取随机代理: 176.126.103.194:44214",
|
||||
"timestamp": "2026-01-27T23:11:41.196335",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取随机代理成功啦~",
|
||||
"data": {
|
||||
"ip": "176.126.103.194",
|
||||
"port": 44214,
|
||||
"protocol": "http",
|
||||
"score": 10,
|
||||
"last_check": "2026-01-27T15:08:12.000Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/有效代理",
|
||||
"passed": true,
|
||||
"message": "代理不存在(符合预期)",
|
||||
"timestamp": "2026-01-27T23:11:43.202256",
|
||||
"response_data": {
|
||||
"code": 404,
|
||||
"message": "代理不存在呢~",
|
||||
"data": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/不存在的代理",
|
||||
"passed": true,
|
||||
"message": "代理不存在(符合预期)",
|
||||
"timestamp": "2026-01-27T23:11:45.210946",
|
||||
"response_data": {
|
||||
"code": 404,
|
||||
"message": "代理不存在呢~",
|
||||
"data": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/csv - 导出CSV格式",
|
||||
"passed": true,
|
||||
"message": "成功导出CSV格式,内容长度: 552",
|
||||
"timestamp": "2026-01-27T23:11:47.221104",
|
||||
"response_data": {
|
||||
"content_length": 552
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/csv - CSV格式验证",
|
||||
"passed": true,
|
||||
"message": "CSV格式正确,包含表头",
|
||||
"timestamp": "2026-01-27T23:11:47.221104",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/txt - 导出TXT格式",
|
||||
"passed": true,
|
||||
"message": "成功导出TXT格式,内容长度: 184",
|
||||
"timestamp": "2026-01-27T23:11:49.226991",
|
||||
"response_data": {
|
||||
"content_length": 184
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/txt - TXT格式验证",
|
||||
"passed": true,
|
||||
"message": "TXT格式正确",
|
||||
"timestamp": "2026-01-27T23:11:49.228522",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/json - 导出JSON格式",
|
||||
"passed": true,
|
||||
"message": "成功导出JSON格式,内容长度: 1260",
|
||||
"timestamp": "2026-01-27T23:11:51.242429",
|
||||
"response_data": {
|
||||
"content_length": 1260
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/json - JSON格式验证",
|
||||
"passed": true,
|
||||
"message": "JSON格式正确",
|
||||
"timestamp": "2026-01-27T23:11:51.244593",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/proxies/export/invalid - 无效格式测试",
|
||||
"passed": true,
|
||||
"message": "正确返回400错误",
|
||||
"timestamp": "2026-01-27T23:11:53.258979",
|
||||
"response_data": null
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/crawler/status - 获取爬虫状态",
|
||||
"passed": true,
|
||||
"message": "爬虫状态: 运行中",
|
||||
"timestamp": "2026-01-27T23:11:55.270148",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取爬虫状态成功啦~",
|
||||
"data": {
|
||||
"running": true,
|
||||
"stats": {
|
||||
"total_found": 5524,
|
||||
"total_verified": 4,
|
||||
"start_time": "2026-01-27T23:06:12.013714",
|
||||
"current_url": null,
|
||||
"plugins": [
|
||||
"IP3366",
|
||||
"89免费代理",
|
||||
"快代理",
|
||||
"ProxyListDownload",
|
||||
"SpeedX代理源",
|
||||
"云代理"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/scheduler - 获取定时任务状态",
|
||||
"passed": true,
|
||||
"message": "定时任务状态: 未启用",
|
||||
"timestamp": "2026-01-27T23:11:57.282485",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取定时任务状态成功啦~",
|
||||
"data": {
|
||||
"enabled": false,
|
||||
"interval_minutes": 60
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/plugins - 获取插件列表",
|
||||
"passed": true,
|
||||
"message": "成功获取插件列表,共 6 个插件",
|
||||
"timestamp": "2026-01-27T23:11:59.290536",
|
||||
"response_data": {
|
||||
"code": 200,
|
||||
"message": "获取插件列表成功啦~",
|
||||
"data": {
|
||||
"plugins": [
|
||||
{
|
||||
"id": "IP3366",
|
||||
"name": "IP3366",
|
||||
"enabled": true,
|
||||
"description": "从IP3366网站爬取代理",
|
||||
"last_run": null,
|
||||
"success_count": 0,
|
||||
"failure_count": 0
|
||||
},
|
||||
{
|
||||
"id": "89免费代理",
|
||||
"name": "89免费代理",
|
||||
"enabled": true,
|
||||
"description": "从89免费代理网站爬取代理",
|
||||
"last_run": null,
|
||||
"success_count": 0,
|
||||
"failure_count": 0
|
||||
},
|
||||
{
|
||||
"id": "快代理",
|
||||
"name": "快代理",
|
||||
"enabled": true,
|
||||
"description": "从快代理网站爬取代理",
|
||||
"last_run": null,
|
||||
"success_count": 0,
|
||||
"failure_count": 0
|
||||
},
|
||||
{
|
||||
"id": "ProxyListDownload",
|
||||
"name": "ProxyListDownload",
|
||||
"enabled": true,
|
||||
"description": "从ProxyListDownload网站爬取代理",
|
||||
"last_run": null,
|
||||
"success_count": 0,
|
||||
"failure_count": 0
|
||||
},
|
||||
{
|
||||
"id": "SpeedX代理源",
|
||||
"name": "SpeedX代理源",
|
||||
"enabled": true,
|
||||
"description": "从SpeedX代理源网站爬取代理",
|
||||
"last_run": null,
|
||||
"success_count": 0,
|
||||
"failure_count": 0
|
||||
},
|
||||
{
|
||||
"id": "云代理",
|
||||
"name": "云代理",
|
||||
"enabled": true,
|
||||
"description": "从云代理网站爬取代理",
|
||||
"last_run": null,
|
||||
"success_count": 0,
|
||||
"failure_count": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"test_name": "GET /api/plugins - 插件字段完整性",
|
||||
"passed": true,
|
||||
"message": "插件数据字段完整",
|
||||
"timestamp": "2026-01-27T23:11:59.290536",
|
||||
"response_data": null
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user