fix: 修复设置系统脱节、队列计数漂移、资源泄露等全量问题

- 统一设置系统:create_scheduler_service 读取 DB 设置覆盖默认值
- 修复 ProxyRepository.update_score 误删所有无效代理的 SQL
- ValidationQueue:修复 Worker 计数漂移与启动恢复任务饿死
- SchedulerService:移除 drain() 阻塞,主循环可正常响应 stop
- TaskService:在调度器周期内自动清理过期任务,防止内存泄漏
- lifespan/conftest:规范关闭顺序,消除 Event loop closed 警告
- Repository:异常日志增加 exc_info,今日新增按 created_at 统计
- ValidatorService:防止 HTTP session 重复关闭,移除 SOCKS 多余 close
- 前端:补全 pluginsStore.isEmpty,ProxyList 最低分数上限改为 100
- 删除 config.py 中冗余的 cors_origins_list property
This commit is contained in:
祀梦
2026-04-04 20:31:52 +08:00
parent 0788a13c8a
commit 875e61f17e
26 changed files with 568 additions and 355 deletions

View File

@@ -97,4 +97,8 @@ export const settingsAPI = {
saveSettings: (data) => api.post('/api/settings', data)
}
export const tasksAPI = {
getTaskStatus: (taskId) => api.get(`/api/tasks/${taskId}`)
}
export default api

View File

@@ -1,4 +1,26 @@
import { pluginsAPI } from '../api'
import { pluginsAPI, tasksAPI } from '../api'
const POLL_INTERVAL = 1000
const MAX_POLL_ATTEMPTS = 30
async function pollTaskStatus(taskId) {
for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL))
const response = await tasksAPI.getTaskStatus(taskId)
if (response.code !== 200) {
continue
}
const status = response.data.status
if (status === 'completed' || status === 'failed') {
return response
}
}
return {
code: 200,
message: '爬取任务进行中,请稍后刷新查看结果',
data: { task_id: taskId, status: 'running' }
}
}
export const pluginService = {
async getPlugins() {
@@ -18,10 +40,28 @@ export const pluginService = {
},
async crawlPlugin(pluginId) {
return pluginsAPI.crawlPlugin(pluginId)
const startRes = await pluginsAPI.crawlPlugin(pluginId)
if (startRes.code !== 200 || !startRes.data?.task_id) {
return startRes
}
const finalRes = await pollTaskStatus(startRes.data.task_id)
return {
code: finalRes.code,
message: finalRes.data?.message || finalRes.message,
data: finalRes.data?.data || finalRes.data
}
},
async crawlAll() {
return pluginsAPI.crawlAll()
const startRes = await pluginsAPI.crawlAll()
if (startRes.code !== 200 || !startRes.data?.task_id) {
return startRes
}
const finalRes = await pollTaskStatus(startRes.data.task_id)
return {
code: finalRes.code,
message: finalRes.data?.message || finalRes.message,
data: finalRes.data?.data || finalRes.data
}
}
}

View File

@@ -14,6 +14,7 @@ export const usePluginsStore = defineStore('plugins', () => {
// ==================== Getters ====================
const enabledCount = computed(() => plugins.value.filter(p => p.enabled).length)
const totalCount = computed(() => plugins.value.length)
const isEmpty = computed(() => !loading.value && plugins.value.length === 0)
// ==================== Actions ====================
@@ -97,6 +98,7 @@ export const usePluginsStore = defineStore('plugins', () => {
// Getters
enabledCount,
totalCount,
isEmpty,
// Actions
fetchPlugins,
togglePlugin,

View File

@@ -132,8 +132,8 @@ export const useProxyStore = defineStore('proxy', () => {
try {
const response = await proxyService.export(format, protocol)
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([response]))
// response 已经是 Blobaxios 配置了 responseType: 'blob'),直接创建下载链接
const url = window.URL.createObjectURL(response)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', `proxies.${format}`)

View File

@@ -23,7 +23,7 @@
<el-input-number
v-model="filterForm.minScore"
:min="0"
:max="10"
:max="100"
style="width: 120px"
@change="handleSearch"
/>
@@ -190,7 +190,7 @@ async function fetchProxies() {
}
abortController = new AbortController()
await proxyStore.fetchProxies({
const success = await proxyStore.fetchProxies({
page: currentPage.value,
page_size: pageSize.value,
protocol: filterForm.protocol || null,
@@ -200,6 +200,9 @@ async function fetchProxies() {
}, abortController.signal)
abortController = null
if (!success) {
ElMessage.error('获取代理列表失败')
}
}
// ==================== 事件处理 ====================