-
Notifications
You must be signed in to change notification settings - Fork 285
Expand file tree
/
Copy pathindex.js
More file actions
300 lines (273 loc) · 10.2 KB
/
index.js
File metadata and controls
300 lines (273 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
import { performance } from 'perf_hooks';
const startTime = performance.now();
import * as fastlogger from './controllers/fastlogger.js'
import path from 'path';
import {checkPhpAvailable} from './utils/phpEnv.js';
import os from 'os';
import qs from 'qs';
import {fileURLToPath} from 'url';
import {validateBasicAuth, validateJs, validatePwd, validatHtml} from "./utils/api_validate.js";
import {startAllPlugins} from "./utils/pluginManager.js";
// 注册自定义import钩子
import './utils/esm-register.mjs';
// 引入python守护进程
import {daemon} from "./utils/daemonManager.js";
// 注册控制器
import {registerRoutes, registerWsRoutes} from './controllers/index.js';
const {fastify, wsApp} = fastlogger;
// 获取当前路径
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = 5757;
const WsPORT = 57575;
const MAX_TEXT_SIZE = process.env.MAX_TEXT_SIZE || 0.1 * 1024 * 1024; // 设置最大文本大小为 0.1 MB
const MAX_IMAGE_SIZE = process.env.MAX_IMAGE_SIZE || 0.5 * 1024 * 1024; // 设置最大图片大小为 500 KB
// 定义options的目录
const rootDir = __dirname;
const docsDir = path.join(__dirname, 'docs');
const jxDir = path.join(__dirname, 'jx');
const publicDir = path.join(__dirname, 'public');
const appsDir = path.join(__dirname, 'apps');
const jsonDir = path.join(__dirname, 'json');
const jsDir = path.join(__dirname, 'spider/js');
const dr2Dir = path.join(__dirname, 'spider/js_dr2');
const pyDir = path.join(__dirname, 'spider/py');
const phpDir = path.join(__dirname, 'spider/php');
const catDir = path.join(__dirname, 'spider/catvod');
const catLibDir = path.join(__dirname, 'spider/catLib');
const xbpqDir = path.join(__dirname, 'spider/xbpq');
const configDir = path.join(__dirname, 'config');
// 异步启动插件,不阻塞主线程
let pluginProcs = {};
setTimeout(() => {
pluginProcs = startAllPlugins(__dirname);
}, 0);
// 添加钩子事件
fastify.addHook('onReady', async () => {
await checkPhpAvailable();
const endTime = performance.now();
console.log(`🚀 Server started in ${(endTime - startTime).toFixed(2)}ms`);
try {
await daemon.startDaemon();
fastify.log.info('Python守护进程已启动');
} catch (error) {
fastify.log.error(`启动Python守护进程失败: ${error.message}`);
fastify.log.error('Python相关功能将不可用');
}
});
async function onClose() {
try {
await daemon.stopDaemon();
fastify.log.info('Python守护进程已停止');
} catch (error) {
fastify.log.error(`停止Python守护进程失败: ${error.message}`);
}
}
// 停止时清理守护进程
fastify.addHook('onClose', async () => {
await onClose();
});
// 给静态目录插件中心挂载basic验证
fastify.addHook('preHandler', (req, reply, done) => {
if (req.raw.url.startsWith('/apps/')) {
if (req.raw.url.includes('clipboard-pusher/index.html')) {
validateBasicAuth(req, reply, async () => {
validatHtml(req, reply, rootDir).then(() => done());
});
} else {
validateBasicAuth(req, reply, done);
}
} else if (req.raw.url.startsWith('/js/') || req.raw.url.startsWith('/py/')) {
validatePwd(req, reply, done).then(async () => {
validateJs(req, reply, dr2Dir).then(() => done());
});
} else {
done();
}
});
// 自定义插件替换 querystring 解析行为.避免出现两个相同参数被解析成列表
fastify.addHook('onRequest', async (req, reply) => {
// 获取原始 URL 中的 query 部分
const rawUrl = req.raw.url;
const urlParts = rawUrl.split('?');
const urlPath = urlParts[0];
let rawQuery = urlParts.slice(1).join('?'); // 处理可能存在的多个 '?' 情况
// log('rawQuery:', rawQuery);
// 使用 qs 库解析 query 参数,确保兼容参数值中包含 '?' 的情况
req.query = qs.parse(rawQuery, {
strictNullHandling: true, // 确保 `=` 被解析为空字符串
arrayLimit: 100, // 自定义数组限制
allowDots: false, // 禁止点号表示嵌套对象
});
// 如果需要,可以在这里对 req.query 进行进一步处理
});
process.on("uncaughtException", (err) => {
console.error("未捕获异常:", err);
// 不退出,让主进程继续跑
});
process.on('unhandledRejection', (err) => {
fastify.log.error(`未处理的Promise拒绝:${err.message}`);
console.log(`发生了致命的错误,已阻止进程崩溃。${err.stack}`);
// 根据情况决定是否退出进程
// 清理后退出进程(避免程序处于未知状态)
// process.exit(1);
});
// 统一退出处理函数
const handleExit = async (signal) => {
console.log(`\n收到信号 ${signal},正在优雅关闭服务器...`);
try {
await onClose();
// 停止 WebSocket 服务器
await stopWebSocketServer();
// 停止主服务器
await fastify.server.close();
console.log('🛑 所有服务器已优雅关闭');
process.exit(0);
} catch (error) {
console.error('关闭服务器时出错:', error);
process.exit(1);
}
};
// 捕获常见退出信号(Linux 上 pm2 stop 会发 SIGINT 或 SIGTERM)
['SIGINT', 'SIGTERM', 'SIGUSR2'].forEach((sig) => {
process.on(sig, () => handleExit(sig));
});
// Windows 上的兼容处理:捕获 Ctrl+C
if (process.platform === 'win32') {
const rl = (await import('readline')).createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on('SIGINT', () => {
handleExit('SIGINT');
});
}
// 捕获 Node.js 主动退出(比如 pm2 stop 也会触发 exit)
process.on('exit', async (code) => {
console.log(`Process exiting with code: ${code}`);
// 这里不能直接用 await fastify.close()(Node 在 exit 里不等异步)
// 但 Fastify 的 SIGINT/SIGTERM 会提前触发,所以这里只记录日志
for (const [name, proc] of Object.entries(pluginProcs)) {
console.log(`[pluginManager] 结束插件 ${name} ${proc.pid}`);
proc.kill();
}
});
const registerOptions = {
rootDir,
docsDir,
jxDir,
publicDir,
appsDir,
jsonDir,
jsDir,
dr2Dir,
pyDir,
phpDir,
catDir,
catLibDir,
xbpqDir,
PORT,
WsPORT,
MAX_TEXT_SIZE,
MAX_IMAGE_SIZE,
configDir,
indexFilePath: path.join(__dirname, 'index.json'),
customFilePath: path.join(__dirname, 'custom.json'),
subFilePath: path.join(__dirname, 'public/sub/sub.json'),
wsApp,
fastify,
};
registerRoutes(fastify, registerOptions);
registerWsRoutes(wsApp, registerOptions);
// 启动WebSocket服务器
const startWebSocketServer = async (option) => {
try {
const address = await wsApp.listen(option);
return wsApp;
} catch (err) {
wsApp.log.error(`WebSocket服务器启动失败,将会影响一些实时弹幕源的使用:${err.message}`);
}
};
// 停止WebSocket服务器
const stopWebSocketServer = async () => {
try {
await wsApp.server.close();
wsApp.log.info('WebSocket服务器已停止');
} catch (err) {
wsApp.log.error(`停止WebSocket服务器失败:${err.message}`);
}
};
// 启动服务
const start = async () => {
try {
// 启动 Fastify 主服务
// await fastify.listen({port: PORT, host: '0.0.0.0'});
await fastify.listen({port: PORT, host: '::'});
// 启动 WebSocket 服务器 (端口 57577)
await startWebSocketServer({port: WsPORT, host: '::'});
// 获取本地和局域网地址
const localAddress = `http://localhost:${PORT}`;
const wsLocalAddress = `http://localhost:${WsPORT}`;
const interfaces = os.networkInterfaces();
let lanAddress = 'Not available';
let wsLanAddress = 'Not available';
// console.log('interfaces:', interfaces);
for (const [key, iface] of Object.entries(interfaces)) {
if (key.startsWith('VMware Network Adapter VMnet') || !iface) continue;
for (const config of iface) {
if (config.family === 'IPv4' && !config.internal) {
lanAddress = `http://${config.address}:${PORT}`;
wsLanAddress = `http://${config.address}:${WsPORT}`;
break;
}
}
}
console.log(`🚀 服务器启动成功:`);
console.log(`📡 主服务 (端口 ${PORT}):`);
console.log(` - Local: ${localAddress}`);
console.log(` - LAN: ${lanAddress}`);
console.log(`🔌 WebSocket服务 (端口 ${WsPORT}):`);
console.log(` - Local: ${wsLocalAddress}`);
console.log(` - LAN: ${wsLanAddress}`);
console.log(`⚙️ 系统信息:`);
console.log(` - PLATFORM: ${process.platform} ${process.arch}`);
console.log(` - VERSION: ${process.version}`);
if (process.env.VERCEL) {
console.log('Running on Vercel!');
console.log('Vercel Environment:', process.env.VERCEL_ENV); // development, preview, production
console.log('Vercel URL:', process.env.VERCEL_URL);
console.log('Vercel Region:', process.env.VERCEL_REGION);
} else {
console.log('Not running on Vercel!');
}
return true;
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
// 停止服务
const stop = async () => {
try {
// 停止 WebSocket 服务器
await stopWebSocketServer();
// 停止主服务器
await fastify.server.close();
console.log('🛑 所有服务已优雅停止');
return true;
} catch (err) {
fastify.log.error(`停止服务器时发生错误:${err.message}`);
return false;
}
};
// 导出 start 和 stop 方法
export {start, stop};
export default async function handler(req, res) {
await fastify.ready()
fastify.server.emit('request', req, res)
}
// 判断当前模块是否为主模块,如果是主模块,则启动服务
const currentFile = path.normalize(fileURLToPath(import.meta.url)); // 使用 normalize 确保路径一致
const indexFile = path.normalize(path.resolve(__dirname, 'index.js')); // 标准化路径
if (currentFile === indexFile) {
start();
}