399 lines
9.9 KiB
JavaScript
399 lines
9.9 KiB
JavaScript
import express from 'express';
|
|
import bridgeManager from '../bridge-manager/index.js';
|
|
import websocketServer from '../websocket-server/index.js';
|
|
import taskScheduler from '../task-scheduler/index.js';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import logger from '../logger/index.js';
|
|
import { authMiddleware } from '../auth/middleware.js';
|
|
|
|
const router = express.Router();
|
|
|
|
router.get('/capacity', authMiddleware, (req, res) => {
|
|
const capacity = bridgeManager.getAvailableCapacity();
|
|
const stats = taskScheduler.getStats();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
...capacity,
|
|
processingTasks: stats.processing,
|
|
pendingTasks: stats.pending,
|
|
availableForNewTasks: Math.max(0, capacity.available - stats.pending)
|
|
}
|
|
});
|
|
});
|
|
|
|
router.get('/health', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
});
|
|
});
|
|
|
|
router.get('/bridges', authMiddleware, (req, res) => {
|
|
const bridges = bridgeManager.getAllBridges();
|
|
res.json({
|
|
success: true,
|
|
data: bridges
|
|
});
|
|
});
|
|
|
|
router.get('/bridges/:bridgeId', authMiddleware, (req, res) => {
|
|
const bridge = bridgeManager.getBridge(req.params.bridgeId);
|
|
if (!bridge) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: '桥接器不存在'
|
|
});
|
|
}
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
id: bridge.id,
|
|
info: bridge.info,
|
|
connectedAt: bridge.connectedAt,
|
|
lastHeartbeat: bridge.lastHeartbeat
|
|
}
|
|
});
|
|
});
|
|
|
|
router.post('/task', authMiddleware, async (req, res) => {
|
|
try {
|
|
const { workflowId, nodeInfoList, webhookUrl, taskType } = req.body;
|
|
const requestId = uuidv4();
|
|
|
|
const capacity = bridgeManager.getAvailableCapacity();
|
|
await taskScheduler.setCurrentCapacity(capacity.online, false);
|
|
|
|
const assignResult = taskScheduler.tryDirectAssign({
|
|
requestId,
|
|
workflowId,
|
|
nodeInfoList,
|
|
webhookUrl,
|
|
taskType
|
|
});
|
|
|
|
if (assignResult.success) {
|
|
logger.info(`收到任务请求, 直接分配实例: ${assignResult.instanceId}, bridgeId: ${assignResult.bridgeId}, requestId: ${requestId}`);
|
|
|
|
try {
|
|
const result = await websocketServer.sendTaskToInstance(
|
|
assignResult.bridgeId,
|
|
assignResult.instanceId,
|
|
{ workflowId, nodeInfoList, webhookUrl },
|
|
requestId
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
requestId,
|
|
instanceId: assignResult.instanceId,
|
|
bridgeId: assignResult.bridgeId,
|
|
status: 'processing',
|
|
...result.data
|
|
}
|
|
});
|
|
} catch (sendError) {
|
|
logger.error('发送任务失败:', sendError);
|
|
taskScheduler.handleTaskFailure(requestId, sendError.message);
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
error: sendError.message,
|
|
requestId
|
|
});
|
|
}
|
|
} else {
|
|
const queueResult = taskScheduler.addTask({
|
|
requestId,
|
|
workflowId,
|
|
nodeInfoList,
|
|
webhookUrl,
|
|
taskType
|
|
});
|
|
|
|
if (queueResult.success) {
|
|
logger.info(`收到任务请求, 无可用实例, 加入队列: ${requestId}, 队列位置: ${queueResult.queuePosition}`);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
requestId,
|
|
status: 'queued',
|
|
queuePosition: queueResult.queuePosition,
|
|
capacity: capacity
|
|
}
|
|
});
|
|
} else {
|
|
logger.warn(`任务队列已满: ${requestId}`);
|
|
|
|
res.status(503).json({
|
|
success: false,
|
|
error: queueResult.error,
|
|
data: {
|
|
requestId,
|
|
capacity
|
|
}
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error('处理任务请求失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
router.get('/task/:taskId', authMiddleware, (req, res) => {
|
|
const taskStatus = taskScheduler.getTaskStatus(req.params.taskId);
|
|
|
|
if (!taskStatus) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: '任务不存在'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: taskStatus
|
|
});
|
|
});
|
|
|
|
router.get('/instances', authMiddleware, (req, res) => {
|
|
const instances = bridgeManager.getAllInstances();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: instances
|
|
});
|
|
});
|
|
|
|
router.get('/overview', authMiddleware, (req, res) => {
|
|
const bridges = bridgeManager.getAllBridges();
|
|
const capacity = bridgeManager.getAvailableCapacity();
|
|
const stats = taskScheduler.getStats();
|
|
|
|
let totalInstances = 0;
|
|
let onlineInstances = 0;
|
|
let busyInstances = 0;
|
|
let offlineInstances = 0;
|
|
|
|
for (const bridge of bridges) {
|
|
if (bridge.info?.instances) {
|
|
totalInstances += bridge.info.instances.length;
|
|
onlineInstances += bridge.info.instances.filter(i => i.status === 'online').length;
|
|
busyInstances += bridge.info.instances.filter(i => i.status === 'busy').length;
|
|
offlineInstances += bridge.info.instances.filter(i => i.status === 'offline').length;
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
bridges: {
|
|
total: bridges.length,
|
|
online: bridges.length
|
|
},
|
|
instances: {
|
|
total: totalInstances,
|
|
online: onlineInstances,
|
|
busy: busyInstances,
|
|
offline: offlineInstances,
|
|
locked: capacity.locked,
|
|
available: capacity.available
|
|
},
|
|
tasks: stats
|
|
}
|
|
});
|
|
});
|
|
|
|
router.get('/monitor/overview', authMiddleware, (req, res) => {
|
|
const bridges = bridgeManager.getAllBridges();
|
|
const capacity = bridgeManager.getAvailableCapacity();
|
|
const stats = taskScheduler.getStats();
|
|
|
|
let totalInstances = 0;
|
|
let onlineInstances = 0;
|
|
let busyInstances = 0;
|
|
let offlineInstances = 0;
|
|
|
|
for (const bridge of bridges) {
|
|
if (bridge.info?.instances) {
|
|
totalInstances += bridge.info.instances.length;
|
|
onlineInstances += bridge.info.instances.filter(i => i.status === 'online').length;
|
|
busyInstances += bridge.info.instances.filter(i => i.status === 'busy').length;
|
|
offlineInstances += bridge.info.instances.filter(i => i.status === 'offline').length;
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
instances: {
|
|
total: totalInstances,
|
|
online: onlineInstances,
|
|
busy: busyInstances,
|
|
offline: offlineInstances,
|
|
locked: capacity.locked,
|
|
available: capacity.available
|
|
},
|
|
tasks: stats
|
|
}
|
|
});
|
|
});
|
|
|
|
router.get('/tasks', authMiddleware, (req, res) => {
|
|
const stats = taskScheduler.getStats();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
stats,
|
|
pending: taskScheduler.pendingTaskQueue.slice(0, 20),
|
|
processing: Array.from(taskScheduler.processingTasks.values()),
|
|
recovering: Array.from(taskScheduler.recoveringTasks.values())
|
|
}
|
|
});
|
|
});
|
|
|
|
router.post('/instances/:instanceId/health-check', authMiddleware, async (req, res) => {
|
|
try {
|
|
const { instanceId } = req.params;
|
|
const bridges = bridgeManager.getAllBridges();
|
|
|
|
let targetBridgeId = null;
|
|
for (const bridge of bridges) {
|
|
if (bridge.info?.instances) {
|
|
const instance = bridge.info.instances.find(i => i.id === instanceId);
|
|
if (instance) {
|
|
targetBridgeId = bridge.id;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!targetBridgeId) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: '实例不存在'
|
|
});
|
|
}
|
|
|
|
const requestId = uuidv4();
|
|
logger.info(`收到实例健康检查请求, instanceId: ${instanceId}, bridgeId: ${targetBridgeId}`);
|
|
|
|
const result = await websocketServer.sendInstanceCheckToBridge(
|
|
targetBridgeId,
|
|
'single',
|
|
instanceId,
|
|
requestId
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.data
|
|
});
|
|
} catch (error) {
|
|
logger.error('处理实例健康检查失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
router.post('/instances/health-check-offline', authMiddleware, async (req, res) => {
|
|
try {
|
|
const bridges = bridgeManager.getAllBridges();
|
|
const results = [];
|
|
|
|
for (const bridge of bridges) {
|
|
const requestId = uuidv4();
|
|
logger.info(`收到离线实例检查请求, bridgeId: ${bridge.id}`);
|
|
|
|
try {
|
|
const result = await websocketServer.sendInstanceCheckToBridge(
|
|
bridge.id,
|
|
'offline',
|
|
null,
|
|
requestId
|
|
);
|
|
results.push({
|
|
bridgeId: bridge.id,
|
|
success: true,
|
|
data: result.data
|
|
});
|
|
} catch (error) {
|
|
results.push({
|
|
bridgeId: bridge.id,
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: results
|
|
});
|
|
} catch (error) {
|
|
logger.error('处理离线实例检查失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
router.post('/instances/health-check-all', authMiddleware, async (req, res) => {
|
|
try {
|
|
const bridges = bridgeManager.getAllBridges();
|
|
const results = [];
|
|
|
|
for (const bridge of bridges) {
|
|
const requestId = uuidv4();
|
|
logger.info(`收到全部实例检查请求, bridgeId: ${bridge.id}`);
|
|
|
|
try {
|
|
const result = await websocketServer.sendInstanceCheckToBridge(
|
|
bridge.id,
|
|
'all',
|
|
null,
|
|
requestId
|
|
);
|
|
results.push({
|
|
bridgeId: bridge.id,
|
|
success: true,
|
|
data: result.data
|
|
});
|
|
} catch (error) {
|
|
results.push({
|
|
bridgeId: bridge.id,
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: results
|
|
});
|
|
} catch (error) {
|
|
logger.error('处理全部实例检查失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
export default router;
|