auto_error_fixer 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DLZstudio/AEF/config/setting.cfg +12 -0
- package/DLZstudio/AEF/log/log.txt +5 -0
- package/DLZstudio/AEF/views/panel.html +640 -0
- package/README.md +114 -0
- package/examples/simple-example.js +32 -0
- package/index.js +603 -0
- package/package.json +47 -0
package/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const nodemailer = require('nodemailer');
|
|
5
|
+
const yaml = require('yaml');
|
|
6
|
+
const { createServer } = require('http');
|
|
7
|
+
const { Server } = require('socket.io');
|
|
8
|
+
|
|
9
|
+
// AEF - Auto Error Fixer 主模块
|
|
10
|
+
class AEF {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.app = express();
|
|
14
|
+
this.server = null;
|
|
15
|
+
this.io = null;
|
|
16
|
+
this.logFilePath = './DLZstudio/AEF/log/log.txt';
|
|
17
|
+
this.configPath = './DLZstudio/AEF/config/setting.cfg';
|
|
18
|
+
this.isEnabled = true;
|
|
19
|
+
this.transporter = null;
|
|
20
|
+
|
|
21
|
+
// 统计信息
|
|
22
|
+
this.stats = {
|
|
23
|
+
fixCount: 0,
|
|
24
|
+
restartCount: 0,
|
|
25
|
+
lastErrorTime: null,
|
|
26
|
+
errorHistory: []
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// 确保目录存在
|
|
30
|
+
this.ensureDirectories();
|
|
31
|
+
|
|
32
|
+
// 初始化配置
|
|
33
|
+
this.loadConfig();
|
|
34
|
+
|
|
35
|
+
// 设置邮件传输器
|
|
36
|
+
this.setupMailTransporter();
|
|
37
|
+
|
|
38
|
+
// 初始化日志
|
|
39
|
+
this.initLog();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 确保必要目录存在
|
|
43
|
+
ensureDirectories() {
|
|
44
|
+
const dirs = [
|
|
45
|
+
'./DLZstudio',
|
|
46
|
+
'./DLZstudio/AEF',
|
|
47
|
+
'./DLZstudio/AEF/config',
|
|
48
|
+
'./DLZstudio/AEF/log',
|
|
49
|
+
'./DLZstudio/AEF/views',
|
|
50
|
+
'./DLZstudio/AEF/public'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
dirs.forEach(dir => {
|
|
54
|
+
if (!fs.existsSync(dir)) {
|
|
55
|
+
fs.mkdirpSync(dir);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 加载配置文件
|
|
61
|
+
loadConfig() {
|
|
62
|
+
try {
|
|
63
|
+
if (!fs.existsSync(this.configPath)) {
|
|
64
|
+
// 创建默认配置文件
|
|
65
|
+
const defaultConfig = {
|
|
66
|
+
password: '123456', // 默认密码
|
|
67
|
+
enablePanel: true,
|
|
68
|
+
email: {
|
|
69
|
+
enabled: false,
|
|
70
|
+
host: '',
|
|
71
|
+
port: 587,
|
|
72
|
+
secure: false,
|
|
73
|
+
user: '',
|
|
74
|
+
pass: '',
|
|
75
|
+
to: '' // 接收邮箱
|
|
76
|
+
},
|
|
77
|
+
autoRestart: true,
|
|
78
|
+
logLevel: 'info'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
fs.writeFileSync(this.configPath, yaml.stringify(defaultConfig));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const configContent = fs.readFileSync(this.configPath, 'utf8');
|
|
85
|
+
this.config = yaml.parse(configContent);
|
|
86
|
+
|
|
87
|
+
// 如果密码为空,使用默认密码
|
|
88
|
+
if (!this.config.password || this.config.password.trim() === '') {
|
|
89
|
+
this.config.password = '123456';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 监听配置文件变化,实现热重载
|
|
93
|
+
fs.watch(this.configPath, (eventType, filename) => {
|
|
94
|
+
if (eventType === 'change') {
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
this.reloadConfig();
|
|
97
|
+
}, 100); // 延迟加载,避免文件写入未完成
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('加载配置文件失败:', error.message);
|
|
102
|
+
this.config = { password: '123456' }; // 使用默认配置
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 重新加载配置
|
|
107
|
+
reloadConfig() {
|
|
108
|
+
try {
|
|
109
|
+
const configContent = fs.readFileSync(this.configPath, 'utf8');
|
|
110
|
+
this.config = yaml.parse(configContent);
|
|
111
|
+
|
|
112
|
+
if (!this.config.password || this.config.password.trim() === '') {
|
|
113
|
+
this.config.password = '123456';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('配置文件已重新加载');
|
|
117
|
+
|
|
118
|
+
// 重新设置邮件传输器
|
|
119
|
+
this.setupMailTransporter();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('重新加载配置文件失败:', error.message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 设置邮件传输器
|
|
126
|
+
setupMailTransporter() {
|
|
127
|
+
if (this.config.email && this.config.email.enabled) {
|
|
128
|
+
this.transporter = nodemailer.createTransporter({
|
|
129
|
+
host: this.config.email.host,
|
|
130
|
+
port: this.config.email.port,
|
|
131
|
+
secure: this.config.email.secure,
|
|
132
|
+
auth: {
|
|
133
|
+
user: this.config.email.user,
|
|
134
|
+
pass: this.config.email.pass
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 初始化日志
|
|
141
|
+
initLog() {
|
|
142
|
+
if (!fs.existsSync(this.logFilePath)) {
|
|
143
|
+
fs.writeFileSync(this.logFilePath, `# AEF 日志文件\n\n${new Date().toISOString()} - AEF 已启动\n`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 写入日志
|
|
148
|
+
log(message, level = 'info') {
|
|
149
|
+
const timestamp = new Date().toISOString();
|
|
150
|
+
const logEntry = `${timestamp} [${level.toUpperCase()}] ${message}\n`;
|
|
151
|
+
|
|
152
|
+
// 输出到控制台
|
|
153
|
+
console.log(logEntry.trim());
|
|
154
|
+
|
|
155
|
+
// 写入日志文件
|
|
156
|
+
try {
|
|
157
|
+
fs.appendFileSync(this.logFilePath, logEntry);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('写入日志文件失败:', error.message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 检测错误类型
|
|
164
|
+
detectError(error) {
|
|
165
|
+
// 这里可以扩展更多错误类型的检测
|
|
166
|
+
const errorPatterns = [
|
|
167
|
+
{ type: 'port_in_use', pattern: /EADDRINUSE|address already in use/i },
|
|
168
|
+
{ type: 'connection_refused', pattern: /ECONNREFUSED|connect ECONNREFUSED/i },
|
|
169
|
+
{ type: 'file_not_found', pattern: /ENOENT|no such file or directory/i },
|
|
170
|
+
{ type: 'permission_denied', pattern: /EACCES|permission denied/i },
|
|
171
|
+
{ type: 'memory_limit', pattern: /JavaScript heap out of memory/i },
|
|
172
|
+
{ type: 'disk_full', pattern: /ENOSPC|no space left on device/i },
|
|
173
|
+
{ type: 'database_connection', pattern: /ECONNRESET|database connection|failed to connect/i },
|
|
174
|
+
{ type: 'timeout_error', pattern: /ETIMEDOUT|timeout|request timed out/i },
|
|
175
|
+
{ type: 'invalid_syntax', pattern: /SyntaxError/i },
|
|
176
|
+
{ type: 'missing_dependency', pattern: /Cannot find module|Module not found/i },
|
|
177
|
+
{ type: 'stack_overflow', pattern: /Maximum call stack size exceeded/i },
|
|
178
|
+
{ type: 'network_error', pattern: /NetworkError|fetch failed/i },
|
|
179
|
+
{ type: 'authentication_failed', pattern: /Authentication failed|Invalid credentials/i }
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
for (const pattern of errorPatterns) {
|
|
183
|
+
if (pattern.pattern.test(error.message || error.toString())) {
|
|
184
|
+
return pattern.type;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return 'unknown';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 尝试修复错误
|
|
192
|
+
async attemptFix(errorType, error) {
|
|
193
|
+
this.log(`尝试修复错误: ${errorType}`, 'info');
|
|
194
|
+
|
|
195
|
+
switch (errorType) {
|
|
196
|
+
case 'port_in_use':
|
|
197
|
+
// 尝试获取占用端口的进程并终止它
|
|
198
|
+
try {
|
|
199
|
+
const port = (error.message.match(/port (\d+)/) || [])[1];
|
|
200
|
+
if (port) {
|
|
201
|
+
const { exec } = require('child_process');
|
|
202
|
+
const util = require('util');
|
|
203
|
+
const execAsync = util.promisify(exec);
|
|
204
|
+
|
|
205
|
+
if (process.platform === 'win32') {
|
|
206
|
+
const { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
|
|
207
|
+
const pidMatch = stdout.match(/^\s*TCP\s+.*:(\d+)\s+.*:(\d+)\s+LISTENING\s+(\d+)/m);
|
|
208
|
+
if (pidMatch) {
|
|
209
|
+
const pid = pidMatch[3];
|
|
210
|
+
await execAsync(`taskkill /PID ${pid} /F`);
|
|
211
|
+
this.log(`终止了占用端口 ${port} 的进程 PID: ${pid}`, 'info');
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// Linux/Mac
|
|
216
|
+
const { stdout } = await execAsync(`lsof -i :${port} | grep LISTEN`);
|
|
217
|
+
const pidMatch = stdout.match(/\s*(\d+)\s+/);
|
|
218
|
+
if (pidMatch) {
|
|
219
|
+
const pid = pidMatch[1];
|
|
220
|
+
await execAsync(`kill -9 ${pid}`);
|
|
221
|
+
this.log(`终止了占用端口 ${port} 的进程 PID: ${pid}`, 'info');
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (fixError) {
|
|
227
|
+
this.log(`修复端口占用错误失败: ${fixError.message}`, 'error');
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'file_not_found':
|
|
232
|
+
// 尝试创建缺失的文件或目录
|
|
233
|
+
try {
|
|
234
|
+
const filePath = (error.message.match(/'([^']+)'/) || [])[1]; // 提取文件路径
|
|
235
|
+
if (filePath && !fs.existsSync(filePath)) {
|
|
236
|
+
const dirPath = path.dirname(filePath);
|
|
237
|
+
if (!fs.existsSync(dirPath)) {
|
|
238
|
+
fs.mkdirpSync(dirPath); // 创建缺失的目录
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 根据文件扩展名创建适当的默认内容
|
|
242
|
+
if (filePath.endsWith('.json')) {
|
|
243
|
+
fs.writeFileSync(filePath, '{}');
|
|
244
|
+
} else if (filePath.endsWith('.txt') || filePath.endsWith('.log')) {
|
|
245
|
+
fs.writeFileSync(filePath, '');
|
|
246
|
+
} else if (filePath.endsWith('.js')) {
|
|
247
|
+
fs.writeFileSync(filePath, '// 新建的JavaScript文件\n');
|
|
248
|
+
} else {
|
|
249
|
+
// 对于其他文件类型,创建空文件
|
|
250
|
+
fs.writeFileSync(filePath, '');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.log(`创建了缺失的文件: ${filePath}`, 'info');
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
} catch (fixError) {
|
|
257
|
+
this.log(`修复文件未找到错误失败: ${fixError.message}`, 'error');
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
|
|
261
|
+
case 'permission_denied':
|
|
262
|
+
// 尝试修改文件权限
|
|
263
|
+
try {
|
|
264
|
+
const filePath = (error.message.match(/'([^']+)'/) || [])[1];
|
|
265
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
266
|
+
if (process.platform !== 'win32') {
|
|
267
|
+
const { exec } = require('child_process');
|
|
268
|
+
const util = require('util');
|
|
269
|
+
const execAsync = util.promisify(exec);
|
|
270
|
+
|
|
271
|
+
await execAsync(`chmod 755 "${filePath}"`);
|
|
272
|
+
this.log(`修改了文件权限: ${filePath}`, 'info');
|
|
273
|
+
return true;
|
|
274
|
+
} else {
|
|
275
|
+
// Windows上无法轻易修改权限,记录信息
|
|
276
|
+
this.log(`在Windows上无法自动修改文件权限: ${filePath}`, 'info');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch (fixError) {
|
|
280
|
+
this.log(`修复权限错误失败: ${fixError.message}`, 'error');
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'database_connection':
|
|
285
|
+
// 尝试重新连接数据库
|
|
286
|
+
try {
|
|
287
|
+
// 这里可以添加特定数据库的重连逻辑
|
|
288
|
+
this.log('尝试重新连接数据库...', 'info');
|
|
289
|
+
|
|
290
|
+
// 延迟一段时间后重试
|
|
291
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
292
|
+
|
|
293
|
+
// 这里可以根据具体的数据库类型实现重连逻辑
|
|
294
|
+
// 比如对于MongoDB, MySQL等
|
|
295
|
+
return true;
|
|
296
|
+
} catch (fixError) {
|
|
297
|
+
this.log(`修复数据库连接错误失败: ${fixError.message}`, 'error');
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case 'timeout_error':
|
|
302
|
+
// 尝试增加超时时间或重试
|
|
303
|
+
try {
|
|
304
|
+
this.log('检测到超时错误,增加重试机制', 'info');
|
|
305
|
+
// 可以在这里实现重试逻辑
|
|
306
|
+
return true;
|
|
307
|
+
} catch (fixError) {
|
|
308
|
+
this.log(`修复超时错误失败: ${fixError.message}`, 'error');
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
|
|
312
|
+
case 'missing_dependency':
|
|
313
|
+
// 尝试安装缺失的依赖
|
|
314
|
+
try {
|
|
315
|
+
const moduleName = (error.message.match(/'([^']+)'/) || [])[1];
|
|
316
|
+
if (moduleName) {
|
|
317
|
+
this.log(`检测到缺失的模块: ${moduleName}`, 'info');
|
|
318
|
+
// 注意:实际生产环境中不建议自动安装依赖
|
|
319
|
+
// 这里只是记录,实际应由开发者处理
|
|
320
|
+
}
|
|
321
|
+
} catch (fixError) {
|
|
322
|
+
this.log(`处理缺失依赖错误失败: ${fixError.message}`, 'error');
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
case 'network_error':
|
|
327
|
+
// 尝试重连网络资源
|
|
328
|
+
try {
|
|
329
|
+
this.log('检测到网络错误,尝试重连...', 'info');
|
|
330
|
+
// 实现重连逻辑
|
|
331
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
332
|
+
return true;
|
|
333
|
+
} catch (fixError) {
|
|
334
|
+
this.log(`修复网络错误失败: ${fixError.message}`, 'error');
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
default:
|
|
339
|
+
this.log(`无法自动修复错误类型: ${errorType}`, 'warning');
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 处理错误
|
|
347
|
+
async handleError(error) {
|
|
348
|
+
if (!this.isEnabled) {
|
|
349
|
+
// 如果AEF被禁用,则直接抛出错误
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const errorType = this.detectError(error);
|
|
354
|
+
this.log(`检测到错误类型: ${errorType}`, 'error');
|
|
355
|
+
this.log(`错误详情: ${error.stack || error.message || error}`, 'error');
|
|
356
|
+
|
|
357
|
+
// 更新最后错误时间
|
|
358
|
+
this.stats.lastErrorTime = new Date().toISOString();
|
|
359
|
+
|
|
360
|
+
// 尝试修复错误
|
|
361
|
+
const fixed = await this.attemptFix(errorType, error);
|
|
362
|
+
|
|
363
|
+
if (!fixed) {
|
|
364
|
+
this.log('无法自动修复错误', 'error');
|
|
365
|
+
|
|
366
|
+
// 对于某些非致命错误,我们不需要重启服务器,只需记录即可
|
|
367
|
+
const nonFatalErrors = ['unknown', 'timeout_error', 'network_error', 'authentication_failed'];
|
|
368
|
+
|
|
369
|
+
if (nonFatalErrors.includes(errorType)) {
|
|
370
|
+
// 对于非致命错误,记录但不重启
|
|
371
|
+
this.log(`检测到非致命错误 ${errorType},继续运行服务`, 'warning');
|
|
372
|
+
|
|
373
|
+
// 发送邮件通知(如果启用)
|
|
374
|
+
if (this.config.email && this.config.email.enabled) {
|
|
375
|
+
await this.sendErrorNotification(error, errorType);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 抛出错误,让调用者处理
|
|
379
|
+
throw error;
|
|
380
|
+
} else {
|
|
381
|
+
// 对于致命错误,发送邮件通知并重启服务器
|
|
382
|
+
this.log('准备重启服务器', 'error');
|
|
383
|
+
|
|
384
|
+
// 发送邮件通知
|
|
385
|
+
if (this.config.email && this.config.email.enabled) {
|
|
386
|
+
await this.sendErrorNotification(error, errorType);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 如果启用了自动重启,则重启服务器
|
|
390
|
+
if (this.config.autoRestart !== false) {
|
|
391
|
+
this.stats.restartCount++;
|
|
392
|
+
this.restartServer();
|
|
393
|
+
} else {
|
|
394
|
+
// 否则抛出错误
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
this.stats.fixCount++;
|
|
400
|
+
this.log('错误已修复,继续运行', 'info');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 发送错误通知邮件
|
|
405
|
+
async sendErrorNotification(error, errorType) {
|
|
406
|
+
if (!this.transporter) {
|
|
407
|
+
this.log('邮件传输器未初始化', 'error');
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
// 默认邮件内容
|
|
413
|
+
const defaultContent = '# 服务器发生错误\n\n### AEF遇到了一个无法自动处理的错误,请手动修复\n### AEF正在尝试重启服务器';
|
|
414
|
+
const mailContent = (this.config.email && this.config.email.content) ?
|
|
415
|
+
this.config.email.content : defaultContent;
|
|
416
|
+
|
|
417
|
+
const mailOptions = {
|
|
418
|
+
from: this.config.email.user,
|
|
419
|
+
to: this.config.email.to,
|
|
420
|
+
subject: `AEF错误通知 - ${errorType}`,
|
|
421
|
+
text: mailContent,
|
|
422
|
+
html: this.markdownToHtml(mailContent)
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
await this.transporter.sendMail(mailOptions);
|
|
426
|
+
this.log('错误通知邮件已发送', 'info');
|
|
427
|
+
} catch (emailError) {
|
|
428
|
+
this.log(`发送错误通知邮件失败: ${emailError.message}`, 'error');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 简单的Markdown转HTML转换器
|
|
433
|
+
markdownToHtml(md) {
|
|
434
|
+
let html = md
|
|
435
|
+
// 转换标题
|
|
436
|
+
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
|
|
437
|
+
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
|
|
438
|
+
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
|
|
439
|
+
// 转换换行
|
|
440
|
+
.replace(/\n\n/g, '<br/><br/>')
|
|
441
|
+
.replace(/\n/g, '<br/>')
|
|
442
|
+
// 确保段落之间有间距
|
|
443
|
+
.replace(/<br\/><br\/>/g, '</p><p>')
|
|
444
|
+
.replace(/^(.+)$/gm, '<p>$1</p>');
|
|
445
|
+
|
|
446
|
+
// 清理多余的段落标签
|
|
447
|
+
html = html.replace(/<p><\/p>/g, '');
|
|
448
|
+
|
|
449
|
+
// 添加整体包装
|
|
450
|
+
html = `<div>${html}</div>`;
|
|
451
|
+
|
|
452
|
+
return html;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 重启服务器
|
|
456
|
+
restartServer() {
|
|
457
|
+
this.log('正在重启服务器...', 'info');
|
|
458
|
+
|
|
459
|
+
// 先关闭当前服务器
|
|
460
|
+
if (this.server) {
|
|
461
|
+
this.server.close(() => {
|
|
462
|
+
this.log('服务器已关闭,准备重启', 'info');
|
|
463
|
+
// 这里实际重启逻辑取决于如何集成到应用中
|
|
464
|
+
// 在实际部署中,可能需要配合pm2或其他进程管理工具
|
|
465
|
+
process.exit(1); // 退出进程,依赖外部进程管理器重启
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
// 如果没有服务器实例,直接退出
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 启动AEF控制面板
|
|
474
|
+
async startPanel() {
|
|
475
|
+
if (this.config.enablePanel === false) {
|
|
476
|
+
this.log('控制面板已被禁用', 'info');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
this.app.use(express.static('./DLZstudio/AEF/public'));
|
|
481
|
+
this.app.use(express.json());
|
|
482
|
+
|
|
483
|
+
// 控制面板路由
|
|
484
|
+
this.app.get('/panel/AEF', (req, res) => {
|
|
485
|
+
res.sendFile(path.join(__dirname, 'views', 'panel.html'));
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// 验证密码接口
|
|
489
|
+
this.app.post('/panel/AEF/login', (req, res) => {
|
|
490
|
+
const { password } = req.body;
|
|
491
|
+
if (password === this.config.password) {
|
|
492
|
+
res.json({ success: true });
|
|
493
|
+
} else {
|
|
494
|
+
res.status(401).json({ success: false, message: '密码错误' });
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// 获取状态接口
|
|
499
|
+
this.app.get('/panel/AEF/status', (req, res) => {
|
|
500
|
+
res.json({
|
|
501
|
+
isEnabled: this.isEnabled,
|
|
502
|
+
config: this.config,
|
|
503
|
+
log: this.getLog(),
|
|
504
|
+
...this.stats,
|
|
505
|
+
memoryInfo: process.memoryUsage()
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// 切换AEF状态接口
|
|
510
|
+
this.app.post('/panel/AEF/toggle', (req, res) => {
|
|
511
|
+
this.isEnabled = !this.isEnabled;
|
|
512
|
+
res.json({ success: true, isEnabled: this.isEnabled });
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// 获取日志接口
|
|
516
|
+
this.app.get('/panel/AEF/log', (req, res) => {
|
|
517
|
+
res.json({ log: this.getLog() });
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// 获取配置文件内容
|
|
521
|
+
this.app.get('/panel/AEF/config', (req, res) => {
|
|
522
|
+
try {
|
|
523
|
+
const configContent = fs.readFileSync(this.configPath, 'utf8');
|
|
524
|
+
res.json({ content: configContent });
|
|
525
|
+
} catch (error) {
|
|
526
|
+
res.status(500).json({ error: error.message });
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// 更新配置文件内容
|
|
531
|
+
this.app.post('/panel/AEF/config', (req, res) => {
|
|
532
|
+
try {
|
|
533
|
+
const { content } = req.body;
|
|
534
|
+
fs.writeFileSync(this.configPath, content);
|
|
535
|
+
res.json({ success: true });
|
|
536
|
+
} catch (error) {
|
|
537
|
+
res.status(500).json({ error: error.message });
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// 启动HTTP服务器
|
|
542
|
+
this.server = createServer(this.app);
|
|
543
|
+
this.io = new Server(this.server);
|
|
544
|
+
|
|
545
|
+
// Socket.IO连接
|
|
546
|
+
this.io.on('connection', (socket) => {
|
|
547
|
+
this.log('控制面板客户端已连接', 'info');
|
|
548
|
+
|
|
549
|
+
// 定期发送日志更新
|
|
550
|
+
const logInterval = setInterval(() => {
|
|
551
|
+
socket.emit('logUpdate', this.getLog());
|
|
552
|
+
}, 2000);
|
|
553
|
+
|
|
554
|
+
socket.on('disconnect', () => {
|
|
555
|
+
clearInterval(logInterval);
|
|
556
|
+
this.log('控制面板客户端已断开连接', 'info');
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const port = process.env.AEF_PANEL_PORT || 3001;
|
|
561
|
+
this.server.listen(port, () => {
|
|
562
|
+
this.log(`AEF控制面板已在端口 ${port} 上启动`, 'info');
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 获取最近的日志
|
|
567
|
+
getLog() {
|
|
568
|
+
try {
|
|
569
|
+
if (fs.existsSync(this.logFilePath)) {
|
|
570
|
+
const logContent = fs.readFileSync(this.logFilePath, 'utf8');
|
|
571
|
+
// 返回最后100行日志
|
|
572
|
+
const lines = logContent.split('\n');
|
|
573
|
+
return lines.slice(Math.max(lines.length - 100, 0)).join('\n');
|
|
574
|
+
}
|
|
575
|
+
return '';
|
|
576
|
+
} catch (error) {
|
|
577
|
+
return `获取日志失败: ${error.message}`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// 中间件函数
|
|
582
|
+
middleware() {
|
|
583
|
+
return async (err, req, res, next) => {
|
|
584
|
+
await this.handleError(err);
|
|
585
|
+
next(err);
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 捕获未处理的异常
|
|
590
|
+
handleUncaughtExceptions() {
|
|
591
|
+
process.on('uncaughtException', async (error) => {
|
|
592
|
+
this.log('捕获到未处理的异常', 'error');
|
|
593
|
+
await this.handleError(error);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
process.on('unhandledRejection', async (reason, promise) => {
|
|
597
|
+
this.log('捕获到未处理的Promise拒绝', 'error');
|
|
598
|
+
await this.handleError(reason instanceof Error ? reason : new Error(reason));
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
module.exports = AEF;
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auto_error_fixer",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "AEF (Auto Error Fixer) - 自动故障修复系统,能够自动检测、修复服务器错误并在必要时重启服务器",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"error-handling",
|
|
11
|
+
"auto-fix",
|
|
12
|
+
"server-monitoring",
|
|
13
|
+
"error-recovery",
|
|
14
|
+
"automatic-restart",
|
|
15
|
+
"server-management"
|
|
16
|
+
],
|
|
17
|
+
"author": "DLZstudio",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/DLZstudio/AEF.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/DLZstudio/AEF/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/DLZstudio/AEF#readme",
|
|
27
|
+
"files": [
|
|
28
|
+
"index.js",
|
|
29
|
+
"README.md",
|
|
30
|
+
"package.json",
|
|
31
|
+
"examples/**/*",
|
|
32
|
+
"DLZstudio/AEF/views/**/*",
|
|
33
|
+
"DLZstudio/AEF/config/**/*",
|
|
34
|
+
"DLZstudio/AEF/log/**/*",
|
|
35
|
+
"DLZstudio/AEF/public/**/*"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"express": "^4.18.2",
|
|
39
|
+
"nodemailer": "^6.9.0",
|
|
40
|
+
"fs-extra": "^11.0.0",
|
|
41
|
+
"yaml": "^2.2.0",
|
|
42
|
+
"socket.io": "^4.7.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"jest": "^29.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|