123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- const express = require('express');
- const router = express.Router();
- const ClashParser = require('../core/clashParser');
- const SpeedTester = require('../core/speedTester');
- const TelegramNotifier = require('../core/notifier');
- const SubscriptionManager = require('../core/subscriptionManager');
- const { Node, TestResult, Notification } = require('../models');
- const logger = require('../utils/logger');
- // 节点管理API
- router.get('/nodes', async (req, res) => {
- try {
- const { page = 1, limit = 20, group, status, search } = req.query;
- const offset = (page - 1) * limit;
-
- const where = {};
- if (group) where.group = group;
- if (status) where.status = status;
- if (search) {
- where[require('sequelize').Op.or] = [
- { name: { [require('sequelize').Op.like]: `%${search}%` } },
- { server: { [require('sequelize').Op.like]: `%${search}%` } }
- ];
- }
- const { count, rows } = await Node.findAndCountAll({
- where,
- limit: parseInt(limit),
- offset: parseInt(offset),
- order: [['createdAt', 'DESC']],
- include: [{
- model: TestResult,
- as: 'testResults',
- limit: 1,
- order: [['testTime', 'DESC']]
- }]
- });
- res.json({
- success: true,
- data: {
- nodes: rows,
- pagination: {
- page: parseInt(page),
- limit: parseInt(limit),
- total: count,
- pages: Math.ceil(count / limit)
- }
- }
- });
- } catch (error) {
- logger.error('获取节点列表失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.get('/nodes/:id', async (req, res) => {
- try {
- const node = await Node.findByPk(req.params.id, {
- include: [{
- model: TestResult,
- as: 'testResults',
- limit: 50,
- order: [['testTime', 'DESC']]
- }]
- });
- if (!node) {
- return res.status(404).json({ success: false, error: '节点不存在' });
- }
- res.json({ success: true, data: node });
- } catch (error) {
- logger.error('获取节点详情失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.post('/nodes', async (req, res) => {
- try {
- const nodeData = req.body;
- const node = await Node.create(nodeData);
-
- logger.info('创建节点成功', { nodeId: node.id, nodeName: node.name });
- res.json({ success: true, data: node });
- } catch (error) {
- logger.error('创建节点失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.put('/nodes/:id', async (req, res) => {
- try {
- const node = await Node.findByPk(req.params.id);
- if (!node) {
- return res.status(404).json({ success: false, error: '节点不存在' });
- }
- await node.update(req.body);
-
- logger.info('更新节点成功', { nodeId: node.id, nodeName: node.name });
- res.json({ success: true, data: node });
- } catch (error) {
- logger.error('更新节点失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.delete('/nodes/:id', async (req, res) => {
- try {
- const node = await Node.findByPk(req.params.id);
- if (!node) {
- return res.status(404).json({ success: false, error: '节点不存在' });
- }
- await node.destroy();
-
- logger.info('删除节点成功', { nodeId: node.id, nodeName: node.name });
- res.json({ success: true });
- } catch (error) {
- logger.error('删除节点失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 导入Clash配置
- router.post('/import/clash', async (req, res) => {
- try {
- const { configPath, configUrl } = req.body;
-
- if (!configPath && !configUrl) {
- return res.status(400).json({ success: false, error: '请提供配置文件路径或URL' });
- }
- const parser = new ClashParser(configPath || './temp_config.yaml');
- let nodes;
- if (configUrl) {
- nodes = await parser.importFromUrl(configUrl);
- } else {
- nodes = await parser.parseConfig();
- }
- // 批量创建节点
- const createdNodes = [];
- for (const nodeData of nodes) {
- try {
- const existingNode = await Node.findOne({
- where: {
- name: nodeData.name,
- server: nodeData.server,
- port: nodeData.port
- }
- });
- if (!existingNode) {
- const node = await Node.create(nodeData);
- createdNodes.push(node);
- }
- } catch (error) {
- logger.warn('创建节点失败', { nodeData, error: error.message });
- }
- }
- logger.info('导入Clash配置成功', { imported: createdNodes.length });
- res.json({
- success: true,
- data: {
- imported: createdNodes.length,
- nodes: createdNodes
- }
- });
- } catch (error) {
- logger.error('导入Clash配置失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 订阅管理API
- router.post('/subscription/update', async (req, res) => {
- try {
- const subscriptionManager = new SubscriptionManager();
- const result = await subscriptionManager.manualUpdate();
-
- if (result.success) {
- res.json({ success: true, data: result.data });
- } else {
- res.status(400).json({ success: false, error: result.error });
- }
- } catch (error) {
- logger.error('手动更新订阅失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.get('/subscription/status', async (req, res) => {
- try {
- const subscriptionManager = new SubscriptionManager();
- const status = subscriptionManager.getStatus();
- res.json({ success: true, data: status });
- } catch (error) {
- logger.error('获取订阅状态失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 测试相关API
- router.post('/test/manual', async (req, res) => {
- try {
- const { nodeIds } = req.body;
-
- let nodes;
- if (nodeIds && nodeIds.length > 0) {
- nodes = await Node.findAll({
- where: { id: nodeIds }
- });
- } else {
- nodes = await Node.findAll({
- where: { isActive: true }
- });
- }
- if (nodes.length === 0) {
- return res.status(400).json({ success: false, error: '没有找到要测试的节点' });
- }
- const speedTester = new SpeedTester();
- const results = await speedTester.testNodes(nodes);
- // 处理测试结果并更新节点状态
- const scheduler = new (require('../core/scheduler'))();
- await scheduler.processTestResults(nodes, results);
- // 发送测试总结报告
- await scheduler.sendTestSummary(nodes, results);
- logger.info('手动测试完成', { testedNodes: nodes.length });
- res.json({ success: true, data: { results, testedNodes: nodes.length } });
- } catch (error) {
- logger.error('手动测试失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // Ping测试API - 参考Electron应用实现
- router.post('/test/ping', async (req, res) => {
- try {
- const { host, port, attempts = 3, timeout = 2000 } = req.body;
-
- if (!host || !port) {
- return res.status(400).json({ success: false, error: '请提供host和port参数' });
- }
- const speedTester = new SpeedTester();
- const result = await speedTester.pingHosts(host, port, attempts, timeout);
- logger.info('Ping测试完成', { host, port, success: result.success });
- res.json({ success: true, data: result });
- } catch (error) {
- logger.error('Ping测试失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 批量Ping测试API - 参考Electron应用实现
- router.post('/test/ping/batch', async (req, res) => {
- try {
- const { hosts, attempts = 3, timeout = 2000 } = req.body;
-
- if (!hosts || !Array.isArray(hosts) || hosts.length === 0) {
- return res.status(400).json({ success: false, error: '请提供hosts数组参数' });
- }
- const speedTester = new SpeedTester();
- const result = await speedTester.batchPingHosts(hosts);
- logger.info('批量Ping测试完成', { testedHosts: hosts.length });
- res.json({
- success: true,
- data: result
- });
- } catch (error) {
- logger.error('批量Ping测试失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.get('/test/results', async (req, res) => {
- try {
- const { page = 1, limit = 50, nodeId, nodeName, isSuccess, startDate, endDate } = req.query;
- const offset = (page - 1) * limit;
-
- const where = {};
- if (nodeId) where.nodeId = nodeId;
- if (isSuccess !== undefined) where.isSuccess = isSuccess === 'true';
- if (startDate || endDate) {
- where.testTime = {};
- if (startDate) where.testTime[require('sequelize').Op.gte] = new Date(startDate);
- if (endDate) where.testTime[require('sequelize').Op.lte] = new Date(endDate);
- }
- const includeOptions = [{
- model: Node,
- as: 'node',
- attributes: ['name', 'type', 'group', 'averageLatency']
- }];
- // 如果提供了节点名称,添加节点名称过滤条件
- if (nodeName) {
- includeOptions[0].where = {
- name: { [require('sequelize').Op.like]: `%${nodeName}%` }
- };
- }
- const { count, rows } = await TestResult.findAndCountAll({
- where,
- limit: parseInt(limit),
- offset: parseInt(offset),
- order: [['testTime', 'DESC']],
- include: includeOptions
- });
- res.json({
- success: true,
- data: {
- results: rows,
- pagination: {
- page: parseInt(page),
- limit: parseInt(limit),
- total: count,
- pages: Math.ceil(count / limit)
- }
- }
- });
- } catch (error) {
- logger.error('获取测试结果失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 通知相关API
- router.get('/notifications', async (req, res) => {
- try {
- const { page = 1, limit = 20, type, isSent } = req.query;
- const offset = (page - 1) * limit;
-
- const where = {};
- if (type) where.type = type;
- if (isSent !== undefined) where.isSent = isSent === 'true';
- const { count, rows } = await Notification.findAndCountAll({
- where,
- limit: parseInt(limit),
- offset: parseInt(offset),
- order: [['createdAt', 'DESC']],
- include: [{
- model: Node,
- as: 'node',
- attributes: ['name', 'group']
- }]
- });
- res.json({
- success: true,
- data: {
- notifications: rows,
- pagination: {
- page: parseInt(page),
- limit: parseInt(limit),
- total: count,
- pages: Math.ceil(count / limit)
- }
- }
- });
- } catch (error) {
- logger.error('获取通知列表失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- router.post('/notifications/test', async (req, res) => {
- try {
- const notifier = new TelegramNotifier();
- const result = await notifier.testConnection();
-
- if (result.success) {
- res.json({ success: true, data: result });
- } else {
- res.status(400).json({ success: false, error: result.error });
- }
- } catch (error) {
- logger.error('测试通知连接失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 统计信息API
- router.get('/stats', async (req, res) => {
- try {
- const [
- totalNodes,
- onlineNodes,
- offlineNodes,
- totalTests,
- successfulTests,
- totalNotifications
- ] = await Promise.all([
- Node.count(),
- Node.count({ where: { status: 'online' } }),
- Node.count({ where: { status: 'offline' } }),
- TestResult.count(),
- TestResult.count({ where: { isSuccess: true } }),
- Notification.count()
- ]);
- // 获取最近24小时的测试统计
- const yesterday = new Date();
- yesterday.setDate(yesterday.getDate() - 1);
-
- const recentTests = await TestResult.count({
- where: {
- testTime: {
- [require('sequelize').Op.gte]: yesterday
- }
- }
- });
- const recentSuccessfulTests = await TestResult.count({
- where: {
- testTime: {
- [require('sequelize').Op.gte]: yesterday
- },
- isSuccess: true
- }
- });
- const stats = {
- nodes: {
- total: totalNodes,
- online: onlineNodes,
- offline: offlineNodes,
- onlineRate: totalNodes > 0 ? Math.round((onlineNodes / totalNodes) * 100) : 0
- },
- tests: {
- total: totalTests,
- successful: successfulTests,
- successRate: totalTests > 0 ? Math.round((successfulTests / totalTests) * 100) : 0,
- recent24h: recentTests,
- recent24hSuccess: recentSuccessfulTests,
- recent24hSuccessRate: recentTests > 0 ? Math.round((recentSuccessfulTests / recentTests) * 100) : 0
- },
- notifications: {
- total: totalNotifications
- }
- };
- res.json({ success: true, data: stats });
- } catch (error) {
- logger.error('获取统计信息失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- // 系统状态API
- router.get('/status', async (req, res) => {
- try {
- const scheduler = req.app.get('scheduler');
- const status = scheduler ? scheduler.getStatus() : { error: '调度器未初始化' };
-
- res.json({ success: true, data: status });
- } catch (error) {
- logger.error('获取系统状态失败', { error: error.message });
- res.status(500).json({ success: false, error: error.message });
- }
- });
- module.exports = router;
|