index.js 3.2 KB

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