routes.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. router.get('/test/results', async (req, res) => {
  213. try {
  214. const { page = 1, limit = 50, nodeId, isSuccess, startDate, endDate } = req.query;
  215. const offset = (page - 1) * limit;
  216. const where = {};
  217. if (nodeId) where.nodeId = nodeId;
  218. if (isSuccess !== undefined) where.isSuccess = isSuccess === 'true';
  219. if (startDate || endDate) {
  220. where.testTime = {};
  221. if (startDate) where.testTime[require('sequelize').Op.gte] = new Date(startDate);
  222. if (endDate) where.testTime[require('sequelize').Op.lte] = new Date(endDate);
  223. }
  224. const { count, rows } = await TestResult.findAndCountAll({
  225. where,
  226. limit: parseInt(limit),
  227. offset: parseInt(offset),
  228. order: [['testTime', 'DESC']],
  229. include: [{
  230. model: Node,
  231. as: 'node',
  232. attributes: ['name', 'type', 'group']
  233. }]
  234. });
  235. res.json({
  236. success: true,
  237. data: {
  238. results: rows,
  239. pagination: {
  240. page: parseInt(page),
  241. limit: parseInt(limit),
  242. total: count,
  243. pages: Math.ceil(count / limit)
  244. }
  245. }
  246. });
  247. } catch (error) {
  248. logger.error('获取测试结果失败', { error: error.message });
  249. res.status(500).json({ success: false, error: error.message });
  250. }
  251. });
  252. // 通知相关API
  253. router.get('/notifications', async (req, res) => {
  254. try {
  255. const { page = 1, limit = 20, type, isSent } = req.query;
  256. const offset = (page - 1) * limit;
  257. const where = {};
  258. if (type) where.type = type;
  259. if (isSent !== undefined) where.isSent = isSent === 'true';
  260. const { count, rows } = await Notification.findAndCountAll({
  261. where,
  262. limit: parseInt(limit),
  263. offset: parseInt(offset),
  264. order: [['createdAt', 'DESC']],
  265. include: [{
  266. model: Node,
  267. as: 'node',
  268. attributes: ['name', 'group']
  269. }]
  270. });
  271. res.json({
  272. success: true,
  273. data: {
  274. notifications: rows,
  275. pagination: {
  276. page: parseInt(page),
  277. limit: parseInt(limit),
  278. total: count,
  279. pages: Math.ceil(count / limit)
  280. }
  281. }
  282. });
  283. } catch (error) {
  284. logger.error('获取通知列表失败', { error: error.message });
  285. res.status(500).json({ success: false, error: error.message });
  286. }
  287. });
  288. router.post('/notifications/test', async (req, res) => {
  289. try {
  290. const notifier = new TelegramNotifier();
  291. const result = await notifier.testConnection();
  292. if (result.success) {
  293. res.json({ success: true, data: result });
  294. } else {
  295. res.status(400).json({ success: false, error: result.error });
  296. }
  297. } catch (error) {
  298. logger.error('测试通知连接失败', { error: error.message });
  299. res.status(500).json({ success: false, error: error.message });
  300. }
  301. });
  302. // 统计信息API
  303. router.get('/stats', async (req, res) => {
  304. try {
  305. const [
  306. totalNodes,
  307. onlineNodes,
  308. offlineNodes,
  309. totalTests,
  310. successfulTests,
  311. totalNotifications
  312. ] = await Promise.all([
  313. Node.count(),
  314. Node.count({ where: { status: 'online' } }),
  315. Node.count({ where: { status: 'offline' } }),
  316. TestResult.count(),
  317. TestResult.count({ where: { isSuccess: true } }),
  318. Notification.count()
  319. ]);
  320. // 获取最近24小时的测试统计
  321. const yesterday = new Date();
  322. yesterday.setDate(yesterday.getDate() - 1);
  323. const recentTests = await TestResult.count({
  324. where: {
  325. testTime: {
  326. [require('sequelize').Op.gte]: yesterday
  327. }
  328. }
  329. });
  330. const recentSuccessfulTests = await TestResult.count({
  331. where: {
  332. testTime: {
  333. [require('sequelize').Op.gte]: yesterday
  334. },
  335. isSuccess: true
  336. }
  337. });
  338. const stats = {
  339. nodes: {
  340. total: totalNodes,
  341. online: onlineNodes,
  342. offline: offlineNodes,
  343. onlineRate: totalNodes > 0 ? Math.round((onlineNodes / totalNodes) * 100) : 0
  344. },
  345. tests: {
  346. total: totalTests,
  347. successful: successfulTests,
  348. successRate: totalTests > 0 ? Math.round((successfulTests / totalTests) * 100) : 0,
  349. recent24h: recentTests,
  350. recent24hSuccess: recentSuccessfulTests,
  351. recent24hSuccessRate: recentTests > 0 ? Math.round((recentSuccessfulTests / recentTests) * 100) : 0
  352. },
  353. notifications: {
  354. total: totalNotifications
  355. }
  356. };
  357. res.json({ success: true, data: stats });
  358. } catch (error) {
  359. logger.error('获取统计信息失败', { error: error.message });
  360. res.status(500).json({ success: false, error: error.message });
  361. }
  362. });
  363. // 系统状态API
  364. router.get('/status', async (req, res) => {
  365. try {
  366. const scheduler = req.app.get('scheduler');
  367. const status = scheduler ? scheduler.getStatus() : { error: '调度器未初始化' };
  368. res.json({ success: true, data: status });
  369. } catch (error) {
  370. logger.error('获取系统状态失败', { error: error.message });
  371. res.status(500).json({ success: false, error: error.message });
  372. }
  373. });
  374. module.exports = router;