mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
357 lines
13 KiB
JavaScript
357 lines
13 KiB
JavaScript
/**
|
||
* @fileoverview Admin API 路由模块
|
||
* @description 提供管理接口,包括系统状态、配置管理、适配器元数据等
|
||
*/
|
||
|
||
import { sendJson, sendApiError } from './respond.js';
|
||
import { ERROR_CODES } from '../errors.js';
|
||
import { logger } from '../../utils/logger.js';
|
||
import {
|
||
getSystemStatus,
|
||
getDataFolders,
|
||
deleteDataFolders,
|
||
clearTempFiles
|
||
} from '../../utils/systemInfo.js';
|
||
import {
|
||
getServerConfig,
|
||
saveServerConfig,
|
||
getBrowserConfig,
|
||
saveBrowserConfig,
|
||
getQueueConfig,
|
||
saveQueueConfig,
|
||
getInstancesConfig,
|
||
saveInstancesConfig,
|
||
getAdaptersConfig,
|
||
saveAdaptersConfig,
|
||
getPoolConfig,
|
||
savePoolConfig
|
||
} from '../../utils/configManager.js';
|
||
import {
|
||
validateServerConfig,
|
||
validateBrowserConfig,
|
||
validateInstancesConfig,
|
||
validatePoolConfig,
|
||
validateAdaptersConfig
|
||
} from '../../utils/configValidator.js';
|
||
import { registry } from '../../backend/registry.js';
|
||
|
||
/**
|
||
* 读取请求体
|
||
* @param {import('http').IncomingMessage} req
|
||
* @returns {Promise<object>}
|
||
*/
|
||
async function readBody(req) {
|
||
const chunks = [];
|
||
for await (const chunk of req) {
|
||
chunks.push(chunk);
|
||
}
|
||
const body = Buffer.concat(chunks).toString();
|
||
return body ? JSON.parse(body) : {};
|
||
}
|
||
|
||
/**
|
||
* 创建 Admin 路由处理器
|
||
* @param {object} context - 路由上下文
|
||
* @param {object} context.config - 完整配置对象
|
||
* @param {object} context.queueManager - 队列管理器
|
||
* @param {string} context.tempDir - 临时目录
|
||
* @returns {Function} Admin 路由处理函数
|
||
*/
|
||
export function createAdminRouter(context) {
|
||
const { config, queueManager, tempDir } = context;
|
||
|
||
/**
|
||
* Admin 路由处理函数
|
||
* @param {import('http').IncomingMessage} req
|
||
* @param {import('http').ServerResponse} res
|
||
* @param {string} pathname - 去除 /admin 前缀后的路径
|
||
*/
|
||
return async function handleAdminRequest(req, res, pathname) {
|
||
const method = req.method;
|
||
|
||
try {
|
||
// ==================== 系统管理 ====================
|
||
|
||
// GET /admin/status - 系统状态
|
||
if (method === 'GET' && pathname === '/status') {
|
||
const status = getSystemStatus();
|
||
sendJson(res, 200, status);
|
||
return;
|
||
}
|
||
|
||
// POST /admin/restart - 重启服务(使用子进程分离)
|
||
if (method === 'POST' && pathname === '/restart') {
|
||
const { spawn } = await import('child_process');
|
||
|
||
sendJson(res, 200, { success: true, message: '服务正在重启...' });
|
||
logger.info('管理器', '收到重启请求,将在 1 秒后重启');
|
||
|
||
setTimeout(() => {
|
||
// 启动新进程(完全独立)
|
||
const child = spawn(process.execPath, process.argv.slice(1), {
|
||
cwd: process.cwd(),
|
||
detached: true,
|
||
stdio: 'ignore',
|
||
env: process.env
|
||
});
|
||
child.unref();
|
||
|
||
// 退出当前进程
|
||
setTimeout(() => process.exit(0), 500);
|
||
}, 1000);
|
||
return;
|
||
}
|
||
|
||
// POST /admin/stop - 停止服务
|
||
if (method === 'POST' && pathname === '/stop') {
|
||
sendJson(res, 200, { success: true, message: '服务正在停止...' });
|
||
logger.info('管理器', '收到停止请求,将在 1 秒后退出');
|
||
|
||
setTimeout(() => process.exit(0), 1000);
|
||
return;
|
||
}
|
||
|
||
// ==================== 缓存与数据管理 ====================
|
||
|
||
// POST /admin/cache/clear - 清理缓存
|
||
if (method === 'POST' && pathname === '/cache/clear') {
|
||
const result = clearTempFiles(tempDir);
|
||
sendJson(res, 200, { success: true, cleaned: result.cleaned });
|
||
return;
|
||
}
|
||
|
||
// GET /admin/data-folders - 列出数据文件夹
|
||
if (method === 'GET' && pathname === '/data-folders') {
|
||
const workers = config.backend?.pool?.workers || [];
|
||
const folders = getDataFolders(workers);
|
||
sendJson(res, 200, folders);
|
||
return;
|
||
}
|
||
|
||
// POST /admin/data-folders/delete - 删除数据文件夹
|
||
if (method === 'POST' && pathname === '/data-folders/delete') {
|
||
const body = await readBody(req);
|
||
if (!body.folders || !Array.isArray(body.folders)) {
|
||
sendApiError(res, { code: ERROR_CODES.INVALID_REQUEST_BODY, message: '缺少 folders 数组' });
|
||
return;
|
||
}
|
||
const workers = config.backend?.pool?.workers || [];
|
||
const result = deleteDataFolders(body.folders, workers);
|
||
|
||
if (result.errors.length > 0) {
|
||
sendJson(res, 207, result); // 207 Multi-Status
|
||
} else {
|
||
sendJson(res, 200, result);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// ==================== 配置管理 ====================
|
||
|
||
// GET/POST /admin/config/server
|
||
if (pathname === '/config/server') {
|
||
if (method === 'GET') {
|
||
const serverConfig = getServerConfig();
|
||
const queueConfig = getQueueConfig();
|
||
sendJson(res, 200, {
|
||
...serverConfig,
|
||
queueBuffer: queueConfig.queueBuffer,
|
||
imageLimit: queueConfig.imageLimit
|
||
});
|
||
} else if (method === 'POST') {
|
||
const body = await readBody(req);
|
||
|
||
// 校验配置
|
||
const validation = validateServerConfig(body);
|
||
if (!validation.valid) {
|
||
sendApiError(res, {
|
||
code: ERROR_CODES.INVALID_REQUEST_BODY,
|
||
message: `配置校验失败: ${validation.errors.join('; ')}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 分别保存 server 和 queue 配置
|
||
saveServerConfig(body);
|
||
if (body.queueBuffer !== undefined || body.imageLimit !== undefined) {
|
||
saveQueueConfig(body);
|
||
}
|
||
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
|
||
} else {
|
||
res.writeHead(405);
|
||
res.end();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// GET/POST /admin/config/browser
|
||
if (pathname === '/config/browser') {
|
||
if (method === 'GET') {
|
||
sendJson(res, 200, getBrowserConfig());
|
||
} else if (method === 'POST') {
|
||
const body = await readBody(req);
|
||
|
||
// 校验配置
|
||
const validation = validateBrowserConfig(body);
|
||
if (!validation.valid) {
|
||
sendApiError(res, {
|
||
code: ERROR_CODES.INVALID_REQUEST_BODY,
|
||
message: `配置校验失败: ${validation.errors.join('; ')}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
saveBrowserConfig(body);
|
||
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
|
||
} else {
|
||
res.writeHead(405);
|
||
res.end();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// GET/POST /admin/config/instances (对应原设计的 workers)
|
||
if (pathname === '/config/instances' || pathname === '/config/workers') {
|
||
if (method === 'GET') {
|
||
sendJson(res, 200, getInstancesConfig());
|
||
} else if (method === 'POST') {
|
||
const body = await readBody(req);
|
||
|
||
// 校验配置(包括 Instance/Worker 名称唯一性)
|
||
const validation = validateInstancesConfig(body);
|
||
if (!validation.valid) {
|
||
sendApiError(res, {
|
||
code: ERROR_CODES.INVALID_REQUEST_BODY,
|
||
message: `配置校验失败: ${validation.errors.join('; ')}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
saveInstancesConfig(body);
|
||
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
|
||
} else {
|
||
res.writeHead(405);
|
||
res.end();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// GET/POST /admin/config/adapters
|
||
if (pathname === '/config/adapters') {
|
||
if (method === 'GET') {
|
||
sendJson(res, 200, getAdaptersConfig());
|
||
} else if (method === 'POST') {
|
||
const body = await readBody(req);
|
||
|
||
// 校验配置
|
||
const validation = validateAdaptersConfig(body);
|
||
if (!validation.valid) {
|
||
sendApiError(res, {
|
||
code: ERROR_CODES.INVALID_REQUEST_BODY,
|
||
message: `配置校验失败: ${validation.errors.join('; ')}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
saveAdaptersConfig(body);
|
||
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
|
||
} else {
|
||
res.writeHead(405);
|
||
res.end();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// GET/POST /admin/config/pool - 负载均衡和故障转移配置
|
||
if (pathname === '/config/pool') {
|
||
if (method === 'GET') {
|
||
sendJson(res, 200, getPoolConfig());
|
||
} else if (method === 'POST') {
|
||
const body = await readBody(req);
|
||
|
||
// 校验配置
|
||
const validation = validatePoolConfig(body);
|
||
if (!validation.valid) {
|
||
sendApiError(res, {
|
||
code: ERROR_CODES.INVALID_REQUEST_BODY,
|
||
message: `配置校验失败: ${validation.errors.join('; ')}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
savePoolConfig(body);
|
||
sendJson(res, 200, { success: true, message: '配置已保存,请重启服务生效' });
|
||
} else {
|
||
res.writeHead(405);
|
||
res.end();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// ==================== 元数据 ====================
|
||
|
||
// GET /admin/adapters - 获取适配器列表(含 configSchema)
|
||
if (method === 'GET' && pathname === '/adapters') {
|
||
const adapters = [];
|
||
const adapterIds = registry.getAdapterIds();
|
||
|
||
for (const id of adapterIds) {
|
||
const adapter = registry.getAdapter(id);
|
||
if (adapter) {
|
||
adapters.push({
|
||
id: adapter.id,
|
||
displayName: adapter.displayName || adapter.id,
|
||
modelCount: adapter.models?.length || 0,
|
||
models: (adapter.models || []).map(m => m.id),
|
||
configSchema: adapter.configSchema || []
|
||
});
|
||
}
|
||
}
|
||
|
||
sendJson(res, 200, adapters);
|
||
return;
|
||
}
|
||
|
||
// ==================== 统计与监控 ====================
|
||
|
||
// GET /admin/stats - 基本统计
|
||
if (method === 'GET' && pathname === '/stats') {
|
||
const instances = config.backend?.pool?.instances || [];
|
||
const workers = config.backend?.pool?.workers || [];
|
||
|
||
sendJson(res, 200, {
|
||
instances: instances.length,
|
||
workers: workers.length
|
||
});
|
||
return;
|
||
}
|
||
|
||
// GET /admin/queue - 任务队列状态
|
||
if (method === 'GET' && pathname === '/queue') {
|
||
const queueStatus = queueManager.getStatus();
|
||
const detailedStatus = queueManager.getDetailedStatus();
|
||
|
||
sendJson(res, 200, {
|
||
processing: queueStatus.processing,
|
||
waiting: queueStatus.queueLength,
|
||
total: queueStatus.total,
|
||
processingTasks: detailedStatus.processing,
|
||
waitingTasks: detailedStatus.waiting
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 404
|
||
res.writeHead(404);
|
||
res.end(JSON.stringify({ error: 'Not Found' }));
|
||
|
||
} catch (err) {
|
||
logger.error('管理器', `请求处理失败: ${err.message}`);
|
||
sendApiError(res, {
|
||
code: ERROR_CODES.INTERNAL_ERROR,
|
||
message: err.message
|
||
});
|
||
}
|
||
};
|
||
}
|