mm_os 4.2.4 → 4.2.7

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.
Files changed (77) hide show
  1. package/README.md +1 -1
  2. package/README_EN.md +2 -2
  3. package/adapter/adapter.js +105 -52
  4. package/adapter/custom_persistence.js +32 -31
  5. package/adapter/mqtt.js +59 -79
  6. package/adapter/socket.js +188 -58
  7. package/adapter/web.js +92 -68
  8. package/adapter/websocket.js +43 -146
  9. package/com/api/drive.js +732 -708
  10. package/com/api/index.js +196 -198
  11. package/com/api/script.tpl.js +24 -24
  12. package/com/cmd/config.tpl.json +48 -122
  13. package/com/cmd/drive.js +176 -1548
  14. package/com/cmd/index.js +287 -1066
  15. package/com/cmd/job/config.json +66 -0
  16. package/com/cmd/job/index.js +241 -0
  17. package/com/cmd/job/test.js +188 -0
  18. package/com/cmd/script.tpl.js +12 -33
  19. package/com/db/drive.js +2206 -2019
  20. package/com/db/index.js +241 -242
  21. package/com/event/config.tpl.json +3 -1
  22. package/com/event/drive.js +3 -1
  23. package/com/event/index.js +73 -46
  24. package/com/event/script.tpl.js +22 -22
  25. package/com/mqtt/config.tpl.json +16 -8
  26. package/com/mqtt/drive.js +696 -676
  27. package/com/mqtt/index.js +1012 -822
  28. package/com/mqtt/index.test.js +503 -0
  29. package/com/mqtt/mm_mqtt.js +446 -420
  30. package/com/mqtt/script.tpl.js +3 -7
  31. package/com/nav/drive.js +7 -7
  32. package/com/nav/index.js +228 -231
  33. package/com/param/index.js +19 -17
  34. package/com/pendant/index.js +467 -441
  35. package/com/socket/drive.js +679 -651
  36. package/com/socket/index.js +94 -48
  37. package/com/sql/drive.js +1283 -1259
  38. package/com/sql/index.js +152 -150
  39. package/com/static/drive.js +1 -1
  40. package/com/static/index.js +232 -226
  41. package/com/static/script.tpl.js +25 -25
  42. package/com/task/drive.js +428 -405
  43. package/com/task/index.js +154 -148
  44. package/com/task/script.tpl.js +37 -34
  45. package/com/template/drive.js +1 -1
  46. package/com/template/index.js +141 -138
  47. package/com.js +87 -6
  48. package/common/middleware/web_after/index.js +5 -11
  49. package/common/middleware/web_base/index.js +1 -1
  50. package/common/middleware/web_cors/index.js +1 -1
  51. package/common/middleware/web_error/index.js +131 -119
  52. package/common/middleware/web_error/middleware.json +2 -1
  53. package/common/middleware/web_logger/index.js +1 -1
  54. package/common/middleware/web_main/index.js +2 -0
  55. package/common/middleware/web_static/index.js +2 -2
  56. package/common/middleware/web_waf/index.js +1 -1
  57. package/core/app/config.tpl.json +4 -4
  58. package/core/app/index.js +710 -534
  59. package/core/middleware/index.js +1 -9
  60. package/core/mod/config.tpl.json +1 -1
  61. package/core/model/config.tpl.json +1 -1
  62. package/core/plugin/config.tpl.json +15 -3
  63. package/core/plugin/index.js +31 -0
  64. package/core/pusher/index.js +158 -161
  65. package/core/scene/loop.js +1306 -1255
  66. package/core/store/index.js +1794 -1755
  67. package/core/store/sql.js +1488 -1464
  68. package/core/zone/bat/index.js +744 -728
  69. package/core/zone/config.tpl.json +3 -3
  70. package/core/zone/index.js +655 -616
  71. package/os.js +0 -4
  72. package/package.json +16 -16
  73. package/server.js +583 -427
  74. package/com/cmd/nlp.js +0 -525
  75. package/core/app/index copy.js +0 -611
  76. package/software.js +0 -194
  77. package/tcp.js +0 -279
package/core/app/index.js CHANGED
@@ -1,534 +1,710 @@
1
- const compressing = require('compressing');
2
- const {
3
- Manager,
4
- Drive
5
- } = require('mm_machine');
6
- const {
7
- Plugin
8
- } = require('../plugin/index.js');
9
-
10
- /**
11
- * 应用
12
- */
13
- class App extends Drive {
14
- static config = {
15
- 'name': 'default',
16
- 'version': '1.0.0',
17
- 'title': '应用',
18
- 'description': '应用',
19
- 'author': 'qww',
20
- 'scope': 'server',
21
- 'dir': '',
22
- // // 游戏配置,非游戏模块不需要配置
23
- // 'game': {
24
- // // 游戏类型
25
- // 'type': 'card_game'
26
- // },
27
- 'dependencies': [],
28
- // // 压缩目录
29
- // 'zip_dir': './static/file/zip',
30
- // // 解压目录
31
- // 'unzip_dir': './static/file/unzip',
32
- // 自定义数据库 - 1 开启 0 关闭
33
- 'diy_sql': 0,
34
- // 自定义缓存 - 1 开启 0 关闭
35
- 'diy_cache': 0,
36
- // sql 配置 - 可以没有,没有则使用默认数据库
37
- 'sql': {
38
- // 数据库方式
39
- 'way': 'mysql',
40
- 'mysql': {
41
- // 数据库主机
42
- 'host': '127.0.0.1',
43
- // 数据库端口
44
- 'port': 3306,
45
- // 数据库用户名
46
- 'username': 'root',
47
- // 数据库密码
48
- 'password': 'Asd159357',
49
- // 数据库名称
50
- 'database': 'mos'
51
- }
52
- },
53
- // 缓存配置 - 可以没有,没有则使用默认缓存
54
- 'cache': {
55
- // 缓存方式方式
56
- 'way': 'redis',
57
- 'redis': {
58
- // 缓存主机
59
- 'host': '127.0.0.1',
60
- // 缓存端口
61
- 'port': 6379,
62
- // 缓存密码
63
- 'password': 'asd159357',
64
- // 缓存数据库
65
- 'database': 0
66
- }
67
- }
68
- };
69
- /**
70
- * 构造函数
71
- * @param {object} config 配置参数
72
- * @param {object} parent 父对象
73
- */
74
- constructor(config, parent) {
75
- super({ ...App.config, ...config || {} }, parent);
76
- // 管理器集合
77
- this.manager = {};
78
-
79
- // 插件,用于功能扩展
80
- this.plugin = {};
81
- }
82
- }
83
-
84
- /**
85
- * 预置
86
- */
87
- App.prototype._preset = function () {
88
- // 数据库,调用统一数据库接口,也允许重定义数据库
89
- this.sql = null;
90
- // 缓存,调用统一缓存接口,也允许重定义缓存
91
- this.cache = null;
92
- // 文件管理,调用统一文件管理接口,也允许重定义文件管理
93
- this.filer = null;
94
-
95
- /**
96
- * 获取服务器实例
97
- * @returns {object} 服务器实例对象
98
- */
99
- this.getServer = function () {
100
- return $.server;
101
- };
102
- };
103
-
104
- /**
105
- * 创建管理器实例
106
- * @param {string} name 管理器名称
107
- * @param {string} title 管理器标题
108
- * @param {Function} cls 管理器类
109
- * @returns {object} 管理器实例
110
- */
111
- App.prototype._createManager = function (name, title, cls) {
112
- let dir = this.getDir();
113
- var manager = new Manager({
114
- name: name,
115
- title: title,
116
- filename: name + '.json',
117
- tpl_dir: `../${name}/`.fullname(__dirname),
118
- base_dir: '',
119
- dir: `./${name}`.fullname(dir)
120
- }, this, this[name], cls);
121
- this.manager[name] = manager;
122
- return manager;
123
- };
124
-
125
- /**
126
- * 初始化管理器集合
127
- * @returns {Promise<void>} 初始化完成
128
- */
129
- App.prototype._initManager = async function () {
130
- // console.time('[TIMING] App._initManager');
131
- // 管理器配置列表
132
- var mgr_configs = [
133
- { name: 'plugin', title: '插件', Module: Plugin }
134
- ];
135
-
136
- // 管理器实例集合
137
- var managers = [];
138
-
139
- // 使用for循环初始化所有管理器
140
- for (var i = 0; i < mgr_configs.length; i++) {
141
- var config = mgr_configs[i];
142
- var manager = this._createManager(config.name, config.title, config.Module);
143
- managers.push(manager);
144
- }
145
-
146
- // 并行执行所有管理器的初始化操作
147
- var list = [];
148
- for (var j = 0; j < managers.length; j++) {
149
- list.push(managers[j].do('init'));
150
- }
151
-
152
- await Promise.all(list);
153
- // console.timeEnd('[TIMING] App._initManager');
154
- };
155
-
156
- /**
157
- * 加载资源
158
- * @returns {Promise<void>} 加载完成
159
- */
160
- App.prototype._loadSources = async function () {
161
- await this.manager.plugin.runAll('load');
162
- };
163
-
164
- /**
165
- * 初始化资源
166
- * @returns {Promise<void>} 初始化完成
167
- */
168
- App.prototype._initSources = async function () {
169
- await this.manager.plugin.runAll('init', this, this.getEventer(), this.getLogger());
170
- };
171
-
172
- /**
173
- * 初始化核心
174
- * @param {object} server 服务器
175
- * @param {object} eventer 事件总线
176
- * @param {object} logger 日志管理器
177
- * @private
178
- */
179
- App.prototype._initCore = async function (server, eventer, logger) {
180
- if (logger) {
181
- this.setLogger(logger);
182
- }
183
- if (eventer) {
184
- this.getEventer = function () {
185
- return eventer;
186
- };
187
- }
188
- if (server) {
189
- this.getServer = function () {
190
- return server;
191
- };
192
- }
193
- // 初始化基础设施
194
- await this._initBase();
195
- // 初始化管理
196
- await this._initManager();
197
- // 加载资源
198
- await this._loadSources();
199
- // 验证依赖关系
200
- this._validateDep();
201
- };
202
-
203
- /**
204
- * 初始化基础设施
205
- * @private
206
- */
207
- App.prototype._initBase = async function () {
208
- // 初始化文件处理器
209
- this.filer = this.getServer().filer;
210
-
211
- // 初始化数据库
212
- if (this.config.diy_sql) {
213
- var cg = this.config.sql || $.config.get('sql');
214
- this.sql = sqlAdmin(this.config.name, cg);
215
- await this.sql.open();
216
- }
217
- else {
218
- this.sql = this.getServer().sql;
219
- }
220
-
221
- // 初始化缓存
222
- if (this.config.diy_cache) {
223
- var cg = this.config.cache || $.config.get('cache');
224
- this.cache = cacheAdmin(this.config.name, cg);
225
- await this.cache.connect();
226
- }
227
- else {
228
- this.cache = this.getServer().cache;
229
- }
230
- };
231
-
232
- /**
233
- * 启动资源
234
- */
235
- App.prototype._startSources = async function () {
236
- await this.manager.plugin.runAll('start');
237
- };
238
-
239
- /**
240
- * 启动核心
241
- * @private
242
- */
243
- App.prototype._startCore = async function () {
244
- // 初始化资源
245
- await this._initSources();
246
- // 启动资源
247
- await this._startSources();
248
- };
249
-
250
- /**
251
- * 验证依赖关系
252
- * @private
253
- */
254
- App.prototype._validateDep = function () {
255
- var required_managers = ['plugin'];
256
-
257
- for (var name of required_managers) {
258
- if (!this.manager[name]) {
259
- throw new Error(`缺少必要的管理器: ${name}`);
260
- }
261
- }
262
- };
263
-
264
- /**
265
- * 压缩插件
266
- * @param {string} name 插件名称
267
- * @returns {string} 打包成功返回压缩包文件地址
268
- */
269
- App.prototype.zipPlugin = async function (name) {
270
- if (!name || typeof name !== 'string') {
271
- throw new TypeError('无效的插件名称');
272
- }
273
- var plugin = this.plugin[name];
274
- if (!plugin) {
275
- throw new Error(`插件 ${name} 不存在`);
276
- }
277
- var dir = plugin.dir;
278
- var file = ('./' + name + '.zip').fullname('/static/file/zip/');
279
- return await this.zip(file, dir);
280
- };
281
-
282
- /**
283
- * 压缩插件
284
- * @param {string} file 插件压缩文件
285
- * @param {string} dir 要压缩的目录
286
- * @returns {string} 打包成功返回压缩包文件地址
287
- */
288
- App.prototype.zip = async function (file, dir = '/static/file/zip/') {
289
- file.addDir();
290
- await compressing.zip.compressDir(dir, file);
291
- if (file.hasFile()) {
292
- return file;
293
- }
294
- return null;
295
- };
296
-
297
- /**
298
- * 解压插件
299
- * @param {object} file 插件压缩文件
300
- * @param {string} unzip_dir 要解压的目录
301
- * @returns {object} 返回解压结果
302
- */
303
- App.prototype.unzip = async function (file, unzip_dir = '/cache/unzip/') {
304
- if (!file) {
305
- throw new TypeError('无效的文件路径');
306
- }
307
- var name = file.basename().left('.');
308
- var dir = name.fullname(unzip_dir);
309
-
310
- try {
311
- // 确保目标目录存在
312
- dir.addDir();
313
- // 解压文件到目标目录
314
- await compressing.zip.uncompress(file, dir);
315
- // 查找目标目录
316
- var target_dir = this._findTargetDir(dir, name);
317
- // 如果找到目标目录,直接使用它作为解压结果
318
- if (target_dir) {
319
- var config_file = './plugin.json'.fullname(target_dir);
320
- if (config_file.hasFile()) {
321
- return { file, config_file, dir: target_dir, unzip_dir: dir };
322
- }
323
- }
324
- return { file, dir, unzip_dir: dir };
325
- } catch (err) {
326
- this.log('error', '插件解压失败!', err);
327
- // 清理失败的解压目录
328
- if (dir.hasDir()) {
329
- dir.delDir();
330
- }
331
- return null;
332
- }
333
- };
334
-
335
- /**
336
- * 查找目标目录
337
- * @param {string} dir 解压目录
338
- * @param {string} name 插件名称
339
- * @returns {string|null} 目标目录路径
340
- * @private
341
- */
342
- App.prototype._findTargetDir = function (dir, name) {
343
- var dir_files = $.dir.get(dir);
344
- if (!dir_files || dir_files.length === 0) {
345
- return null;
346
- }
347
- for (var i = 0; i < dir_files.length; i++) {
348
- var item = dir_files[i];
349
- if (item.hasDir() && item.basename() === name) {
350
- var plugin_json = './plugin.json'.fullname(item);
351
- if (plugin_json.hasFile()) {
352
- return item;
353
- }
354
- }
355
- }
356
- return null;
357
- };
358
-
359
- /**
360
- * 卸载插件
361
- * @param {string} name 插件名称
362
- * @returns {string} 返回卸载路径
363
- */
364
- App.prototype.uninstallPlugin = async function (name) {
365
- if (!name || typeof name !== 'string') {
366
- this.log('error', '卸载失败:无效的插件名称');
367
- return null;
368
- }
369
-
370
- var plugin = this.manager.plugin.get(name);
371
- if (!plugin) {
372
- this.log('error', '卸载失败:插件不存在');
373
- return null;
374
- }
375
-
376
- var dir = plugin.dir;
377
-
378
- // 执行卸载脚本
379
- if (plugin.call) {
380
- await plugin.call('uninstall');
381
- }
382
- try {
383
- this.manager.plugin.del(name);
384
- dir.delDir();
385
- } catch (err) {
386
- this.log('error', '卸载失败:', err);
387
- return null;
388
- }
389
- };
390
-
391
- /**
392
- * 复制目录
393
- * @param {string} dir_src 原路径
394
- * @param {string} dir 新路径
395
- * @returns {object} 返回复制结果
396
- */
397
- App.prototype.copyDir = function (dir_src, dir) {
398
- return new Promise((resolve, reject) => {
399
- if (!dir_src || !dir) {
400
- this.log('error', '复制目录失败:无效的目录路径');
401
- reject(new Error('无效的目录路径'));
402
- return;
403
- }
404
-
405
- if (!dir_src.hasDir()) {
406
- this.log('error', '复制目录失败:源目录不存在');
407
- reject(new Error('源目录不存在'));
408
- return;
409
- }
410
-
411
- try {
412
- // 确保目标目录存在
413
- dir.addDir();
414
-
415
- $.dir.copy(dir_src, dir, (err) => {
416
- if (err) {
417
- this.log('error', '复制目录失败:', err);
418
- reject(err);
419
- } else {
420
- resolve({
421
- dir_src,
422
- dir
423
- });
424
- }
425
- });
426
- } catch (err) {
427
- this.log('error', '复制目录失败:', err);
428
- reject(err);
429
- }
430
- });
431
- };
432
-
433
- /**
434
- * 解压插件
435
- * @param {object} file 插件压缩文件
436
- * @returns {object} 返回解压结果
437
- */
438
- App.prototype._unzipPlugin = async function (file) {
439
- var ret = await this.unzip(file);
440
- if (!ret) {
441
- return null;
442
- }
443
-
444
- // 直接使用解压目录作为 dir_src
445
- var dir_src = ret.dir;
446
-
447
- var config_file = './plugin.json'.fullname(dir_src);
448
-
449
- if (!config_file.hasFile()) {
450
- throw new Error('安装失败:找不到配置文件');
451
- }
452
-
453
- var config = config_file.loadJson();
454
- if (!config) {
455
- throw new Error('安装失败:配置文件无效');
456
- }
457
-
458
- var app = config.app || this.config.name;
459
- var name = config.name;
460
-
461
- if (!name) {
462
- throw new Error('安装失败:配置文件中缺少名称字段');
463
- }
464
-
465
- var dir = `/app/${app}/plugin/${name}`;
466
-
467
- await this.copyDir(dir_src, dir);
468
- ret.app = app;
469
- ret.name = name;
470
- ret.dir = dir;
471
- return ret;
472
- };
473
-
474
- /**
475
- * 安装插件
476
- * @param {object} info 插件信息
477
- * @returns {object} 返回安装结果
478
- */
479
- App.prototype._installPlugin = async function (info) {
480
- if (!info) {
481
- return null;
482
- }
483
- if (!info.config_file) {
484
- throw new Error('安装失败:找不到配置文件');
485
- }
486
-
487
- var manager = this.manager.plugin;
488
- if (info.app !== this.config.name) {
489
- var app = this.getServer().app[info.app];
490
- if (!app) {
491
- throw new Error(`安装失败:应用 ${info.app} 不存在`);
492
- }
493
- manager = app.manager.plugin;
494
- }
495
- // 只加载当前插件,而不是重载所有插件
496
- await manager.load(info.config_file);
497
-
498
- // 执行安装方法
499
- var plugin = manager.getMod(info.name);
500
- if (plugin && plugin.call) {
501
- await plugin.call('install');
502
- }
503
- return true;
504
- };
505
-
506
- /**
507
- * 安装插件
508
- * @param {string} file 插件压缩文件路径
509
- * @returns {object} 返回安装结果
510
- */
511
- App.prototype.installPlugin = async function (file) {
512
- if (!file) {
513
- throw new TypeError('无效的文件路径');
514
- }
515
-
516
- // 解压插件
517
- var info = await this._unzipPlugin(file);
518
- if (!info) {
519
- return null;
520
- }
521
- // 安装插件
522
- await this._installPlugin(info);
523
-
524
- // 清理临时目录
525
- setTimeout(() => {
526
- if (info.unzip_dir && info.unzip_dir.hasDir()) {
527
- info.unzip_dir.delDir();
528
- }
529
- }, 3000);
530
- info.zip_file = file;
531
- return info;
532
- };
533
-
534
- exports.App = App;
1
+ const compressing = require('compressing');
2
+ const { Manager, Drive } = require('mm_machine');
3
+ const { Plugin } = require('../plugin/index.js');
4
+
5
+ /**
6
+ * 应用
7
+ */
8
+ class App extends Drive {
9
+ static config = {
10
+ name: 'default',
11
+ version: '1.0.0',
12
+ title: '应用',
13
+ description: '应用',
14
+ author: 'qww',
15
+ scope: 'server',
16
+ dir: '',
17
+ // 运行模式: 1=生产, 3=开发热重载, 根据 NODE_ENV 自动切换
18
+ mode: process.env.NODE_ENV === 'production' ? 1 : 3,
19
+ // // 游戏配置,非游戏模块不需要配置
20
+ // 'game': {
21
+ // // 游戏类型
22
+ // 'type': 'card_game'
23
+ // },
24
+ dependencies: [],
25
+ // // 压缩目录
26
+ // 'zip_dir': './static/file/zip',
27
+ // // 解压目录
28
+ // 'unzip_dir': './static/file/unzip',
29
+ // 自定义数据库 - 1 开启 0 关闭
30
+ diy_sql: 0,
31
+ // 自定义缓存 - 1 开启 0 关闭
32
+ diy_cache: 0,
33
+ // sql 配置 - 可以没有,没有则使用默认数据库
34
+ sql: {
35
+ // 数据库方式
36
+ way: 'mysql',
37
+ mysql: {
38
+ // 数据库主机
39
+ host: '127.0.0.1',
40
+ // 数据库端口
41
+ port: 3306,
42
+ // 数据库用户名
43
+ username: 'root',
44
+ // 数据库密码
45
+ password: process.env.DB_PASSWORD || '',
46
+ // 数据库名称
47
+ database: 'mm',
48
+ },
49
+ },
50
+ // 缓存配置 - 可以没有,没有则使用默认缓存
51
+ cache: {
52
+ // 缓存方式方式
53
+ way: 'redis',
54
+ redis: {
55
+ // 缓存主机
56
+ host: '127.0.0.1',
57
+ // 缓存端口
58
+ port: 6379,
59
+ // 缓存密码
60
+ password: process.env.REDIS_PASSWORD || '',
61
+ // 缓存数据库
62
+ database: 0,
63
+ },
64
+ },
65
+ };
66
+ /**
67
+ * 构造函数
68
+ * @param {object} config 配置参数
69
+ * @param {object} parent 父对象
70
+ */
71
+ constructor(config, parent) {
72
+ super({ ...App.config, ...(config || {}) }, parent);
73
+ // 管理器集合
74
+ this.manager = {};
75
+
76
+ // 插件,用于功能扩展
77
+ this.plugin = {};
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 预置
83
+ */
84
+ App.prototype._preset = function () {
85
+ // 数据库,调用统一数据库接口,也允许重定义数据库
86
+ this.sql = null;
87
+ // 缓存,调用统一缓存接口,也允许重定义缓存
88
+ this.cache = null;
89
+ // 文件管理,调用统一文件管理接口,也允许重定义文件管理
90
+ this.filer = null;
91
+
92
+ /**
93
+ * 获取服务器实例
94
+ * @returns {object} 服务器实例对象
95
+ */
96
+ this.getServer = function () {
97
+ return $.server;
98
+ };
99
+ };
100
+
101
+ /**
102
+ * 创建管理器实例
103
+ * @param {string} name 管理器名称
104
+ * @param {string} title 管理器标题
105
+ * @param {Function} cls 管理器类
106
+ * @returns {object} 管理器实例
107
+ */
108
+ App.prototype._createManager = function (name, title, cls) {
109
+ let dir = this.getDir();
110
+ var manager = new Manager(
111
+ {
112
+ name: name,
113
+ title: title,
114
+ filename: name + '.json',
115
+ tpl_dir: `../${name}/`.fullname(__dirname),
116
+ base_dir: '',
117
+ dir: `./${name}`.fullname(dir),
118
+ },
119
+ this,
120
+ this[name],
121
+ cls,
122
+ );
123
+ this.manager[name] = manager;
124
+ return manager;
125
+ };
126
+
127
+ /**
128
+ * 初始化管理器集合
129
+ * @returns {Promise<void>} 初始化完成
130
+ */
131
+ App.prototype._initManager = async function () {
132
+ // 管理器配置列表
133
+ var mgr_configs = [{ name: 'plugin', title: '插件', Module: Plugin }];
134
+
135
+ // 管理器实例集合
136
+ var managers = [];
137
+
138
+ // 使用for循环初始化所有管理器
139
+ for (var i = 0; i < mgr_configs.length; i++) {
140
+ var config = mgr_configs[i];
141
+ var manager = this._createManager(config.name, config.title, config.Module);
142
+ managers.push(manager);
143
+ }
144
+
145
+ // 并行执行所有管理器的初始化操作
146
+ var list = [];
147
+ for (var j = 0; j < managers.length; j++) {
148
+ list.push(managers[j].do('load'));
149
+ }
150
+
151
+ await Promise.all(list);
152
+ };
153
+
154
+ /**
155
+ * 加载资源
156
+ * @returns {Promise<void>} 加载完成
157
+ */
158
+ App.prototype._loadSources = async function () {
159
+ await this.manager.plugin.runAll('load');
160
+ };
161
+
162
+ /**
163
+ * 初始化资源
164
+ * @returns {Promise<void>} 初始化完成
165
+ */
166
+ App.prototype._initSources = async function () {
167
+ await this.manager.plugin.runAll(
168
+ 'init',
169
+ this,
170
+ this.getEventer(),
171
+ this.getLogger(),
172
+ );
173
+ };
174
+
175
+ /**
176
+ * 初始化核心
177
+ * @param {object} server 服务器
178
+ * @param {object} eventer 事件总线
179
+ * @param {object} logger 日志管理器
180
+ * @private
181
+ */
182
+ App.prototype._initCore = async function (server, eventer, logger) {
183
+ if (logger) {
184
+ this.setLogger(logger);
185
+ }
186
+ if (eventer) {
187
+ this.getEventer = function () {
188
+ return eventer;
189
+ };
190
+ }
191
+ if (server) {
192
+ this.getServer = function () {
193
+ return server;
194
+ };
195
+ }
196
+ // 初始化基础设施
197
+ await this._initBase();
198
+ // 初始化管理
199
+ await this._initManager();
200
+ // 加载资源
201
+ await this._loadSources();
202
+ // 验证依赖关系
203
+ this._validateDep();
204
+ };
205
+
206
+ /**
207
+ * 初始化基础设施
208
+ * @private
209
+ */
210
+ App.prototype._initBase = async function () {
211
+ // 初始化文件处理器
212
+ this.filer = this.getServer().filer;
213
+
214
+ // 初始化数据库
215
+ if (this.config.diy_sql) {
216
+ var cg = this.config.sql || $.config.get('sql');
217
+ this.sql = sqlAdmin(this.config.name, cg);
218
+ await this.sql.open();
219
+ } else {
220
+ this.sql = this.getServer().sql;
221
+ }
222
+
223
+ // 初始化缓存
224
+ if (this.config.diy_cache) {
225
+ var cg = this.config.cache || $.config.get('cache');
226
+ this.cache = cacheAdmin(this.config.name, cg);
227
+ await this.cache.connect();
228
+ } else {
229
+ this.cache = this.getServer().cache;
230
+ }
231
+ };
232
+
233
+ /**
234
+ * 启动资源
235
+ */
236
+ App.prototype._startSources = async function () {
237
+ await this.manager.plugin.runAll('start');
238
+ };
239
+
240
+ /**
241
+ * 启动核心
242
+ * @private
243
+ */
244
+ App.prototype._startCore = async function () {
245
+ // 初始化资源
246
+ await this._initSources();
247
+ // 启动资源
248
+ await this._startSources();
249
+ // 注册信号处理和全局错误处理
250
+ this._signalHandle();
251
+ };
252
+
253
+ /**
254
+ * 验证依赖关系
255
+ * @private
256
+ */
257
+ App.prototype._validateDep = function () {
258
+ var required_managers = ['plugin'];
259
+
260
+ for (var name of required_managers) {
261
+ if (!this.manager[name]) {
262
+ throw new Error(`缺少必要的管理器: ${name}`);
263
+ }
264
+ }
265
+ };
266
+
267
+ /**
268
+ * 获取压缩目录
269
+ * @returns {string} 压缩目录
270
+ */
271
+ App.prototype.getZipDir = function () {
272
+ return '/static/file/zip/'.fullname();
273
+ };
274
+
275
+ /**
276
+ * 压缩插件
277
+ * @param {string} name 插件名称
278
+ * @returns {string} 打包成功返回压缩包文件地址
279
+ */
280
+ App.prototype.zipPlugin = async function (name) {
281
+ if (!name || typeof name !== 'string') {
282
+ throw new TypeError('无效的插件名称');
283
+ }
284
+ var plugin = this.plugin[name];
285
+ if (!plugin) {
286
+ throw new Error(`插件 ${name} 不存在`);
287
+ }
288
+ var dir = plugin.getDir();
289
+ var file = ('./' + name + '.zip').fullname(this.getZipDir());
290
+ return await this.zip(file, dir);
291
+ };
292
+
293
+ /**
294
+ * 压缩目录
295
+ * @param {string} file 压缩文件
296
+ * @param {string} dir 要压缩的目录
297
+ * @returns {string} 打包成功返回压缩包文件路径
298
+ */
299
+ App.prototype.zip = async function (file, dir) {
300
+ var target_dir = dir || this.getZipDir();
301
+ file.addDir();
302
+ if (!target_dir.hasDir()) {
303
+ target_dir.addDir();
304
+ }
305
+ // 重试压缩,避免文件在压缩过程中被修改导致 yazl 报错
306
+ var max_retry = 3;
307
+ for (var i = 0; i < max_retry; i++) {
308
+ try {
309
+ await compressing.zip.compressDir(target_dir, file);
310
+ break;
311
+ } catch (error) {
312
+ if (
313
+ i < max_retry - 1 &&
314
+ error.message.includes('unexpected number of bytes')
315
+ ) {
316
+ // 等待后重试
317
+ await new Promise((resolve) => setTimeout(resolve, 500));
318
+ continue;
319
+ }
320
+ throw error;
321
+ }
322
+ }
323
+ if (file.hasFile()) {
324
+ return file;
325
+ }
326
+ return null;
327
+ };
328
+
329
+ /**
330
+ * 解压插件
331
+ * @param {object} file 插件压缩文件
332
+ * @param {string} unzip_dir 要解压的目录
333
+ * @returns {object} 返回解压结果
334
+ */
335
+ App.prototype.unzip = async function (file, unzip_dir = '/cache/unzip/') {
336
+ if (!file) {
337
+ throw new TypeError('无效的文件路径');
338
+ }
339
+ var name = file.basename().left('.');
340
+ var dir = name.fullname(unzip_dir);
341
+
342
+ try {
343
+ // 先清理可能残留的旧解压目录,避免只读文件冲突
344
+ if (dir.hasDir()) {
345
+ dir.delDir();
346
+ }
347
+ // 确保目标目录存在
348
+ dir.addDir();
349
+ // 解压文件到目标目录
350
+ await compressing.zip.uncompress(file, dir);
351
+ // 查找目标目录
352
+ var target_dir = this._findTargetDir(dir, name);
353
+ // 如果找到目标目录,直接使用它作为解压结果
354
+ if (target_dir) {
355
+ var config_file = './plugin.json'.fullname(target_dir);
356
+ if (config_file.hasFile()) {
357
+ return { file, config_file, dir: target_dir, unzip_dir: dir };
358
+ }
359
+ }
360
+ return { file, dir, unzip_dir: dir };
361
+ } catch (err) {
362
+ this.log('error', '插件解压失败!', err);
363
+ // 清理失败的解压目录
364
+ if (dir.hasDir()) {
365
+ dir.delDir();
366
+ }
367
+ return null;
368
+ }
369
+ };
370
+
371
+ /**
372
+ * 查找目标目录
373
+ * @param {string} dir 解压目录
374
+ * @param {string} name 插件名称
375
+ * @returns {string|null} 目标目录路径
376
+ * @private
377
+ */
378
+ App.prototype._findTargetDir = function (dir, name) {
379
+ var dir_files = $.dir.get(dir);
380
+ if (!dir_files || dir_files.length === 0) {
381
+ return null;
382
+ }
383
+ for (var i = 0; i < dir_files.length; i++) {
384
+ var item = dir_files[i];
385
+ if (item.hasDir()) {
386
+ // 优先按名称匹配
387
+ if (item.basename() === name) {
388
+ var plugin_json = './plugin.json'.fullname(item);
389
+ if (plugin_json.hasFile()) {
390
+ return item;
391
+ }
392
+ }
393
+ }
394
+ }
395
+ // 名称匹配失败时,查找任意包含 plugin.json 的子目录
396
+ for (var i = 0; i < dir_files.length; i++) {
397
+ var item = dir_files[i];
398
+ if (item.hasDir()) {
399
+ var plugin_json = './plugin.json'.fullname(item);
400
+ if (plugin_json.hasFile()) {
401
+ return item;
402
+ }
403
+ }
404
+ }
405
+ return null;
406
+ };
407
+
408
+ /**
409
+ * 卸载插件
410
+ * @param {string} name 插件名称
411
+ * @returns {string} 返回卸载路径
412
+ */
413
+ App.prototype.uninstallPlugin = async function (name) {
414
+ if (!name || typeof name !== 'string') {
415
+ this.log('error', '卸载失败:无效的插件名称');
416
+ return null;
417
+ }
418
+
419
+ var plugin = this.manager.plugin.get(name);
420
+ if (!plugin) {
421
+ this.log('error', '卸载失败:插件不存在');
422
+ return null;
423
+ }
424
+
425
+ var dir = plugin.getDir();
426
+
427
+ // 执行卸载脚本
428
+ if (plugin.call) {
429
+ await plugin.call('uninstall');
430
+ }
431
+ try {
432
+ this.manager.plugin.del(name);
433
+ dir.delDir();
434
+ } catch (err) {
435
+ this.log('error', '卸载失败:', err);
436
+ return null;
437
+ }
438
+ };
439
+
440
+ /**
441
+ * 复制目录
442
+ * @param {string} dir_src 原路径
443
+ * @param {string} dir 新路径
444
+ * @returns {object} 返回复制结果
445
+ */
446
+ App.prototype.copyDir = function (dir_src, dir) {
447
+ return new Promise((resolve, reject) => {
448
+ if (!dir_src || !dir) {
449
+ this.log('error', '复制目录失败:无效的目录路径');
450
+ reject(new Error('无效的目录路径'));
451
+ return;
452
+ }
453
+
454
+ if (!dir_src.hasDir()) {
455
+ this.log('error', '复制目录失败:源目录不存在');
456
+ reject(new Error('源目录不存在'));
457
+ return;
458
+ }
459
+
460
+ try {
461
+ // 确保目标目录存在
462
+ dir.addDir();
463
+
464
+ $.dir.copy(dir_src, dir, (err) => {
465
+ if (err) {
466
+ this.log('error', '复制目录失败:', err);
467
+ reject(err);
468
+ } else {
469
+ resolve({
470
+ dir_src,
471
+ dir,
472
+ });
473
+ }
474
+ });
475
+ } catch (err) {
476
+ this.log('error', '复制目录失败:', err);
477
+ reject(err);
478
+ }
479
+ });
480
+ };
481
+
482
+ /**
483
+ * 解压插件
484
+ * @param {object} file 插件压缩文件
485
+ * @returns {object} 返回解压结果
486
+ */
487
+ App.prototype._unzipPlugin = async function (file) {
488
+ var ret = await this.unzip(file);
489
+ if (!ret) {
490
+ return null;
491
+ }
492
+
493
+ // 直接使用解压目录作为 dir_src
494
+ var dir_src = ret.dir;
495
+
496
+ var config_file = './plugin.json'.fullname(dir_src);
497
+
498
+ if (!config_file.hasFile()) {
499
+ throw new Error('安装失败:找不到配置文件');
500
+ }
501
+
502
+ var config = config_file.loadJson();
503
+ if (!config) {
504
+ throw new Error('安装失败:配置文件无效');
505
+ }
506
+
507
+ var app = config.app || this.config.name;
508
+ var name = config.name;
509
+
510
+ if (!name) {
511
+ throw new Error('安装失败:配置文件中缺少名称字段');
512
+ }
513
+
514
+ var dir = `/app/${app}/plugin/${name}`;
515
+
516
+ await this.copyDir(dir_src, dir);
517
+ ret.app = app;
518
+ ret.name = name;
519
+ ret.dir = dir;
520
+ return ret;
521
+ };
522
+
523
+ /**
524
+ * 安装插件
525
+ * @param {object} info 插件信息
526
+ * @returns {object} 返回安装结果
527
+ */
528
+ App.prototype._installPlugin = async function (info) {
529
+ if (!info) {
530
+ return null;
531
+ }
532
+ if (!info.config_file) {
533
+ throw new Error('安装失败:找不到配置文件');
534
+ }
535
+
536
+ var manager = this.manager.plugin;
537
+ if (info.app !== this.config.name) {
538
+ var app = this.getServer().app[info.app];
539
+ if (!app) {
540
+ throw new Error(`安装失败:应用 ${info.app} 不存在`);
541
+ }
542
+ manager = app.manager.plugin;
543
+ }
544
+ // 只加载当前插件,而不是重载所有插件
545
+ await manager.load(info.config_file);
546
+
547
+ // 执行安装方法
548
+ var plugin = manager.getMod(info.name);
549
+ if (plugin && plugin.call) {
550
+ await plugin.call('install');
551
+ }
552
+ return true;
553
+ };
554
+
555
+ /**
556
+ * 安装插件
557
+ * @param {string} file 插件压缩文件路径
558
+ * @returns {object} 返回安装结果
559
+ */
560
+ App.prototype.installPlugin = async function (file) {
561
+ if (!file) {
562
+ throw new TypeError('无效的文件路径');
563
+ }
564
+
565
+ // 解压插件
566
+ var info = await this._unzipPlugin(file);
567
+ if (!info) {
568
+ return null;
569
+ }
570
+ // 安装插件
571
+ await this._installPlugin(info);
572
+
573
+ // 清理临时目录
574
+ var _this = this;
575
+ this._cleanup_timer = setTimeout(() => {
576
+ _this.log('info', '清理临时目录', info.unzip_dir);
577
+ if (info.unzip_dir && info.unzip_dir.hasDir()) {
578
+ info.unzip_dir.delDir();
579
+ }
580
+ }, 3000);
581
+ info.zip_file = file;
582
+ return info;
583
+ };
584
+
585
+ // 模块级单例: 每种 process 事件只注册一次, 热重载时仅切换内部引用
586
+ var _app_ref = null;
587
+ var _inited = false;
588
+
589
+ function _initProcess() {
590
+ if (_inited) return;
591
+ _inited = true;
592
+
593
+ process.on('SIGTERM', () => {
594
+ if (_app_ref) _app_ref._onSignal('SIGTERM');
595
+ });
596
+ process.on('SIGINT', () => {
597
+ if (_app_ref) _app_ref._onSignal('SIGINT');
598
+ });
599
+ process.on('uncaughtException', (err) => {
600
+ if (_app_ref) {
601
+ _app_ref.log('error', '未捕获异常, 进程即将退出', {
602
+ message: err.message,
603
+ stack: err.stack,
604
+ });
605
+ }
606
+ process.exit(1);
607
+ });
608
+ process.on('unhandledRejection', (reason) => {
609
+ if (_app_ref) {
610
+ _app_ref.log('error', '未处理的Promise rejection', {
611
+ reason: reason ? reason.message : '',
612
+ stack: reason ? reason.stack : '',
613
+ });
614
+ }
615
+ });
616
+ }
617
+
618
+ /**
619
+ * 注册信号和错误处理, 热重载时仅更新 app 引用
620
+ * @private
621
+ */
622
+ App.prototype._signalHandle = function () {
623
+ _app_ref = this;
624
+ _initProcess();
625
+ };
626
+
627
+ /**
628
+ * 处理系统信号
629
+ * @param {string} signal 信号名称
630
+ * @private
631
+ */
632
+ App.prototype._onSignal = async function (signal) {
633
+ this.log('info', `收到 ${signal} 信号, 开始优雅关闭`);
634
+
635
+ try {
636
+ var timeout = setTimeout(() => {
637
+ this.log('warn', '优雅关闭超时, 强制退出');
638
+ process.exit(1);
639
+ }, 15000);
640
+
641
+ await this._destroy();
642
+
643
+ clearTimeout(timeout);
644
+ this.log('info', '优雅关闭完成');
645
+ process.exit(0);
646
+ } catch (err) {
647
+ this.log('error', '优雅关闭失败', err);
648
+ process.exit(1);
649
+ }
650
+ };
651
+ App.prototype._destroy = async function () {
652
+ if (this._cleanup_timer) {
653
+ clearTimeout(this._cleanup_timer);
654
+ this._cleanup_timer = null;
655
+ }
656
+ if (this.config.diy_sql && this.sql && this.sql.close) {
657
+ try {
658
+ await this.sql.close();
659
+ } catch (err) {
660
+ this.log('error', '关闭数据库连接失败', err);
661
+ }
662
+ }
663
+ if (this.config.diy_cache && this.cache && this.cache.close) {
664
+ try {
665
+ await this.cache.close();
666
+ } catch (err) {
667
+ this.log('error', '关闭缓存连接失败', err);
668
+ }
669
+ }
670
+ };
671
+
672
+ /**
673
+ * 健康检查, 返回各组件状态
674
+ * @returns {object} 健康状态对象
675
+ */
676
+ App.prototype.healthcheck = async function () {
677
+ var status = {
678
+ status: 'ok',
679
+ uptime: process.uptime(),
680
+ memory: process.memoryUsage(),
681
+ timestamp: new Date().toISOString(),
682
+ services: {},
683
+ };
684
+
685
+ // 检查数据库连接
686
+ if (this.sql) {
687
+ try {
688
+ await this.sql.run('SELECT 1');
689
+ status.services.database = 'ok';
690
+ } catch {
691
+ status.services.database = 'error';
692
+ status.status = 'degraded';
693
+ }
694
+ }
695
+
696
+ // 检查缓存连接
697
+ if (this.cache) {
698
+ try {
699
+ await this.cache.ping();
700
+ status.services.cache = 'ok';
701
+ } catch {
702
+ status.services.cache = 'error';
703
+ status.status = 'degraded';
704
+ }
705
+ }
706
+
707
+ return status;
708
+ };
709
+
710
+ exports.App = App;