diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index 4cc81b9..ac7f684 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -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'
})
}
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 3a4b343..e12fc1e 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -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')
diff --git a/frontend/src/views/ProxyList.vue b/frontend/src/views/ProxyList.vue
index c1a4d94..575e3ed 100644
--- a/frontend/src/views/ProxyList.vue
+++ b/frontend/src/views/ProxyList.vue
@@ -5,7 +5,7 @@
-
+
@@ -14,24 +14,14 @@
-
+
-
+
-
-
- 🔍
- 搜索
-
-
- 🔄
- 重置
-
-
@@ -40,19 +30,24 @@
@@ -76,15 +71,14 @@
-
+ {{ scope.row.score || 0 }}
+
+
+
+
+ {{ formatDateTime(scope.row.last_check) }}
-
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()} 格式成功啦~`)
}
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue
index 22c8681..60a4b24 100644
--- a/frontend/src/views/Settings.vue
+++ b/frontend/src/views/Settings.vue
@@ -14,6 +14,17 @@
+
+
+ 用于执行管理操作的API Key
+
+
@@ -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;
diff --git a/tasks_manager.py b/tasks_manager.py
index 172a7d2..569f071 100644
--- a/tasks_manager.py
+++ b/tasks_manager.py
@@ -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:
diff --git a/test_results.json b/test_results.json
new file mode 100644
index 0000000..ae99e39
--- /dev/null
+++ b/test_results.json
@@ -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
+ }
+ ]
+}
\ No newline at end of file