index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. const express = require('express');
  2. const path = require('path');
  3. const { spawn } = require('child_process');
  4. const cors = require('cors');
  5. const app = express();
  6. const PORT = process.env.NODE_ENV === 'development' ? 8080 : 3000;
  7. const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
  8. const CLIENT_DIST_DIR = process.env.NODE_ENV === 'development' ? path.join(__dirname, '../client/dist') : path.join(__dirname, './public');
  9. function parseK6StdoutTable(text) {
  10. const keysToKeep = new Set([
  11. 'checks_total',
  12. 'checks_succeeded',
  13. 'checks_failed',
  14. 'browser_data_received',
  15. 'browser_data_sent',
  16. 'browser_http_req_duration',
  17. 'browser_http_req_failed',
  18. 'browser_web_vital_cls',
  19. 'browser_web_vital_fcp',
  20. 'browser_web_vital_ttfb',
  21. 'checks',
  22. 'data_received',
  23. 'data_sent',
  24. 'iteration_duration',
  25. 'iterations',
  26. 'vus',
  27. 'vus_max'
  28. ]);
  29. const lines = text.split('\n');
  30. const result = {};
  31. for (let line of lines) {
  32. line = line.replace(/^[✓×\s]+/, ''); // 去掉开头 ✓ × 空格
  33. if (!line.includes(':')) continue;
  34. let [keyPart, valPart] = line.split(':');
  35. if (!valPart) continue;
  36. const key = keyPart.trim().replace(/\.+$/, '');
  37. if (!keysToKeep.has(key)) continue;
  38. valPart = valPart.trim();
  39. if (/(\w+\()?=/.test(valPart)) {
  40. const stats = {};
  41. const regex = /([^\s=]+)=([^\s]+)/g;
  42. let match;
  43. while ((match = regex.exec(valPart)) !== null) {
  44. stats[match[1]] = match[2];
  45. }
  46. result[key] = stats;
  47. } else {
  48. const parts = valPart.split(/\s+/);
  49. if (parts.length === 4 && parts[2].endsWith('/s')) {
  50. result[key] = {
  51. value: parts[0] + ' ' + parts[1],
  52. rate: parts[2] + ' ' + parts[3],
  53. };
  54. } else if (parts.length === 2 && parts[1].endsWith('/s')) {
  55. result[key] = {
  56. value: parts[0],
  57. rate: parts[1],
  58. };
  59. } else {
  60. result[key] = valPart;
  61. }
  62. }
  63. }
  64. return result;
  65. }
  66. // ====== 中间件 ======
  67. app.use(cors());
  68. // 静态资源
  69. app.use('/static', express.static(SCREENSHOTS_DIR));
  70. app.use(express.static(CLIENT_DIST_DIR));
  71. // ====== API 路由 ======
  72. app.get('/api/k6', async (req, res) => {
  73. const targetUrl = req.query.targetUrl;
  74. const timeStamp = req.query.timeStamp;
  75. console.log("targetUrl:", targetUrl);
  76. console.log("timeStamp:", timeStamp);
  77. if (!targetUrl || !timeStamp) {
  78. return res.status(400).json({ error: '缺少 targetUrl 或 timeStamp 参数' });
  79. }
  80. // 运行 k6 脚本
  81. const k6Process = spawn('k6', ['run', '--no-color=false', '--env', 'targetUrl=' + targetUrl, '--env', 'timeStamp=' + timeStamp, './script-browser.js']);
  82. let outputBuffer = '';
  83. k6Process.stdout.on('data', (chunk) => {
  84. outputBuffer += chunk.toString();
  85. console.log(chunk.toString())
  86. });
  87. k6Process.on('close', () => {
  88. const parsed = parseK6StdoutTable(outputBuffer);
  89. res.json(parsed); // 返回结构化结果给前端
  90. });
  91. });
  92. // ====== history 路由兼容(放在所有 API 路由之后) ======
  93. app.get('*', (req, res) => {
  94. res.sendFile(path.join(CLIENT_DIST_DIR, 'index.html'));
  95. });
  96. // ====== 启动服务 ======
  97. app.listen(PORT, () => {
  98. console.log(`Express server running at http://localhost:${PORT}`);
  99. });