routes.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. const express = require('express');
  2. const router = express.Router();
  3. const ClashParser = require('../core/clashParser');
  4. const SpeedTester = require('../core/speedTester');
  5. const TelegramNotifier = require('../core/notifier');
  6. const SubscriptionManager = require('../core/subscriptionManager');
  7. const { Node, TestResult, Notification } = require('../models');
  8. const logger = require('../utils/logger');
  9. // 节点管理API
  10. router.get('/nodes', async (req, res) => {
  11. try {
  12. const { page = 1, limit = 20, group, status, search } = req.query;
  13. const offset = (page - 1) * limit;
  14. const where = {};
  15. if (group) where.group = group;
  16. if (status) where.status = status;
  17. if (search) {
  18. where[require('sequelize').Op.or] = [
  19. { name: { [require('sequelize').Op.like]: `%${search}%` } },
  20. { server: { [require('sequelize').Op.like]: `%${search}%` } }
  21. ];
  22. }
  23. const { count, rows } = await Node.findAndCountAll({
  24. where,
  25. limit: parseInt(limit),
  26. offset: parseInt(offset),
  27. order: [['createdAt', 'DESC']],
  28. include: [{
  29. model: TestResult,
  30. as: 'testResults',
  31. limit: 1,
  32. order: [['testTime', 'DESC']]
  33. }]
  34. });
  35. res.json({
  36. success: true,
  37. data: {
  38. nodes: rows,
  39. pagination: {
  40. page: parseInt(page),
  41. limit: parseInt(limit),
  42. total: count,
  43. pages: Math.ceil(count / limit)
  44. }
  45. }
  46. });
  47. } catch (error) {
  48. logger.error('获取节点列表失败', { error: error.message });
  49. res.status(500).json({ success: false, error: error.message });
  50. }
  51. });
  52. router.get('/nodes/:id', async (req, res) => {
  53. try {
  54. const node = await Node.findByPk(req.params.id, {
  55. include: [{
  56. model: TestResult,
  57. as: 'testResults',
  58. limit: 50,
  59. order: [['testTime', 'DESC']]
  60. }]
  61. });
  62. if (!node) {
  63. return res.status(404).json({ success: false, error: '节点不存在' });
  64. }
  65. res.json({ success: true, data: node });
  66. } catch (error) {
  67. logger.error('获取节点详情失败', { error: error.message });
  68. res.status(500).json({ success: false, error: error.message });
  69. }
  70. });
  71. router.post('/nodes', async (req, res) => {
  72. try {
  73. const nodeData = req.body;
  74. const node = await Node.create(nodeData);
  75. logger.info('创建节点成功', { nodeId: node.id, nodeName: node.name });
  76. res.json({ success: true, data: node });
  77. } catch (error) {
  78. logger.error('创建节点失败', { error: error.message });
  79. res.status(500).json({ success: false, error: error.message });
  80. }
  81. });
  82. router.put('/nodes/:id', async (req, res) => {
  83. try {
  84. const node = await Node.findByPk(req.params.id);
  85. if (!node) {
  86. return res.status(404).json({ success: false, error: '节点不存在' });
  87. }
  88. await node.update(req.body);
  89. logger.info('更新节点成功', { nodeId: node.id, nodeName: node.name });
  90. res.json({ success: true, data: node });
  91. } catch (error) {
  92. logger.error('更新节点失败', { error: error.message });
  93. res.status(500).json({ success: false, error: error.message });
  94. }
  95. });
  96. router.delete('/nodes/:id', async (req, res) => {
  97. try {
  98. const node = await Node.findByPk(req.params.id);
  99. if (!node) {
  100. return res.status(404).json({ success: false, error: '节点不存在' });
  101. }
  102. await node.destroy();
  103. logger.info('删除节点成功', { nodeId: node.id, nodeName: node.name });
  104. res.json({ success: true });
  105. } catch (error) {
  106. logger.error('删除节点失败', { error: error.message });
  107. res.status(500).json({ success: false, error: error.message });
  108. }
  109. });
  110. // 导入Clash配置
  111. router.post('/import/clash', async (req, res) => {
  112. try {
  113. const { configPath, configUrl } = req.body;
  114. if (!configPath && !configUrl) {
  115. return res.status(400).json({ success: false, error: '请提供配置文件路径或URL' });
  116. }
  117. const parser = new ClashParser(configPath || './temp_config.yaml');
  118. let nodes;
  119. if (configUrl) {
  120. nodes = await parser.importFromUrl(configUrl);
  121. } else {
  122. nodes = await parser.parseConfig();
  123. }
  124. // 批量创建节点
  125. const createdNodes = [];
  126. for (const nodeData of nodes) {
  127. try {
  128. const existingNode = await Node.findOne({
  129. where: {
  130. name: nodeData.name,
  131. server: nodeData.server,
  132. port: nodeData.port
  133. }
  134. });
  135. if (!existingNode) {
  136. const node = await Node.create(nodeData);
  137. createdNodes.push(node);
  138. }
  139. } catch (error) {
  140. logger.warn('创建节点失败', { nodeData, error: error.message });
  141. }
  142. }
  143. logger.info('导入Clash配置成功', { imported: createdNodes.length });
  144. res.json({
  145. success: true,
  146. data: {
  147. imported: createdNodes.length,
  148. nodes: createdNodes
  149. }
  150. });
  151. } catch (error) {
  152. logger.error('导入Clash配置失败', { error: error.message });
  153. res.status(500).json({ success: false, error: error.message });
  154. }
  155. });
  156. // 订阅管理API
  157. router.post('/subscription/update', async (req, res) => {
  158. try {
  159. const subscriptionManager = new SubscriptionManager();
  160. const result = await subscriptionManager.manualUpdate();
  161. if (result.success) {
  162. res.json({ success: true, data: result.data });
  163. } else {
  164. res.status(400).json({ success: false, error: result.error });
  165. }
  166. } catch (error) {
  167. logger.error('手动更新订阅失败', { error: error.message });
  168. res.status(500).json({ success: false, error: error.message });
  169. }
  170. });
  171. router.get('/subscription/status', async (req, res) => {
  172. try {
  173. const subscriptionManager = new SubscriptionManager();
  174. const status = subscriptionManager.getStatus();
  175. res.json({ success: true, data: status });
  176. } catch (error) {
  177. logger.error('获取订阅状态失败', { error: error.message });
  178. res.status(500).json({ success: false, error: error.message });
  179. }
  180. });
  181. // 测试相关API
  182. router.post('/test/manual', async (req, res) => {
  183. try {
  184. const { nodeIds } = req.body;
  185. let nodes;
  186. if (nodeIds && nodeIds.length > 0) {
  187. nodes = await Node.findAll({
  188. where: { id: nodeIds }
  189. });
  190. } else {
  191. nodes = await Node.findAll({
  192. where: { isActive: true }
  193. });
  194. }
  195. if (nodes.length === 0) {
  196. return res.status(400).json({ success: false, error: '没有找到要测试的节点' });
  197. }
  198. const speedTester = new SpeedTester();
  199. const results = await speedTester.testNodes(nodes);
  200. // 处理测试结果并更新节点状态
  201. const scheduler = new (require('../core/scheduler'))();
  202. await scheduler.processTestResults(nodes, results);
  203. // 发送测试总结报告
  204. await scheduler.sendTestSummary(nodes, results);
  205. logger.info('手动测试完成', { testedNodes: nodes.length });
  206. res.json({ success: true, data: { results, testedNodes: nodes.length } });
  207. } catch (error) {
  208. logger.error('手动测试失败', { error: error.message });
  209. res.status(500).json({ success: false, error: error.message });
  210. }
  211. });
  212. // Ping测试API - 参考Electron应用实现
  213. router.post('/test/ping', async (req, res) => {
  214. try {
  215. const { host, port, attempts = 3, timeout = 2000 } = req.body;
  216. if (!host || !port) {
  217. return res.status(400).json({ success: false, error: '请提供host和port参数' });
  218. }
  219. const speedTester = new SpeedTester();
  220. const result = await speedTester.pingHosts(host, port, attempts, timeout);
  221. logger.info('Ping测试完成', { host, port, success: result.success });
  222. res.json({ success: true, data: result });
  223. } catch (error) {
  224. logger.error('Ping测试失败', { error: error.message });
  225. res.status(500).json({ success: false, error: error.message });
  226. }
  227. });
  228. // 批量Ping测试API - 参考Electron应用实现
  229. router.post('/test/ping/batch', async (req, res) => {
  230. try {
  231. const { hosts, attempts = 3, timeout = 2000 } = req.body;
  232. if (!hosts || !Array.isArray(hosts) || hosts.length === 0) {
  233. return res.status(400).json({ success: false, error: '请提供hosts数组参数' });
  234. }
  235. const speedTester = new SpeedTester();
  236. const result = await speedTester.batchPingHosts(hosts);
  237. logger.info('批量Ping测试完成', { testedHosts: hosts.length });
  238. res.json({
  239. success: true,
  240. data: result
  241. });
  242. } catch (error) {
  243. logger.error('批量Ping测试失败', { error: error.message });
  244. res.status(500).json({ success: false, error: error.message });
  245. }
  246. });
  247. router.get('/test/results', async (req, res) => {
  248. try {
  249. const { page = 1, limit = 50, nodeId, nodeName, isSuccess, startDate, endDate } = req.query;
  250. const offset = (page - 1) * limit;
  251. const where = {};
  252. if (nodeId) where.nodeId = nodeId;
  253. if (isSuccess !== undefined) where.isSuccess = isSuccess === 'true';
  254. if (startDate || endDate) {
  255. where.testTime = {};
  256. if (startDate) where.testTime[require('sequelize').Op.gte] = new Date(startDate);
  257. if (endDate) where.testTime[require('sequelize').Op.lte] = new Date(endDate);
  258. }
  259. const includeOptions = [{
  260. model: Node,
  261. as: 'node',
  262. attributes: ['name', 'type', 'group', 'averageLatency']
  263. }];
  264. // 如果提供了节点名称,添加节点名称过滤条件
  265. if (nodeName) {
  266. includeOptions[0].where = {
  267. name: { [require('sequelize').Op.like]: `%${nodeName}%` }
  268. };
  269. }
  270. const { count, rows } = await TestResult.findAndCountAll({
  271. where,
  272. limit: parseInt(limit),
  273. offset: parseInt(offset),
  274. order: [['testTime', 'DESC']],
  275. include: includeOptions
  276. });
  277. res.json({
  278. success: true,
  279. data: {
  280. results: rows,
  281. pagination: {
  282. page: parseInt(page),
  283. limit: parseInt(limit),
  284. total: count,
  285. pages: Math.ceil(count / limit)
  286. }
  287. }
  288. });
  289. } catch (error) {
  290. logger.error('获取测试结果失败', { error: error.message });
  291. res.status(500).json({ success: false, error: error.message });
  292. }
  293. });
  294. // 通知相关API
  295. router.get('/notifications', async (req, res) => {
  296. try {
  297. const { page = 1, limit = 20, type, isSent } = req.query;
  298. const offset = (page - 1) * limit;
  299. const where = {};
  300. if (type) where.type = type;
  301. if (isSent !== undefined) where.isSent = isSent === 'true';
  302. const { count, rows } = await Notification.findAndCountAll({
  303. where,
  304. limit: parseInt(limit),
  305. offset: parseInt(offset),
  306. order: [['createdAt', 'DESC']],
  307. include: [{
  308. model: Node,
  309. as: 'node',
  310. attributes: ['name', 'group']
  311. }]
  312. });
  313. res.json({
  314. success: true,
  315. data: {
  316. notifications: rows,
  317. pagination: {
  318. page: parseInt(page),
  319. limit: parseInt(limit),
  320. total: count,
  321. pages: Math.ceil(count / limit)
  322. }
  323. }
  324. });
  325. } catch (error) {
  326. logger.error('获取通知列表失败', { error: error.message });
  327. res.status(500).json({ success: false, error: error.message });
  328. }
  329. });
  330. router.post('/notifications/test', async (req, res) => {
  331. try {
  332. const notifier = new TelegramNotifier();
  333. const result = await notifier.testConnection();
  334. if (result.success) {
  335. res.json({ success: true, data: result });
  336. } else {
  337. res.status(400).json({ success: false, error: result.error });
  338. }
  339. } catch (error) {
  340. logger.error('测试通知连接失败', { error: error.message });
  341. res.status(500).json({ success: false, error: error.message });
  342. }
  343. });
  344. // 统计信息API
  345. router.get('/stats', async (req, res) => {
  346. try {
  347. const [
  348. totalNodes,
  349. onlineNodes,
  350. offlineNodes,
  351. totalTests,
  352. successfulTests,
  353. totalNotifications
  354. ] = await Promise.all([
  355. Node.count(),
  356. Node.count({ where: { status: 'online' } }),
  357. Node.count({ where: { status: 'offline' } }),
  358. TestResult.count(),
  359. TestResult.count({ where: { isSuccess: true } }),
  360. Notification.count()
  361. ]);
  362. // 获取最近24小时的测试统计
  363. const yesterday = new Date();
  364. yesterday.setDate(yesterday.getDate() - 1);
  365. const recentTests = await TestResult.count({
  366. where: {
  367. testTime: {
  368. [require('sequelize').Op.gte]: yesterday
  369. }
  370. }
  371. });
  372. const recentSuccessfulTests = await TestResult.count({
  373. where: {
  374. testTime: {
  375. [require('sequelize').Op.gte]: yesterday
  376. },
  377. isSuccess: true
  378. }
  379. });
  380. const stats = {
  381. nodes: {
  382. total: totalNodes,
  383. online: onlineNodes,
  384. offline: offlineNodes,
  385. onlineRate: totalNodes > 0 ? Math.round((onlineNodes / totalNodes) * 100) : 0
  386. },
  387. tests: {
  388. total: totalTests,
  389. successful: successfulTests,
  390. successRate: totalTests > 0 ? Math.round((successfulTests / totalTests) * 100) : 0,
  391. recent24h: recentTests,
  392. recent24hSuccess: recentSuccessfulTests,
  393. recent24hSuccessRate: recentTests > 0 ? Math.round((recentSuccessfulTests / recentTests) * 100) : 0
  394. },
  395. notifications: {
  396. total: totalNotifications
  397. }
  398. };
  399. res.json({ success: true, data: stats });
  400. } catch (error) {
  401. logger.error('获取统计信息失败', { error: error.message });
  402. res.status(500).json({ success: false, error: error.message });
  403. }
  404. });
  405. // 系统状态API
  406. router.get('/status', async (req, res) => {
  407. try {
  408. const scheduler = req.app.get('scheduler');
  409. const status = scheduler ? scheduler.getStatus() : { error: '调度器未初始化' };
  410. res.json({ success: true, data: status });
  411. } catch (error) {
  412. logger.error('获取系统状态失败', { error: error.message });
  413. res.status(500).json({ success: false, error: error.message });
  414. }
  415. });
  416. module.exports = router;