问题分析: - 服务频繁崩溃(平均1小时1次) - 无异常处理导致任何错误都引发进程退出 - 无进程管理器,崩溃后不自动重启 - 端口冲突无检测机制 主要修复: 1. 全局异常处理 - uncaughtException 捕获 - unhandledRejection 捕获 - 端口错误监听 - API错误中间件 2. PM2进程管理 - 安装PM2 v6.0.14 - 配置自动重启 - 启用开机自启(systemd) - 进程监控和日志管理 3. 健康检查端点 - GET /health - 返回运行时状态、内存、版本信息 - 用于监控和诊断 4. 优雅关闭机制 - 正确释放HTTP服务器 - 关闭数据库连接 - 10秒超时保护 5. 配置文件修复 - package.json: main指向server.js - 版本更新到3.1.0 - 添加PM2快捷命令 新增文件: - CRASH-ANALYSIS.md: 详细问题分析报告 - service.sh: 服务管理脚本(start/stop/restart/logs等) 测试结果: - 20次并发请求 100%成功 - PM2自动重启验证通过 - 开机自启配置完成 影响: - 稳定性: 1小时1崩 → 持续稳定运行 - 可维护性: 完善的日志和监控 - 可靠性: 崩溃自动恢复
419 lines
13 KiB
JavaScript
419 lines
13 KiB
JavaScript
const express = require('express');
|
|
const sqlite3 = require('sqlite3').verbose();
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
const PORT = 3000;
|
|
|
|
// 中间件
|
|
app.use(express.json());
|
|
app.use(express.static('public'));
|
|
|
|
// 数据库连接
|
|
const db = new sqlite3.Database('./tasks.db', (err) => {
|
|
if (err) {
|
|
console.error('❌ 数据库连接失败:', err.message);
|
|
process.exit(1);
|
|
}
|
|
console.log('✅ 已连接到 SQLite 数据库');
|
|
});
|
|
|
|
// 初始化数据库表
|
|
db.serialize(() => {
|
|
// 项目表
|
|
db.run(`CREATE TABLE IF NOT EXISTS projects (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
status TEXT DEFAULT 'active',
|
|
created_by TEXT DEFAULT 'human',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
// 任务表
|
|
db.run(`CREATE TABLE IF NOT EXISTS tasks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
project_id INTEGER NOT NULL,
|
|
parent_id INTEGER,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
type TEXT DEFAULT 'development',
|
|
priority TEXT DEFAULT 'normal',
|
|
status TEXT DEFAULT 'initial',
|
|
assigned_to TEXT,
|
|
created_by TEXT DEFAULT 'human',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (project_id) REFERENCES projects(id),
|
|
FOREIGN KEY (parent_id) REFERENCES tasks(id)
|
|
)`);
|
|
|
|
// 智能体表
|
|
db.run(`CREATE TABLE IF NOT EXISTS agents (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT UNIQUE NOT NULL,
|
|
type TEXT,
|
|
capabilities TEXT,
|
|
status TEXT DEFAULT 'idle',
|
|
current_task TEXT,
|
|
completed_tasks INTEGER DEFAULT 0,
|
|
last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
// 附件表
|
|
db.run(`CREATE TABLE IF NOT EXISTS attachments (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
task_id INTEGER NOT NULL,
|
|
filename TEXT NOT NULL,
|
|
filepath TEXT NOT NULL,
|
|
size INTEGER,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
|
)`);
|
|
|
|
// 规约表 (支持全局和项目级别)
|
|
db.run(`CREATE TABLE IF NOT EXISTS rules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
project_id INTEGER,
|
|
rule_no TEXT NOT NULL,
|
|
rule_type TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
)`);
|
|
|
|
console.log('✅ 数据库表已初始化');
|
|
});
|
|
|
|
// ==================== API 路由 ====================
|
|
|
|
// 健康检查
|
|
app.get('/health', (req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage(),
|
|
version: require('./package.json').version
|
|
});
|
|
});
|
|
|
|
// 统计信息
|
|
app.get('/api/stats', (req, res) => {
|
|
const stats = {};
|
|
|
|
db.get('SELECT COUNT(*) as count FROM projects', (err, row) => {
|
|
stats.projects = row ? row.count : 0;
|
|
|
|
db.get('SELECT COUNT(*) as count FROM tasks', (err, row) => {
|
|
stats.tasks = row ? row.count : 0;
|
|
|
|
db.get('SELECT COUNT(*) as count FROM tasks WHERE status = "in_progress"', (err, row) => {
|
|
stats.in_progress_tasks = row ? row.count : 0;
|
|
|
|
db.get('SELECT COUNT(*) as count FROM tasks WHERE status = "completed"', (err, row) => {
|
|
stats.completed_tasks = row ? row.count : 0;
|
|
|
|
db.get('SELECT COUNT(*) as count FROM agents', (err, row) => {
|
|
stats.total_agents = row ? row.count : 0;
|
|
|
|
db.get('SELECT COUNT(*) as count FROM agents WHERE status = "busy"', (err, row) => {
|
|
stats.active_agents = row ? row.count : 0;
|
|
res.json(stats);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// 项目管理
|
|
app.get('/api/projects', (req, res) => {
|
|
db.all('SELECT * FROM projects ORDER BY created_at DESC', (err, rows) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json(rows || []);
|
|
});
|
|
});
|
|
|
|
app.post('/api/projects', (req, res) => {
|
|
const { name, description, created_by = 'human' } = req.body;
|
|
|
|
db.run(
|
|
'INSERT INTO projects (name, description, created_by) VALUES (?, ?, ?)',
|
|
[name, description, created_by],
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ id: this.lastID, name, description, created_by });
|
|
}
|
|
);
|
|
});
|
|
|
|
app.get('/api/projects/:id', (req, res) => {
|
|
db.get('SELECT * FROM projects WHERE id = ?', [req.params.id], (err, row) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
if (!row) return res.status(404).json({ error: 'Project not found' });
|
|
res.json(row);
|
|
});
|
|
});
|
|
|
|
app.delete('/api/projects/:id', (req, res) => {
|
|
// 先删除项目下的所有任务
|
|
db.run('DELETE FROM tasks WHERE project_id = ?', [req.params.id], (err) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
|
|
// 然后删除项目
|
|
db.run('DELETE FROM projects WHERE id = ?', [req.params.id], function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
if (this.changes === 0) return res.status(404).json({ error: 'Project not found' });
|
|
res.json({ success: true });
|
|
});
|
|
});
|
|
});
|
|
|
|
// 任务管理
|
|
app.get('/api/tasks', (req, res) => {
|
|
const { project_id } = req.query;
|
|
let query = 'SELECT t.*, (SELECT COUNT(*) FROM tasks WHERE parent_id = t.id) as subtask_count, (SELECT COUNT(*) FROM attachments WHERE task_id = t.id) as attachment_count FROM tasks t';
|
|
let params = [];
|
|
|
|
if (project_id) {
|
|
query += ' WHERE t.project_id = ?';
|
|
params.push(project_id);
|
|
}
|
|
|
|
query += ' ORDER BY t.created_at DESC';
|
|
|
|
db.all(query, params, (err, rows) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json(rows || []);
|
|
});
|
|
});
|
|
|
|
app.post('/api/tasks', (req, res) => {
|
|
const { project_id, parent_id, title, description, type = 'development', priority = 'normal', status = 'initial', assigned_to, created_by = 'human' } = req.body;
|
|
|
|
db.run(
|
|
'INSERT INTO tasks (project_id, parent_id, title, description, type, priority, status, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
[project_id, parent_id || null, title, description, type, priority, status, assigned_to || null, created_by],
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ id: this.lastID, project_id, title, type, priority, status });
|
|
}
|
|
);
|
|
});
|
|
|
|
app.get('/api/tasks/:id', (req, res) => {
|
|
db.get('SELECT * FROM tasks WHERE id = ?', [req.params.id], (err, task) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
if (!task) return res.status(404).json({ error: 'Task not found' });
|
|
|
|
// 获取附件
|
|
db.all('SELECT * FROM attachments WHERE task_id = ?', [req.params.id], (err, attachments) => {
|
|
task.attachments = attachments || [];
|
|
|
|
// 获取子任务
|
|
db.all('SELECT * FROM tasks WHERE parent_id = ?', [req.params.id], (err, subtasks) => {
|
|
task.subtasks = subtasks || [];
|
|
res.json(task);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
app.put('/api/tasks/:id', (req, res) => {
|
|
const updates = [];
|
|
const values = [];
|
|
|
|
['status', 'type', 'priority', 'assigned_to', 'title', 'description'].forEach(field => {
|
|
if (req.body[field] !== undefined) {
|
|
updates.push(`${field} = ?`);
|
|
values.push(req.body[field]);
|
|
}
|
|
});
|
|
|
|
if (updates.length === 0) {
|
|
return res.status(400).json({ error: 'No fields to update' });
|
|
}
|
|
|
|
values.push(req.params.id);
|
|
|
|
db.run(
|
|
`UPDATE tasks SET ${updates.join(', ')} WHERE id = ?`,
|
|
values,
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
if (this.changes === 0) return res.status(404).json({ error: 'Task not found' });
|
|
res.json({ success: true });
|
|
}
|
|
);
|
|
});
|
|
|
|
app.delete('/api/tasks/:id', (req, res) => {
|
|
db.run('DELETE FROM tasks WHERE id = ?', [req.params.id], function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ success: true });
|
|
});
|
|
});
|
|
|
|
// 智能体管理
|
|
app.get('/api/agents', (req, res) => {
|
|
db.all('SELECT * FROM agents ORDER BY name', (err, rows) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json(rows || []);
|
|
});
|
|
});
|
|
|
|
app.post('/api/agents', (req, res) => {
|
|
const { name, type, capabilities } = req.body;
|
|
|
|
db.run(
|
|
'INSERT INTO agents (name, type, capabilities) VALUES (?, ?, ?)',
|
|
[name, type, JSON.stringify(capabilities)],
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ id: this.lastID, name, type, capabilities });
|
|
}
|
|
);
|
|
});
|
|
|
|
// 规约管理 (支持多条目)
|
|
// 获取全局规约列表
|
|
app.get('/api/rules/global', (req, res) => {
|
|
db.all('SELECT * FROM rules WHERE project_id IS NULL ORDER BY rule_no', (err, rows) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json(rows || []);
|
|
});
|
|
});
|
|
|
|
// 获取项目规约列表
|
|
app.get('/api/rules/project/:projectId', (req, res) => {
|
|
db.all('SELECT * FROM rules WHERE project_id = ? ORDER BY rule_no', [req.params.projectId], (err, rows) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json(rows || []);
|
|
});
|
|
});
|
|
|
|
// 获取单个规约
|
|
app.get('/api/rules/:id', (req, res) => {
|
|
db.get('SELECT * FROM rules WHERE id = ?', [req.params.id], (err, row) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
if (!row) return res.status(404).json({ error: 'Rule not found' });
|
|
res.json(row);
|
|
});
|
|
});
|
|
|
|
// 创建规约
|
|
app.post('/api/rules', (req, res) => {
|
|
const { project_id, rule_no, rule_type, description } = req.body;
|
|
|
|
db.run(
|
|
'INSERT INTO rules (project_id, rule_no, rule_type, description) VALUES (?, ?, ?, ?)',
|
|
[project_id || null, rule_no, rule_type, description],
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ id: this.lastID, project_id, rule_no, rule_type, description });
|
|
}
|
|
);
|
|
});
|
|
|
|
// 更新规约
|
|
app.put('/api/rules/:id', (req, res) => {
|
|
const { rule_no, rule_type, description } = req.body;
|
|
const updates = [];
|
|
const values = [];
|
|
|
|
if (rule_no !== undefined) { updates.push('rule_no = ?'); values.push(rule_no); }
|
|
if (rule_type !== undefined) { updates.push('rule_type = ?'); values.push(rule_type); }
|
|
if (description !== undefined) { updates.push('description = ?'); values.push(description); }
|
|
updates.push('updated_at = CURRENT_TIMESTAMP');
|
|
|
|
if (values.length === 0) {
|
|
return res.status(400).json({ error: 'No fields to update' });
|
|
}
|
|
|
|
values.push(req.params.id);
|
|
|
|
db.run(
|
|
`UPDATE rules SET ${updates.join(', ')} WHERE id = ?`,
|
|
values,
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ success: true });
|
|
}
|
|
);
|
|
});
|
|
|
|
// 删除规约
|
|
app.delete('/api/rules/:id', (req, res) => {
|
|
db.run('DELETE FROM rules WHERE id = ?', [req.params.id], function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json({ success: true });
|
|
});
|
|
});
|
|
|
|
|
|
// 错误处理中间件(必须在所有路由之后)
|
|
app.use((err, req, res, next) => {
|
|
console.error('❌ API错误:', err.stack);
|
|
res.status(500).json({
|
|
error: err.message || '服务器内部错误',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
// 启动服务器
|
|
const server = app.listen(PORT, () => {
|
|
console.log(`🚀 多智能体任务管理系统已启动`);
|
|
console.log(` 本地访问: http://localhost:${PORT}`);
|
|
console.log(` 远程访问: http://10.0.6.5:${PORT}`);
|
|
});
|
|
|
|
// 监听端口错误
|
|
server.on('error', (err) => {
|
|
if (err.code === 'EADDRINUSE') {
|
|
console.error(`❌ 端口 ${PORT} 已被占用`);
|
|
console.error(` 请先停止占用端口的进程: lsof -ti:${PORT} | xargs kill -9`);
|
|
process.exit(1);
|
|
} else {
|
|
console.error('❌ 服务器启动失败:', err);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
// 全局异常捕获
|
|
process.on('uncaughtException', (err) => {
|
|
console.error('❌ 未捕获异常:', err);
|
|
console.error(' 堆栈:', err.stack);
|
|
// 不退出进程,继续运行
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
console.error('❌ 未处理的Promise拒绝:', reason);
|
|
console.error(' Promise:', promise);
|
|
// 不退出进程,继续运行
|
|
});
|
|
|
|
// 优雅关闭
|
|
process.on('SIGINT', () => {
|
|
console.log('\n⏸️ 收到关闭信号,准备关闭服务器...');
|
|
server.close(() => {
|
|
console.log('📡 HTTP服务器已关闭');
|
|
db.close((err) => {
|
|
if (err) {
|
|
console.error('❌ 数据库关闭失败:', err);
|
|
process.exit(1);
|
|
}
|
|
console.log('💾 数据库连接已关闭');
|
|
console.log('👋 服务器已完全关闭');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
// 强制超时退出(10秒)
|
|
setTimeout(() => {
|
|
console.error('⚠️ 强制退出(超时)');
|
|
process.exit(1);
|
|
}, 10000);
|
|
});
|