2026-05-10 13:12:44 +08:00

248 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* core/task-queue — 任务队列系统
*
* 任务结构:
* task_id — 唯一任务标识
* source — 来源broadcast / maintenance / dev / auto
* type — 类型system-task / dev-task / maintenance-task / auto-task
* priority — 优先级high / normal / low
* status — 状态pending / running / completed / failed
* executor — 执行者zhuyuan
*
* 任务类型:
* system-task — 系统级任务(广播、状态同步)
* dev-task — 开发任务(代码变更、功能开发)
* maintenance-task — 维护任务(结构修复、文档更新)
* auto-task — 自动生成任务(自检发现的问题)
*
* 任务来源:
* - Notion 广播
* - 系统维护任务
* - 开发任务
* - 自动开发循环生成
*
* 执行流程:
* 任务进入队列 → 按优先级去重 → 执行器运行 → 执行结果写回
*
* 调用方式:
* node core/task-queue [status|run|cleanup]
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '../..');
const QUEUE_PATH = path.join(ROOT, 'core/task-queue/queue.json');
/**
* 加载当前队列
*/
function loadQueue() {
if (!fs.existsSync(QUEUE_PATH)) {
return { version: '5.1', tasks: [], last_updated: null };
}
try {
return JSON.parse(fs.readFileSync(QUEUE_PATH, 'utf-8'));
} catch {
return { version: '5.1', tasks: [], last_updated: null };
}
}
/**
* 保存队列
*/
function saveQueue(queue) {
queue.last_updated = new Date().toISOString();
fs.writeFileSync(QUEUE_PATH, JSON.stringify(queue, null, 2), 'utf-8');
}
/**
* 添加任务到队列
*/
function enqueue(task) {
const queue = loadQueue();
// 去重:相同 task_id 只保留最新
const existing = queue.tasks.findIndex(t => t.task_id === task.task_id);
if (existing !== -1) {
const original = queue.tasks[existing];
queue.tasks[existing] = {
...original,
...task,
queued_at: original.queued_at,
updated_at: new Date().toISOString()
};
console.log(`🔄 更新已有任务: ${task.task_id}`);
} else {
queue.tasks.push({
...task,
queued_at: new Date().toISOString()
});
console.log(` 新增任务: ${task.task_id}`);
}
saveQueue(queue);
return task;
}
/**
* 批量入队
*/
function enqueueBatch(tasks) {
return tasks.map(t => enqueue(t));
}
/**
* 获取下一个待执行任务(按优先级)
*/
function dequeue() {
const queue = loadQueue();
const priorityOrder = { high: 0, normal: 1, low: 2 };
const pending = queue.tasks
.filter(t => t.status === 'pending')
.sort((a, b) => (priorityOrder[a.priority] || 1) - (priorityOrder[b.priority] || 1));
if (pending.length === 0) return null;
const task = pending[0];
task.status = 'running';
task.started_at = new Date().toISOString();
saveQueue(queue);
return task;
}
/**
* 标记任务完成
*/
function complete(taskId, result = null) {
const queue = loadQueue();
const task = queue.tasks.find(t => t.task_id === taskId);
if (!task) {
console.warn(`⚠️ 任务不存在: ${taskId}`);
return null;
}
task.status = 'completed';
task.completed_at = new Date().toISOString();
if (result) task.result = result;
saveQueue(queue);
console.log(`✅ 任务完成: ${taskId}`);
return task;
}
/**
* 标记任务失败
*/
function fail(taskId, error = null) {
const queue = loadQueue();
const task = queue.tasks.find(t => t.task_id === taskId);
if (!task) {
console.warn(`⚠️ 任务不存在: ${taskId}`);
return null;
}
task.status = 'failed';
task.failed_at = new Date().toISOString();
if (error) task.error = String(error);
saveQueue(queue);
console.log(`❌ 任务失败: ${taskId}`);
return task;
}
/**
* 获取队列状态
*/
function status() {
const queue = loadQueue();
const tasks = queue.tasks;
const stats = {
total: tasks.length,
pending: tasks.filter(t => t.status === 'pending').length,
running: tasks.filter(t => t.status === 'running').length,
completed: tasks.filter(t => t.status === 'completed').length,
failed: tasks.filter(t => t.status === 'failed').length,
last_updated: queue.last_updated,
by_type: {
'system-task': tasks.filter(t => t.type === 'system-task').length,
'dev-task': tasks.filter(t => t.type === 'dev-task').length,
'maintenance-task': tasks.filter(t => t.type === 'maintenance-task').length,
'auto-task': tasks.filter(t => t.type === 'auto-task').length,
'glada-task': tasks.filter(t => t.type === 'glada-task').length
}
};
return stats;
}
/**
* 清理已完成任务(保留最近 50 条)
*/
function cleanup() {
const queue = loadQueue();
const completed = queue.tasks.filter(t =>
t.status === 'completed' || t.status === 'failed'
);
if (completed.length > 50) {
const keep = completed
.sort((a, b) => (b.completed_at || b.failed_at || '').localeCompare(a.completed_at || a.failed_at || ''))
.slice(0, 50);
const keepIds = new Set(keep.map(t => t.task_id));
const active = queue.tasks.filter(t =>
t.status === 'pending' || t.status === 'running'
);
queue.tasks = [...active, ...keep];
saveQueue(queue);
console.log(`🧹 清理完成,保留 ${queue.tasks.length} 条任务`);
}
}
// CLI 入口
if (require.main === module) {
const cmd = process.argv[2] || 'status';
switch (cmd) {
case 'status': {
const s = status();
console.log('📊 任务队列状态:');
console.log(` 总计: ${s.total}`);
console.log(` 待处理: ${s.pending}`);
console.log(` 执行中: ${s.running}`);
console.log(` 已完成: ${s.completed}`);
console.log(` 失败: ${s.failed}`);
console.log(` 更新时间: ${s.last_updated || '无'}`);
if (s.total > 0) {
console.log(' 任务类型分布:');
for (const [type, count] of Object.entries(s.by_type)) {
if (count > 0) console.log(` ${type}: ${count}`);
}
}
break;
}
case 'run': {
const task = dequeue();
if (task) {
console.log(`🚀 取出任务: ${task.task_id}`);
} else {
console.log('✅ 队列为空,无待执行任务');
}
break;
}
case 'cleanup': {
cleanup();
break;
}
default:
console.log('用法: node core/task-queue [status|run|cleanup]');
}
}
module.exports = { loadQueue, enqueue, enqueueBatch, dequeue, complete, fail, status, cleanup };