3v324v23 commited on
Commit
2324f78
·
1 Parent(s): 89253be

feat: Upgrade to interactive Web UI with SSE streaming

Browse files
Files changed (2) hide show
  1. README.md +1 -0
  2. index.js +153 -75
README.md CHANGED
@@ -19,6 +19,7 @@ short_description: 演示 Node.js 大文件流式读写、XML/JSON 处理及性
19
 
20
  ## 功能特性
21
 
 
22
  - **流式架构**:核心模块均采用流式处理,适用于高并发和大数据量场景。
23
  - **性能分析**:内置简单的性能监控工具,可直观看到各操作耗时。
24
  - **模块化设计**:功能拆分为独立模块,易于扩展和维护。
 
19
 
20
  ## 功能特性
21
 
22
+ - **交互式 Web 界面**:通过 Web 页面点击按钮运行演示,实时查看日志输出。
23
  - **流式架构**:核心模块均采用流式处理,适用于高并发和大数据量场景。
24
  - **性能分析**:内置简单的性能监控工具,可直观看到各操作耗时。
25
  - **模块化设计**:功能拆分为独立模块,易于扩展和维护。
index.js CHANGED
@@ -1,12 +1,11 @@
1
  const express = require('express');
2
  const path = require('path');
3
  const fs = require('fs');
 
4
  const LargeFileHandler = require('./src/largeFile');
5
  const xmlJsonHandler = require('./src/xmlJson');
6
  const performanceMonitor = require('./src/performance');
7
 
8
- const { exec } = require('child_process');
9
-
10
  const app = express();
11
  const PORT = 7860;
12
 
@@ -21,23 +20,8 @@ const LARGE_FILE_PATH = path.join(DATA_DIR, 'large_data.csv');
21
  const XML_FILE_PATH = path.join(DATA_DIR, 'data.xml');
22
  const LARGE_JSON_PATH = path.join(DATA_DIR, 'large_data.json');
23
 
24
- // Custom logger to capture output
25
- class StringLogger {
26
- constructor() {
27
- this.logs = [];
28
- }
29
- log(...args) {
30
- const msg = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ');
31
- this.logs.push(msg);
32
- }
33
- error(...args) {
34
- const msg = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ');
35
- this.logs.push(`ERROR: ${msg}`);
36
- }
37
- getOutput() {
38
- return this.logs.join('\n');
39
- }
40
- }
41
 
42
  async function runDemo() {
43
  console.log('=== Node.js 性能优化与大文件处理演示 ===\n');
@@ -47,7 +31,7 @@ async function runDemo() {
47
  console.log('--- 1. 大文件流式处理 ---');
48
  const largeFileHandler = new LargeFileHandler(LARGE_FILE_PATH);
49
 
50
- // Generate 100k lines (reduced for demo speed on web request)
51
  const lineCount = 100000;
52
  console.log(`正在生成测试数据 (${lineCount} 行)...`);
53
  await largeFileHandler.generateTestFile(lineCount);
@@ -58,7 +42,7 @@ async function runDemo() {
58
 
59
  console.log('\n--- 2. XML 处理 ---');
60
  // Generate XML
61
- const xmlContent = xmlJsonHandler.generateXml(1000); // Reduced size
62
  fs.writeFileSync(XML_FILE_PATH, xmlContent);
63
  console.log(`XML 文件已保存: ${XML_FILE_PATH}`);
64
 
@@ -69,7 +53,7 @@ async function runDemo() {
69
  console.log('\n--- 3. JSON 大文件流式处理 ---');
70
  // Generate large JSON
71
  console.log('正在生成 JSON 数据...');
72
- await xmlJsonHandler.generateLargeJson(LARGE_JSON_PATH, 10000); // Reduced size
73
 
74
  // Stream parse JSON
75
  console.log('开始流式解析 JSON...');
@@ -89,83 +73,177 @@ async function runDemo() {
89
  }
90
  }
91
 
92
- app.get('/', async (req, res) => {
93
- const logger = new StringLogger();
94
-
95
- // Check if running
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  if (global.isDemoRunning) {
97
- return res.send('Demo is already running, please wait...');
 
 
 
 
 
 
 
98
  }
99
-
 
 
 
 
 
 
100
  global.isDemoRunning = true;
101
 
102
- // Override console methods to capture output
 
 
 
 
 
103
  const originalConsoleLog = console.log;
104
  const originalConsoleError = console.error;
105
-
106
  console.log = (...args) => {
107
- logger.log(...args);
 
108
  originalConsoleLog.apply(console, args);
109
  };
 
110
  console.error = (...args) => {
111
- logger.error(...args);
 
112
  originalConsoleError.apply(console, args);
113
  };
114
 
115
  try {
116
  await runDemo();
117
  } catch (e) {
118
- console.error('Unhandled error in demo:', e);
119
  } finally {
120
- // Restore console methods
121
  console.log = originalConsoleLog;
122
  console.error = originalConsoleError;
123
  global.isDemoRunning = false;
 
 
 
124
  }
125
-
126
- const output = logger.getOutput();
127
- res.send(`
128
- <html>
129
- <head>
130
- <title>Node.js Performance Demo</title>
131
- <style>
132
- body { font-family: monospace; background: #f0f0f0; padding: 20px; }
133
- pre { background: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); white-space: pre-wrap; }
134
- h1 { color: #333; }
135
- .btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin-top: 20px; }
136
- </style>
137
- </head>
138
- <body>
139
- <h1>Node.js Performance Demo Output</h1>
140
- <pre>${output}</pre>
141
- <p>Refresh to run again.</p>
142
- <a href="/system-info" class="btn">View System Info (child_process demo)</a>
143
- </body>
144
- </html>
145
- `);
146
  });
147
 
148
- app.get('/system-info', (req, res) => {
 
149
  exec('uname -a && echo "\\nDisk Usage:" && df -h | head -n 5', (error, stdout, stderr) => {
150
- const output = error ? `Error: ${error.message}` : stdout;
151
- res.send(`
152
- <html>
153
- <head>
154
- <title>System Info</title>
155
- <style>
156
- body { font-family: monospace; background: #f0f0f0; padding: 20px; }
157
- pre { background: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); white-space: pre-wrap; }
158
- h1 { color: #333; }
159
- .btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin-top: 20px; }
160
- </style>
161
- </head>
162
- <body>
163
- <h1>System Info (via child_process)</h1>
164
- <pre>${output}</pre>
165
- <a href="/" class="btn">Back to Demo</a>
166
- </body>
167
- </html>
168
- `);
169
  });
170
  });
171
 
 
1
  const express = require('express');
2
  const path = require('path');
3
  const fs = require('fs');
4
+ const { exec } = require('child_process');
5
  const LargeFileHandler = require('./src/largeFile');
6
  const xmlJsonHandler = require('./src/xmlJson');
7
  const performanceMonitor = require('./src/performance');
8
 
 
 
9
  const app = express();
10
  const PORT = 7860;
11
 
 
20
  const XML_FILE_PATH = path.join(DATA_DIR, 'data.xml');
21
  const LARGE_JSON_PATH = path.join(DATA_DIR, 'large_data.json');
22
 
23
+ // Global lock to prevent concurrent demos
24
+ global.isDemoRunning = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  async function runDemo() {
27
  console.log('=== Node.js 性能优化与大文件处理演示 ===\n');
 
31
  console.log('--- 1. 大文件流式处理 ---');
32
  const largeFileHandler = new LargeFileHandler(LARGE_FILE_PATH);
33
 
34
+ // Generate 100k lines
35
  const lineCount = 100000;
36
  console.log(`正在生成测试数据 (${lineCount} 行)...`);
37
  await largeFileHandler.generateTestFile(lineCount);
 
42
 
43
  console.log('\n--- 2. XML 处理 ---');
44
  // Generate XML
45
+ const xmlContent = xmlJsonHandler.generateXml(1000);
46
  fs.writeFileSync(XML_FILE_PATH, xmlContent);
47
  console.log(`XML 文件已保存: ${XML_FILE_PATH}`);
48
 
 
53
  console.log('\n--- 3. JSON 大文件流式处理 ---');
54
  // Generate large JSON
55
  console.log('正在生成 JSON 数据...');
56
+ await xmlJsonHandler.generateLargeJson(LARGE_JSON_PATH, 10000);
57
 
58
  // Stream parse JSON
59
  console.log('开始流式解析 JSON...');
 
73
  }
74
  }
75
 
76
+ // Serve the main page
77
+ app.get('/', (req, res) => {
78
+ res.send(`
79
+ <!DOCTYPE html>
80
+ <html>
81
+ <head>
82
+ <title>Node.js Performance Demo</title>
83
+ <meta charset="utf-8">
84
+ <style>
85
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #f0f2f5; padding: 40px; color: #333; }
86
+ .container { max-width: 900px; margin: 0 auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
87
+ h1 { color: #2c3e50; margin-bottom: 20px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
88
+ .controls { margin-bottom: 20px; display: flex; gap: 10px; }
89
+ button { padding: 10px 20px; font-size: 16px; cursor: pointer; background: #007bff; color: white; border: none; border-radius: 5px; transition: background 0.3s; }
90
+ button:hover { background: #0056b3; }
91
+ button:disabled { background: #ccc; cursor: not-allowed; }
92
+ #output { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 5px; font-family: "Menlo", "Monaco", "Courier New", monospace; height: 500px; overflow-y: auto; white-space: pre-wrap; font-size: 14px; line-height: 1.5; border: 1px solid #333; }
93
+ .status { margin-top: 10px; font-style: italic; color: #666; }
94
+ </style>
95
+ </head>
96
+ <body>
97
+ <div class="container">
98
+ <h1>Node.js Performance Demo</h1>
99
+ <div class="controls">
100
+ <button id="runBtn" onclick="runDemo()">▶ 运行性能演示</button>
101
+ <button onclick="getSystemInfo()" style="background: #28a745;">🖥 获取系统信息</button>
102
+ <button onclick="clearOutput()" style="background: #6c757d;">🗑 清空输出</button>
103
+ </div>
104
+ <div id="output">等待操作...</div>
105
+ <div id="status" class="status"></div>
106
+ </div>
107
+
108
+ <script>
109
+ const outputDiv = document.getElementById('output');
110
+ const statusDiv = document.getElementById('status');
111
+ const runBtn = document.getElementById('runBtn');
112
+
113
+ function log(msg, isError = false) {
114
+ if (outputDiv.innerHTML === '等待操作...') {
115
+ outputDiv.innerHTML = '';
116
+ }
117
+ const span = document.createElement('div');
118
+ span.textContent = msg;
119
+ if (isError) span.style.color = '#ff6b6b';
120
+ outputDiv.appendChild(span);
121
+ outputDiv.scrollTop = outputDiv.scrollHeight;
122
+ }
123
+
124
+ function clearOutput() {
125
+ outputDiv.innerHTML = '等待操作...';
126
+ statusDiv.textContent = '';
127
+ }
128
+
129
+ function runDemo() {
130
+ if (runBtn.disabled) return;
131
+
132
+ runBtn.disabled = true;
133
+ statusDiv.textContent = '正在运行演示...';
134
+ outputDiv.innerHTML = ''; // Clear previous output
135
+
136
+ const eventSource = new EventSource('/api/run-demo');
137
+
138
+ eventSource.onmessage = function(event) {
139
+ try {
140
+ const data = JSON.parse(event.data);
141
+ if (data.type === 'log') {
142
+ log(data.message);
143
+ } else if (data.type === 'error') {
144
+ log(data.message, true);
145
+ } else if (data.type === 'end') {
146
+ eventSource.close();
147
+ runBtn.disabled = false;
148
+ statusDiv.textContent = '演示完成';
149
+ }
150
+ } catch (e) {
151
+ console.error('Error parsing SSE data', e);
152
+ }
153
+ };
154
+
155
+ eventSource.onerror = function(err) {
156
+ console.error('EventSource failed:', err);
157
+ eventSource.close();
158
+ runBtn.disabled = false;
159
+ statusDiv.textContent = '连接中断';
160
+ log('Error: Connection to server lost.', true);
161
+ };
162
+ }
163
+
164
+ async function getSystemInfo() {
165
+ statusDiv.textContent = '正在获取系统信息...';
166
+ try {
167
+ const res = await fetch('/api/system-info');
168
+ const text = await res.text();
169
+ log('\\n=== 系统信息 ===');
170
+ log(text);
171
+ statusDiv.textContent = '获取成功';
172
+ } catch (e) {
173
+ log('Error fetching system info: ' + e.message, true);
174
+ }
175
+ }
176
+ </script>
177
+ </body>
178
+ </html>
179
+ `);
180
+ });
181
+
182
+ // SSE Endpoint for running the demo
183
+ app.get('/api/run-demo', async (req, res) => {
184
  if (global.isDemoRunning) {
185
+ res.writeHead(200, {
186
+ 'Content-Type': 'text/event-stream',
187
+ 'Cache-Control': 'no-cache',
188
+ 'Connection': 'keep-alive'
189
+ });
190
+ res.write(`data: ${JSON.stringify({ type: 'error', message: 'Demo is already running. Please wait.' })}\n\n`);
191
+ res.write(`data: ${JSON.stringify({ type: 'end' })}\n\n`);
192
+ return res.end();
193
  }
194
+
195
+ res.writeHead(200, {
196
+ 'Content-Type': 'text/event-stream',
197
+ 'Cache-Control': 'no-cache',
198
+ 'Connection': 'keep-alive'
199
+ });
200
+
201
  global.isDemoRunning = true;
202
 
203
+ // Helper to send SSE messages
204
+ const send = (type, message) => {
205
+ res.write(`data: ${JSON.stringify({ type, message })}\n\n`);
206
+ };
207
+
208
+ // Override console methods
209
  const originalConsoleLog = console.log;
210
  const originalConsoleError = console.error;
211
+
212
  console.log = (...args) => {
213
+ const msg = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ');
214
+ send('log', msg);
215
  originalConsoleLog.apply(console, args);
216
  };
217
+
218
  console.error = (...args) => {
219
+ const msg = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ');
220
+ send('error', msg);
221
  originalConsoleError.apply(console, args);
222
  };
223
 
224
  try {
225
  await runDemo();
226
  } catch (e) {
227
+ console.error('Unhandled error:', e);
228
  } finally {
229
+ // Restore console
230
  console.log = originalConsoleLog;
231
  console.error = originalConsoleError;
232
  global.isDemoRunning = false;
233
+
234
+ send('end', 'Done');
235
+ res.end();
236
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  });
238
 
239
+ // API for system info
240
+ app.get('/api/system-info', (req, res) => {
241
  exec('uname -a && echo "\\nDisk Usage:" && df -h | head -n 5', (error, stdout, stderr) => {
242
+ if (error) {
243
+ res.status(500).send(`Error: ${error.message}`);
244
+ return;
245
+ }
246
+ res.send(stdout);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  });
248
  });
249