mb-rrvideo-server 1.0.2

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/src/index.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * mb-rrvideo-server 主入口
3
+ * 独立的 Node.js 转码服务
4
+ */
5
+
6
+ const http = require('http');
7
+ const url = require('url');
8
+ const config = require('./config');
9
+ const logger = require('./logger');
10
+ const { handleReceiveRequest } = require('./routes/receive');
11
+ const { handleConvertV1, handleConvertV2 } = require('./routes/convert');
12
+ const { handleMerge } = require('./routes/merge');
13
+
14
+ // 创建 HTTP 服务器
15
+ const server = http.createServer((req, res) => {
16
+ try {
17
+ const parsed = url.parse(req.url || '/');
18
+ const pathname = parsed.pathname || '/';
19
+
20
+ if (req.method === 'POST') {
21
+ // 统一接收入口(对应 PHP 的 receiveRequestByRecordIdAct)
22
+ if (pathname === '/Convert/receiveRequestByRecordIdAct') {
23
+ return handleReceiveRequest(req, res);
24
+ }
25
+
26
+ // 直接转码入口(兼容旧版)
27
+ if (pathname === '/convert') {
28
+ let body = '';
29
+ req.on('data', (chunk) => { body += chunk.toString(); });
30
+ req.on('end', async () => {
31
+ try {
32
+ const params = JSON.parse(body || '{}');
33
+ const logFileName = `${params.record_id || 'unknown'}_convert_${Date.now()}`;
34
+
35
+ // 判断是 V1 还是 V2
36
+ const isV2 = params.tasks && Array.isArray(params.tasks);
37
+ if (isV2) {
38
+ await handleConvertV2(params, res, logFileName);
39
+ } else {
40
+ await handleConvertV1(params, res, logFileName);
41
+ }
42
+ } catch (e) {
43
+ res.statusCode = 400;
44
+ res.setHeader('Content-Type', 'application/json');
45
+ res.end(JSON.stringify({ result_code: 1003, message: '请求体格式错误' }));
46
+ }
47
+ });
48
+ return;
49
+ }
50
+
51
+ // 直接合并入口(兼容旧版)
52
+ if (pathname === '/merge') {
53
+ let body = '';
54
+ req.on('data', (chunk) => { body += chunk.toString(); });
55
+ req.on('end', async () => {
56
+ try {
57
+ const params = JSON.parse(body || '{}');
58
+ const logFileName = `${params.record_id || 'unknown'}_merge_${Date.now()}`;
59
+ await handleMerge(params, res, logFileName);
60
+ } catch (e) {
61
+ res.statusCode = 400;
62
+ res.setHeader('Content-Type', 'application/json');
63
+ res.end(JSON.stringify({ result_code: 1003, message: '请求体格式错误' }));
64
+ }
65
+ });
66
+ return;
67
+ }
68
+
69
+ // 未知路由
70
+ res.statusCode = 404;
71
+ res.setHeader('Content-Type', 'application/json');
72
+ res.end(JSON.stringify({ result_code: 1000, message: '路由不存在' }));
73
+ } else if (req.method === 'GET') {
74
+ // 健康检查
75
+ if (pathname === '/' || pathname === '/health') {
76
+ res.setHeader('Content-Type', 'application/json');
77
+ res.end(JSON.stringify({ result_code: 0, message: 'rrvideo-server is running' }));
78
+ return;
79
+ }
80
+
81
+ res.statusCode = 404;
82
+ res.setHeader('Content-Type', 'application/json');
83
+ res.end(JSON.stringify({ result_code: 1000, message: '路由不存在' }));
84
+ } else {
85
+ res.statusCode = 405;
86
+ res.setHeader('Content-Type', 'application/json');
87
+ res.end(JSON.stringify({ result_code: 1000, message: '方法不允许' }));
88
+ }
89
+ } catch (error) {
90
+ console.error(`处理请求时出错: ${error.message}`);
91
+ res.statusCode = 500;
92
+ res.setHeader('Content-Type', 'application/json');
93
+ res.end(JSON.stringify({ result_code: 1000, message: '服务器错误' }));
94
+ }
95
+ });
96
+
97
+ // 启动服务器
98
+ if (require.main === module) {
99
+ const PORT = config.get('server.port', 24203);
100
+ const HOST = config.get('server.host', '0.0.0.0');
101
+
102
+ server.listen(PORT, HOST, () => {
103
+ console.log(`rrvideo-server listening on ${HOST}:${PORT}`);
104
+ console.log(`Storage type: ${config.get('storage.type')}`);
105
+ console.log(`Log level: ${config.get('log.level')}`);
106
+ });
107
+
108
+ // 程序异常或提前终止时的处理
109
+ process.on('exit', (code) => {
110
+ console.log(`进程退出,代码: ${code}`);
111
+ logger.flushAll();
112
+ });
113
+
114
+ process.on('SIGINT', () => {
115
+ console.log('收到 SIGINT 信号,正在关闭...');
116
+ logger.flushAll();
117
+ process.exit(0);
118
+ });
119
+
120
+ process.on('SIGTERM', () => {
121
+ console.log('收到 SIGTERM 信号,正在关闭...');
122
+ logger.flushAll();
123
+ process.exit(0);
124
+ });
125
+
126
+ process.on('uncaughtException', (err) => {
127
+ console.error(`未捕获的异常: ${err.message}`);
128
+ console.error(err.stack);
129
+ logger.flushAll();
130
+ process.exit(1);
131
+ });
132
+
133
+ process.on('unhandledRejection', (reason, promise) => {
134
+ console.error(`未处理的 Promise 拒绝: ${reason}`);
135
+ });
136
+ }
137
+
138
+ module.exports = { server };
package/src/logger.js ADDED
@@ -0,0 +1,144 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class Logger {
5
+ constructor() {
6
+ this.buffers = new Map(); // fileKey -> { logs: [], timer: null }
7
+ this.flushInterval = 2000; // 2秒
8
+ this.bufferMaxSize = 500; // 500条
9
+ this.baseDir = path.join(__dirname, '../logs');
10
+ }
11
+
12
+ ensureDir(dirPath) {
13
+ if (!fs.existsSync(dirPath)) {
14
+ fs.mkdirSync(dirPath, { recursive: true });
15
+ }
16
+ }
17
+
18
+ getCurrentDate() {
19
+ const date = new Date();
20
+ const year = date.getFullYear();
21
+ const month = String(date.getMonth() + 1).padStart(2, '0');
22
+ const day = String(date.getDate()).padStart(2, '0');
23
+ return `${year}-${month}-${day}`;
24
+ }
25
+
26
+ getCurrentTime() {
27
+ const date = new Date();
28
+ const hour = String(date.getHours()).padStart(2, '0');
29
+ const minute = String(date.getMinutes()).padStart(2, '0');
30
+ const second = String(date.getSeconds()).padStart(2, '0');
31
+ return `${hour}:${minute}:${second}`;
32
+ }
33
+
34
+ getLogFilePath(logType, fileName) {
35
+ const dateStr = this.getCurrentDate();
36
+ const logDir = path.join(this.baseDir, logType, dateStr);
37
+ this.ensureDir(logDir);
38
+ return path.join(logDir, `${fileName}.log`);
39
+ }
40
+
41
+ flushBuffer(fileKey) {
42
+ const buf = this.buffers.get(fileKey);
43
+ if (!buf || buf.logs.length === 0) return;
44
+
45
+ try {
46
+ const { logType, fileName, logs } = buf;
47
+ const filePath = this.getLogFilePath(logType, fileName);
48
+ const content = logs.join('\n') + '\n';
49
+ fs.appendFileSync(filePath, content);
50
+ buf.logs = [];
51
+
52
+ // 清除定时器
53
+ if (buf.timer) {
54
+ clearTimeout(buf.timer);
55
+ buf.timer = null;
56
+ }
57
+ } catch (error) {
58
+ console.error(`[Logger] 刷新日志失败 [${fileKey}]:`, error.message);
59
+ }
60
+ }
61
+
62
+ scheduleFlush(fileKey) {
63
+ const buf = this.buffers.get(fileKey);
64
+ if (!buf || buf.timer) return;
65
+
66
+ buf.timer = setTimeout(() => {
67
+ this.flushBuffer(fileKey);
68
+ }, this.flushInterval);
69
+ }
70
+
71
+ log(logType, fileName, message, level = 'INFO') {
72
+ try {
73
+ const timestamp = this.getCurrentTime();
74
+ const lines = String(message).split('\n');
75
+ const fileKey = `${logType}::${fileName}`;
76
+
77
+ if (!this.buffers.has(fileKey)) {
78
+ this.buffers.set(fileKey, {
79
+ logType,
80
+ fileName,
81
+ logs: [],
82
+ timer: null
83
+ });
84
+ }
85
+
86
+ const buf = this.buffers.get(fileKey);
87
+
88
+ for (const line of lines) {
89
+ if (!line.trim()) continue;
90
+ const logLine = `[${timestamp}] [${level}] ${line}`;
91
+ console.log(logLine); // 同时输出到控制台
92
+ buf.logs.push(logLine);
93
+ }
94
+
95
+ // 缓冲区满立即刷新
96
+ if (buf.logs.length >= this.bufferMaxSize) {
97
+ this.flushBuffer(fileKey);
98
+ } else {
99
+ this.scheduleFlush(fileKey);
100
+ }
101
+ } catch (error) {
102
+ console.error(`[Logger] 写日志失败:`, error.message);
103
+ }
104
+ }
105
+
106
+ flushImmediate(logType, fileName) {
107
+ const fileKey = `${logType}::${fileName}`;
108
+ this.flushBuffer(fileKey);
109
+ }
110
+
111
+ flushAll() {
112
+ for (const fileKey of this.buffers.keys()) {
113
+ this.flushBuffer(fileKey);
114
+ }
115
+ }
116
+ }
117
+
118
+ // 单例模式
119
+ const logger = new Logger();
120
+
121
+ // 进程退出时刷新所有日志
122
+ process.on('exit', () => {
123
+ logger.flushAll();
124
+ });
125
+
126
+ process.on('SIGINT', () => {
127
+ logger.flushAll();
128
+ process.exit(0);
129
+ });
130
+
131
+ process.on('SIGTERM', () => {
132
+ logger.flushAll();
133
+ process.exit(0);
134
+ });
135
+
136
+ // 导出便捷方法
137
+ module.exports = {
138
+ info: (message, fileName, logType = 'convert') => logger.log(logType, fileName, message, 'INFO'),
139
+ debug: (message, fileName, logType = 'convert') => logger.log(logType, fileName, message, 'DEBUG'),
140
+ warn: (message, fileName, logType = 'convert') => logger.log(logType, fileName, message, 'WARN'),
141
+ error: (message, fileName, logType = 'convert') => logger.log(logType, fileName, message, 'ERROR'),
142
+ flush: (fileName, logType = 'convert') => logger.flushImmediate(logType, fileName),
143
+ flushAll: () => logger.flushAll()
144
+ };