jsir 2.4.0 → 2.4.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/cmd/oaa.js +122 -16
- package/deps/evalCode.js +2 -6
- package/deps/room.js +131 -73
- package/deps/server.js +50 -46
- package/deps/setting.js +5 -1
- package/deps/util.js +42 -9
- package/package.json +1 -1
package/cmd/oaa.js
CHANGED
|
@@ -11,7 +11,8 @@ const {
|
|
|
11
11
|
createConsole, setTips, delTips,
|
|
12
12
|
getEditor, errorStr, getConfigDir,
|
|
13
13
|
getFullPath, parseUniqueName, toUniqueName, isJsirFileName, toJsirFileName,
|
|
14
|
-
getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey
|
|
14
|
+
getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey,
|
|
15
|
+
createDetachedProcess, interceptStdStreams
|
|
15
16
|
} = $lib;
|
|
16
17
|
const _args = process.argv.slice(2).map(trim);
|
|
17
18
|
const evalCode = require('../deps/evalCode')
|
|
@@ -29,6 +30,8 @@ const _fileWatcherMap = {}
|
|
|
29
30
|
const _types = setting.fileType
|
|
30
31
|
const console = createConsole();
|
|
31
32
|
const room = require('../deps/room');
|
|
33
|
+
const server = require('../deps/server')
|
|
34
|
+
const {reqNode} = require("../deps/room");
|
|
32
35
|
|
|
33
36
|
let lastFilterArg = '';
|
|
34
37
|
let _cmdMapFile = setting.name + 'CmdMap.json'
|
|
@@ -157,6 +160,7 @@ function checkWorkspaces() {
|
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
async function start() {
|
|
163
|
+
interceptStdStreams()
|
|
160
164
|
checkWorkspaces()
|
|
161
165
|
initWorkspace()
|
|
162
166
|
|
|
@@ -391,7 +395,7 @@ function _nextLine(callback, promptStr, hidden, resolve, end, isText) {
|
|
|
391
395
|
_haveWrapperInput = true;
|
|
392
396
|
closeRl()
|
|
393
397
|
inputStr = inputStr.replace(/\s+$/, '');
|
|
394
|
-
if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine)) {
|
|
398
|
+
if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine) && (!callback || callback === wrapperInput)) {
|
|
395
399
|
inputStr = '.e ' + inputStr;
|
|
396
400
|
}
|
|
397
401
|
let pro = (callback || wrapperInput)(inputStr);
|
|
@@ -580,11 +584,11 @@ function wrapperAlias(str) {
|
|
|
580
584
|
async function _wrapperInput(str) {
|
|
581
585
|
_haveWrapperInput = true;
|
|
582
586
|
global.$newInput = true;
|
|
583
|
-
|
|
584
587
|
str = trim(str)
|
|
585
588
|
if (!str) {
|
|
586
589
|
return;
|
|
587
590
|
}
|
|
591
|
+
global.$newError = false;
|
|
588
592
|
str = wrapperAlias(str);
|
|
589
593
|
|
|
590
594
|
if (/^[`'"]/.test(str)) {
|
|
@@ -624,10 +628,16 @@ async function _wrapperInput(str) {
|
|
|
624
628
|
let firstName = getJsirTypeKey(fileName)
|
|
625
629
|
if (firstName === setting.fileKey) {
|
|
626
630
|
await workFile(uniqueName)
|
|
627
|
-
} else if (firstName
|
|
628
|
-
console.log(String(fs.readFileSync(path)));
|
|
629
|
-
} else {
|
|
631
|
+
} else if (firstName === setting.exeKey) {
|
|
630
632
|
await runCmd(str)
|
|
633
|
+
} else if (firstName === setting.initKey) {
|
|
634
|
+
if (strs[1] === '-') {
|
|
635
|
+
await offServer(uniqueName)
|
|
636
|
+
} else {
|
|
637
|
+
await joinServer(uniqueName)
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
console.log(String(fs.readFileSync(path)));
|
|
631
641
|
}
|
|
632
642
|
} else {
|
|
633
643
|
await runCmd(str)
|
|
@@ -635,6 +645,44 @@ async function _wrapperInput(str) {
|
|
|
635
645
|
}
|
|
636
646
|
}
|
|
637
647
|
|
|
648
|
+
async function joinServer(uniqueName) {
|
|
649
|
+
let exportLib = await _requireSource(setting.defaultSpace, uniqueName, true);
|
|
650
|
+
let asyncFnSize = 0
|
|
651
|
+
for (let key of Object.keys(exportLib)) {
|
|
652
|
+
let val = exportLib[key];
|
|
653
|
+
if (getType(val) !== 'AsyncFunction') {
|
|
654
|
+
throw `service function must be async`;
|
|
655
|
+
}
|
|
656
|
+
asyncFnSize ++
|
|
657
|
+
}
|
|
658
|
+
if (asyncFnSize <= 0) {
|
|
659
|
+
console.warn('no items');
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
let pair = parseUniqueName(uniqueName)
|
|
663
|
+
for (let key of Object.keys(exportLib)) {
|
|
664
|
+
let val = exportLib[key];
|
|
665
|
+
server.setRoute('post', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
|
|
666
|
+
res.result = await val(...req);
|
|
667
|
+
}, server.jsonHandle)
|
|
668
|
+
}
|
|
669
|
+
console.msg(uniqueName, 'has become a service.');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function offServer(uniqueName) {
|
|
673
|
+
let pair = parseUniqueName(uniqueName)
|
|
674
|
+
let service = `${pair[0]}/${trimJsirFileName(pair[1])}`;
|
|
675
|
+
|
|
676
|
+
for (let key of Object.keys(setting.routes)) {
|
|
677
|
+
let ss = key.split("/").map(trim).filter(i => i);
|
|
678
|
+
let keyService = ss[1] + '/' + ss[2]
|
|
679
|
+
if (keyService === service) {
|
|
680
|
+
delete setting.routes[key]
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
console.msg(uniqueName, 'service has been offline.');
|
|
684
|
+
}
|
|
685
|
+
|
|
638
686
|
async function dealStartGlobalMode(rows, items, text) {
|
|
639
687
|
console.info('global search mode');
|
|
640
688
|
rows.push(...await dealStarCase(await evalText('return $context'), items, '$context'))
|
|
@@ -978,18 +1026,20 @@ async function showRooms() {
|
|
|
978
1026
|
})
|
|
979
1027
|
for (let key of Object.keys(room.jsirs)) {
|
|
980
1028
|
let jsir = room.jsirs[key]
|
|
1029
|
+
let pidStr = (process.pid === jsir.pid ? "*" : " ") + jsir.pid;
|
|
981
1030
|
results.push({
|
|
982
1031
|
mid: i,
|
|
983
1032
|
name: (isLocal ? "*" : " ") + room.name,
|
|
984
1033
|
node: room.selfNode,
|
|
985
1034
|
active: room.active,
|
|
986
|
-
pid:
|
|
1035
|
+
pid: jsir.newError ? errorStr(pidStr):pidStr,
|
|
987
1036
|
space: jsir.space,
|
|
988
1037
|
running: jsir.active,
|
|
989
1038
|
port: jsir.port,
|
|
990
1039
|
back: jsir.back,
|
|
991
1040
|
busy: jsir.busy,
|
|
992
|
-
tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n')
|
|
1041
|
+
tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n'),
|
|
1042
|
+
services: Object.keys(jsir.services || []).map(msgStr).join('\n')
|
|
993
1043
|
});
|
|
994
1044
|
}
|
|
995
1045
|
}
|
|
@@ -1443,17 +1493,48 @@ const keywordDef = {
|
|
|
1443
1493
|
comment: 'manage room ([node])',
|
|
1444
1494
|
exeFn: async (args) => {
|
|
1445
1495
|
await room.syncSetting();
|
|
1446
|
-
if (args
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1496
|
+
if (args.length > 0) {
|
|
1497
|
+
let have = false;
|
|
1498
|
+
let tasks = []
|
|
1499
|
+
for (let room of setting.rooms) {
|
|
1500
|
+
for (let pid of Object.keys(room.jsirs || {})) {
|
|
1501
|
+
if (args.indexOf(room.selfNode) === -1 && args.indexOf(pid) === -1) {
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
if (setting.selfRoom.selfNode === room.selfNode && String(process.pid) === pid) {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
have = true;
|
|
1508
|
+
tasks.push(async (input) => {
|
|
1509
|
+
let jsir = room.jsirs[pid];
|
|
1510
|
+
console.log(infoStr(`[${pid}]`))
|
|
1511
|
+
let resp = await reqNode(room.selfNode, "post", "/enter", jsir.port, JSON.stringify({input}))
|
|
1512
|
+
console.log(JSON.parse(resp).output)
|
|
1513
|
+
})
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
let input = have ? await nextLine(i => i):''
|
|
1517
|
+
if (vl(input)) {
|
|
1518
|
+
for (let task of tasks) {
|
|
1519
|
+
try {
|
|
1520
|
+
await task(input)
|
|
1521
|
+
} catch (e) {
|
|
1522
|
+
console.error(e)
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1451
1525
|
}
|
|
1452
1526
|
} else {
|
|
1453
1527
|
await showRooms();
|
|
1454
1528
|
}
|
|
1455
1529
|
},
|
|
1456
1530
|
short: 'm'
|
|
1531
|
+
},
|
|
1532
|
+
nsir: {
|
|
1533
|
+
comment: 'new jsir',
|
|
1534
|
+
exeFn: async (args) => {
|
|
1535
|
+
await createDetachedProcess('jsir', ['.p'])
|
|
1536
|
+
},
|
|
1537
|
+
short: 'N'
|
|
1457
1538
|
}
|
|
1458
1539
|
}
|
|
1459
1540
|
|
|
@@ -2050,7 +2131,7 @@ function filterRequire(currSpace, cmdMatchStr) {
|
|
|
2050
2131
|
return cmds[0];
|
|
2051
2132
|
}
|
|
2052
2133
|
|
|
2053
|
-
async function _requireSource(currSpace, cmdMatchStr) {
|
|
2134
|
+
async function _requireSource(currSpace, cmdMatchStr, force = false) {
|
|
2054
2135
|
let result
|
|
2055
2136
|
let uniqueName = filterRequire(currSpace, cmdMatchStr);
|
|
2056
2137
|
let path = getFullPath(uniqueName)
|
|
@@ -2058,7 +2139,32 @@ async function _requireSource(currSpace, cmdMatchStr) {
|
|
|
2058
2139
|
let pair = parseUniqueName(uniqueName)
|
|
2059
2140
|
let typeKey = getJsirTypeKey(pair[1]);
|
|
2060
2141
|
if (typeKey === setting.initKey) {
|
|
2061
|
-
|
|
2142
|
+
let pair = parseUniqueName(uniqueName)
|
|
2143
|
+
let serviceKey = `${pair[0]}/${trimJsirFileName(pair[1])}`
|
|
2144
|
+
if (setting.defaultSpace !== 'local' && !force && setting.services[serviceKey]) {
|
|
2145
|
+
let serviceObj = {};
|
|
2146
|
+
for (let fn of setting.services[serviceKey]) {
|
|
2147
|
+
serviceObj[fn] = async (...args) => {
|
|
2148
|
+
let key = `${serviceKey}/${fn}`;
|
|
2149
|
+
let targets = setting.serviceFns[key] || []
|
|
2150
|
+
targets = targets
|
|
2151
|
+
.filter(i => i.active)
|
|
2152
|
+
.filter(i => !room.isDisableConnect(i.node, i.port));
|
|
2153
|
+
let target = null;
|
|
2154
|
+
if (targets.length > 0) {
|
|
2155
|
+
target = room.busyPick(targets);
|
|
2156
|
+
} else {
|
|
2157
|
+
throw `service function ${key} not found`;
|
|
2158
|
+
}
|
|
2159
|
+
let resp = await room.reqNode(target.node, 'post',
|
|
2160
|
+
`/${pair[0]}/${encodeURIComponent(trimJsirFileName(pair[1]))}/${fn}`, target.port, JSON.stringify(args))
|
|
2161
|
+
return JSON.parse(resp).result;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return serviceObj
|
|
2165
|
+
} else {
|
|
2166
|
+
result = await evalText(text, uniqueName)
|
|
2167
|
+
}
|
|
2062
2168
|
} else if (typeKey === setting.exeKey) {
|
|
2063
2169
|
result = async (argsStr) => {
|
|
2064
2170
|
return await runCmd(trim(argsStr), uniqueName, text)
|
|
@@ -2148,7 +2254,7 @@ process.on('beforeExit', function () {
|
|
|
2148
2254
|
delTips();
|
|
2149
2255
|
} else {
|
|
2150
2256
|
nextLine();
|
|
2151
|
-
room.onRoom()
|
|
2257
|
+
room.onRoom(wrapperInput)
|
|
2152
2258
|
}
|
|
2153
2259
|
});
|
|
2154
2260
|
|
package/deps/evalCode.js
CHANGED
|
@@ -37,10 +37,6 @@ module.exports = async ($text = '', $cmdName = '', $args = [],
|
|
|
37
37
|
$homeDir, $lib, $cmdMap
|
|
38
38
|
}
|
|
39
39
|
let console = $lib.createConsole($cmdName);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})()`)
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error(e)
|
|
45
|
-
}
|
|
40
|
+
return await eval(`(async ()=>{${$text};
|
|
41
|
+
})()`)
|
|
46
42
|
}
|
package/deps/room.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips,
|
|
3
3
|
getRoomsDir, e, regEach, isRunningInBackground,
|
|
4
|
-
batchAsync, debugStr
|
|
4
|
+
batchAsync, debugStr, trim, getOr, errorTag, warnStr, getConfig
|
|
5
5
|
} = require('./util');
|
|
6
6
|
const {setRoute, createSign} = require('../deps/server')
|
|
7
7
|
const roomDataFile = "jsirRoom.json"
|
|
@@ -35,11 +35,28 @@ function isPidAlive(pid) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function onRoom() {
|
|
38
|
+
function onRoom(wrapperInput) {
|
|
39
39
|
if (!setting.roomTid[0]) {
|
|
40
40
|
try {
|
|
41
41
|
setRoute("post", "/", (req, res) => setting.selfRoom)
|
|
42
42
|
setRoute("get", "/", (req, res) => setting.selfRoom)
|
|
43
|
+
setRoute("post", "/enter", async (req, res) => {
|
|
44
|
+
if (!getConfig("enableRemote", true)) {
|
|
45
|
+
res.output = warnStr("Disable Remote!")
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (vl(req.input)) {
|
|
49
|
+
if (!isRunningInBackground() && ['.nsir', '.N'].indexOf(req.input.trim()) === -1) {
|
|
50
|
+
res.output = warnStr("Disable Remote!")
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setting.enterOutputs = []
|
|
54
|
+
await wrapperInput(req.input)
|
|
55
|
+
let outputs = setting.enterOutputs || [];
|
|
56
|
+
setting.enterOutputs = null;
|
|
57
|
+
res.output = outputs.join('\n');
|
|
58
|
+
}
|
|
59
|
+
})
|
|
43
60
|
} catch (e) {
|
|
44
61
|
debug("initRoute failed", e)
|
|
45
62
|
}
|
|
@@ -128,7 +145,7 @@ async function syncRooms() {
|
|
|
128
145
|
}
|
|
129
146
|
})
|
|
130
147
|
}
|
|
131
|
-
await batchAsync(fns,
|
|
148
|
+
await batchAsync(fns, 33);
|
|
132
149
|
await fileLock(roomsDirLockKey, async () => {
|
|
133
150
|
for (let syncRoom of [...syncRooms]) {
|
|
134
151
|
if (syncRoom.selfNode) {
|
|
@@ -186,6 +203,30 @@ async function _syncSetting(room) {
|
|
|
186
203
|
jsir.active = Date.now() - (jsir.lastUpdateTime || 0) <= 9000)
|
|
187
204
|
return room;
|
|
188
205
|
});
|
|
206
|
+
|
|
207
|
+
setting.services = {}
|
|
208
|
+
setting.serviceFns = {}
|
|
209
|
+
for (const room of setting.rooms) {
|
|
210
|
+
for (const pid of Object.keys(room.jsirs || [])) {
|
|
211
|
+
let jsir = room.jsirs[pid];
|
|
212
|
+
for (const service of Object.keys(jsir.services || [])) {
|
|
213
|
+
let serviceFns = jsir.services[service] || [];
|
|
214
|
+
|
|
215
|
+
let existFns = setting.services[service] || [];
|
|
216
|
+
existFns.push(...serviceFns)
|
|
217
|
+
setting.services[service] = [...new Set(existFns)]
|
|
218
|
+
|
|
219
|
+
for (const fn of serviceFns) {
|
|
220
|
+
getOr(setting.serviceFns, service + '/' + fn, []).push({
|
|
221
|
+
node: room.selfNode,
|
|
222
|
+
port: jsir.port,
|
|
223
|
+
busy: jsir.busy,
|
|
224
|
+
active: jsir.active
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
189
230
|
}
|
|
190
231
|
|
|
191
232
|
async function initRoom() {
|
|
@@ -205,6 +246,10 @@ async function initRoom() {
|
|
|
205
246
|
await syncSetting();
|
|
206
247
|
}
|
|
207
248
|
|
|
249
|
+
/*
|
|
250
|
+
最小为0
|
|
251
|
+
100 为1 毫秒
|
|
252
|
+
*/
|
|
208
253
|
async function getEventLoopDelay() {
|
|
209
254
|
const start = process.hrtime.bigint();
|
|
210
255
|
const delay = 1; // 用于测量的短暂延迟(单位:毫秒)
|
|
@@ -219,6 +264,15 @@ async function initRoomJsir(room) {
|
|
|
219
264
|
room.jsirs = Object.fromEntries(
|
|
220
265
|
Object.entries(room.jsirs || {}).filter(([pid]) => isPidAlive(pid))
|
|
221
266
|
);
|
|
267
|
+
|
|
268
|
+
let services = {}
|
|
269
|
+
for (let key of Object.keys(setting.routes)) {
|
|
270
|
+
let ss = key.split("/").map(trim).filter(i => i);
|
|
271
|
+
if (ss.length > 3) {
|
|
272
|
+
getOr(services, ss[1] + '/' + ss[2], []).push(ss[3])
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
let lastJsir = room.jsirs[process.pid]
|
|
222
276
|
room.jsirs[process.pid] = {
|
|
223
277
|
pid: process.pid,
|
|
224
278
|
space: setting.defaultSpace,
|
|
@@ -226,10 +280,12 @@ async function initRoomJsir(room) {
|
|
|
226
280
|
obj[key] = getValTips()[index];
|
|
227
281
|
return obj;
|
|
228
282
|
}, {}),
|
|
229
|
-
busy: await getEventLoopDelay(),
|
|
283
|
+
busy: await getEventLoopDelay() || (lastJsir ? lastJsir.busy:0),
|
|
230
284
|
back: isRunningInBackground(),
|
|
231
285
|
lastUpdateTime: Date.now(),
|
|
232
|
-
port: setting.server ? setting.server.address().port:null
|
|
286
|
+
port: setting.server ? setting.server.address().port:null,
|
|
287
|
+
services: services,
|
|
288
|
+
newError: global.$newError
|
|
233
289
|
}
|
|
234
290
|
}
|
|
235
291
|
|
|
@@ -263,62 +319,21 @@ function getLocalIPs() {
|
|
|
263
319
|
return result;
|
|
264
320
|
}
|
|
265
321
|
|
|
266
|
-
async function sshEnables(ips) {
|
|
267
|
-
// 检查开放 22 端口的 IP
|
|
268
|
-
const sshEnabledIPs = [];
|
|
269
|
-
const portCheckPromises = ips.map((ip) =>
|
|
270
|
-
checkPortOpen(ip, 22).then((isOpen) => {
|
|
271
|
-
if (isOpen) {
|
|
272
|
-
sshEnabledIPs.push(ip);
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
await Promise.all(portCheckPromises);
|
|
278
|
-
return sshEnabledIPs;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 检查指定 IP 的指定端口是否开放
|
|
283
|
-
* @param {string} ip - 目标 IP 地址
|
|
284
|
-
* @param {number} port - 目标端口
|
|
285
|
-
* @returns {Promise<boolean>} - 是否开放
|
|
286
|
-
*/
|
|
287
|
-
function checkPortOpen(ip, port) {
|
|
288
|
-
return new Promise((resolve) => {
|
|
289
|
-
const socket = new net.Socket();
|
|
290
|
-
socket.setTimeout(1000); // 设置超时时间
|
|
291
|
-
|
|
292
|
-
socket
|
|
293
|
-
.connect(port, ip, () => {
|
|
294
|
-
socket.destroy(); // 成功连接后关闭 socket
|
|
295
|
-
resolve(true);
|
|
296
|
-
})
|
|
297
|
-
.on("error", () => {
|
|
298
|
-
socket.destroy(); // 遇到错误时关闭 socket
|
|
299
|
-
resolve(false);
|
|
300
|
-
})
|
|
301
|
-
.on("timeout", () => {
|
|
302
|
-
socket.destroy(); // 超时时关闭 socket
|
|
303
|
-
resolve(false);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
322
|
function isDisableConnect(node, port) {
|
|
309
323
|
let disableTime = setting.disableConnect[node + ":" + port] || 0;
|
|
310
324
|
return disableTime > Date.now()
|
|
311
325
|
}
|
|
312
326
|
|
|
313
|
-
async function reqNode(node, method, url) {
|
|
314
|
-
let port = 52108;
|
|
327
|
+
async function reqNode(node, method, url, port, body) {
|
|
315
328
|
let room = setting.rooms.filter(i => i.selfNode === node)[0]
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
329
|
+
if (!port) {
|
|
330
|
+
port = 52108;
|
|
331
|
+
if (room) {
|
|
332
|
+
let activeJsir = Object.values(room.jsirs)
|
|
333
|
+
.filter(i => !isDisableConnect(node, i.port));
|
|
334
|
+
if (activeJsir.length > 0) {
|
|
335
|
+
port = busyPick(activeJsir).port;
|
|
336
|
+
}
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
339
|
let opt = {
|
|
@@ -332,33 +347,76 @@ async function reqNode(node, method, url) {
|
|
|
332
347
|
};
|
|
333
348
|
debug('reqRoom', JSON.stringify(opt))
|
|
334
349
|
return await new Promise((resolve, reject) => {
|
|
335
|
-
|
|
336
|
-
let
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
350
|
+
try {
|
|
351
|
+
let timeoutMs = url === '/' ? 3000:49000;
|
|
352
|
+
let timeout = null
|
|
353
|
+
|
|
354
|
+
const req = http.request(opt, (res) => {
|
|
355
|
+
let data = '';
|
|
356
|
+
// 数据块接收
|
|
357
|
+
res.on('data', (chunk) => {
|
|
358
|
+
data += chunk;
|
|
359
|
+
});
|
|
360
|
+
// 响应结束
|
|
361
|
+
res.on('end', () => {
|
|
362
|
+
clearTimeout(timeout)
|
|
363
|
+
// 检查响应状态码
|
|
364
|
+
if (res.statusCode !== 200) {
|
|
365
|
+
reject(errorTag(new Error(data), decodeURIComponent(url)));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
resolve(data)
|
|
369
|
+
});
|
|
340
370
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
371
|
+
|
|
372
|
+
// 设置超时逻辑
|
|
373
|
+
timeout = setTimeout(() => {
|
|
374
|
+
req.destroy(); // 中断请求
|
|
375
|
+
}, timeoutMs); // 10秒超时
|
|
376
|
+
|
|
377
|
+
// 错误处理
|
|
378
|
+
req.on('error', (e) => {
|
|
379
|
+
clearTimeout(timeout); // 清除超时定时器
|
|
380
|
+
if (room) {
|
|
381
|
+
setting.disableConnect[node + ":" + port] = Date.now() + 6000
|
|
382
|
+
}
|
|
383
|
+
reject(e)
|
|
344
384
|
});
|
|
345
|
-
});
|
|
346
385
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (room) {
|
|
350
|
-
setting.disableConnect[node + ":" + port] = Date.now() + 9000
|
|
386
|
+
if (body) {
|
|
387
|
+
req.write(body)
|
|
351
388
|
}
|
|
389
|
+
// 结束请求
|
|
390
|
+
req.end();
|
|
391
|
+
} catch (e) {
|
|
352
392
|
reject(e)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// 结束请求
|
|
356
|
-
req.end();
|
|
393
|
+
}
|
|
357
394
|
})
|
|
358
395
|
}
|
|
359
396
|
|
|
397
|
+
function busyPick(busyItems) {
|
|
398
|
+
if (busyItems.length === 1) {
|
|
399
|
+
return busyItems[0]
|
|
400
|
+
}
|
|
401
|
+
// 反转权重:计算 1 / busy 值作为权重
|
|
402
|
+
const weights = busyItems.map(item => 1 / (item.busy || 1));
|
|
403
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
404
|
+
const random = Math.random() * totalWeight;
|
|
405
|
+
|
|
406
|
+
let cumulativeWeight = 0;
|
|
407
|
+
for (let i = 0; i < busyItems.length; i++) {
|
|
408
|
+
cumulativeWeight += weights[i];
|
|
409
|
+
if (random < cumulativeWeight) {
|
|
410
|
+
return busyItems[i]; // 选中反转权重命中的对象
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
360
415
|
module.exports = {
|
|
361
416
|
onRoom,
|
|
362
417
|
offRoom,
|
|
363
|
-
syncSetting
|
|
418
|
+
syncSetting,
|
|
419
|
+
reqNode,
|
|
420
|
+
isDisableConnect,
|
|
421
|
+
busyPick
|
|
364
422
|
}
|
package/deps/server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
|
-
const {createConsole, vl, aesDecipher, md5, aesCipher, debugStr} = require('./util');
|
|
2
|
+
const {createConsole, vl, aesDecipher, md5, aesCipher, debugStr, isError} = require('./util');
|
|
3
3
|
const console = createConsole();
|
|
4
4
|
const setting = require('../deps/setting')
|
|
5
5
|
// 尝试监听的端口
|
|
6
6
|
const preferredPort = 52108;
|
|
7
7
|
// 路由存储
|
|
8
|
-
const routes =
|
|
8
|
+
const routes = setting.routes;
|
|
9
9
|
let invokeStart = false;
|
|
10
10
|
|
|
11
11
|
function debug(...args) {
|
|
@@ -36,10 +36,14 @@ function createSign() {
|
|
|
36
36
|
async function createServer(port) {
|
|
37
37
|
const server = http.createServer(async (req, res) => {
|
|
38
38
|
const method = req.method.toLowerCase();
|
|
39
|
-
const url = req.url;
|
|
39
|
+
const url = decodeURIComponent(req.url);
|
|
40
40
|
|
|
41
|
+
// 获取客户端的 IP 地址
|
|
42
|
+
const clientIp = req.socket.remoteAddress || req.connection.remoteAddress;
|
|
41
43
|
// 查找对应的路由处理函数
|
|
42
44
|
const routeKey = `${method} ${url}`;
|
|
45
|
+
debug(`Received ${routeKey} from ${clientIp}`);
|
|
46
|
+
|
|
43
47
|
if (routes[routeKey]) {
|
|
44
48
|
// 取header里的sign字段
|
|
45
49
|
const sign = req.headers['sign'];
|
|
@@ -47,24 +51,40 @@ async function createServer(port) {
|
|
|
47
51
|
const decodedTime = verifySign(sign);
|
|
48
52
|
const currentTime = Date.now();
|
|
49
53
|
|
|
50
|
-
if (decodedTime === null || Math.abs(currentTime - decodedTime) >
|
|
51
|
-
// 解码失败或者时间差大于9秒,返回403
|
|
54
|
+
if (decodedTime === null || Math.abs(currentTime - decodedTime) > 6000) {
|
|
52
55
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
53
|
-
res.end('Forbidden: Invalid Sign
|
|
56
|
+
res.end('Forbidden: Invalid Sign');
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
59
|
} else {
|
|
57
60
|
// 如果没有sign字段,返回403
|
|
58
61
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
59
|
-
res.end('Forbidden: Missing Sign
|
|
62
|
+
res.end('Forbidden: Missing Sign');
|
|
60
63
|
return;
|
|
61
64
|
}
|
|
62
|
-
|
|
63
65
|
// 如果签名通过,继续处理请求
|
|
64
|
-
|
|
66
|
+
try {
|
|
67
|
+
let body = '';
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
// 监听请求数据
|
|
70
|
+
req.on('data', chunk => {
|
|
71
|
+
body += chunk;
|
|
72
|
+
});
|
|
73
|
+
req.on('error', (err) => {
|
|
74
|
+
reject(err)
|
|
75
|
+
});
|
|
76
|
+
req.on('end', async () => {
|
|
77
|
+
resolve()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
await routes[routeKey](body, res);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
83
|
+
res.end(String(isError(e) ? e.message:e));
|
|
84
|
+
}
|
|
65
85
|
} else {
|
|
66
86
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
67
|
-
res.end('Not Found
|
|
87
|
+
res.end('Not Found');
|
|
68
88
|
}
|
|
69
89
|
});
|
|
70
90
|
|
|
@@ -95,6 +115,12 @@ async function startServer() {
|
|
|
95
115
|
}
|
|
96
116
|
|
|
97
117
|
// 设置路由的方法
|
|
118
|
+
/*
|
|
119
|
+
exam:
|
|
120
|
+
method, url, fn, handler(req, res, fn, err)
|
|
121
|
+
method, url, fn(req, res, err), null
|
|
122
|
+
err: {code, msg}
|
|
123
|
+
*/
|
|
98
124
|
function setRoute(method, url, fn, handler = jsonHandle) {
|
|
99
125
|
startServer();
|
|
100
126
|
|
|
@@ -106,51 +132,29 @@ function setRoute(method, url, fn, handler = jsonHandle) {
|
|
|
106
132
|
} else {
|
|
107
133
|
routes[routeKey] = fn;
|
|
108
134
|
}
|
|
109
|
-
debug(`
|
|
135
|
+
debug(`setRoute: ${method.toUpperCase()} ${url} ${handler ? handler.name:''}`);
|
|
110
136
|
return setting.server;
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
debug(`DelRoute: ${method.toUpperCase()} ${url}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function jsonHandle(req, res, fn) {
|
|
120
|
-
let body = '';
|
|
121
|
-
|
|
122
|
-
// 监听请求数据
|
|
123
|
-
req.on('data', chunk => {
|
|
124
|
-
body += chunk;
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
req.on('end', async () => {
|
|
128
|
-
try {
|
|
129
|
-
// 解析 JSON 请求体
|
|
130
|
-
const requestData = body ? JSON.parse(body):{};
|
|
139
|
+
async function jsonHandle(req, res, fn) {
|
|
140
|
+
// 解析 JSON 请求体
|
|
141
|
+
const requestData = req ? JSON.parse(req):{};
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
// 创建响应 JSON 数据
|
|
144
|
+
let responseData = {};
|
|
145
|
+
let result = await fn(requestData, responseData);
|
|
146
|
+
if (vl(result)) {
|
|
147
|
+
responseData = result;
|
|
148
|
+
}
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} catch (error) {
|
|
144
|
-
// 错误处理:如果 JSON 解析失败
|
|
145
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
146
|
-
res.end(JSON.stringify({ error: 'Invalid JSON format' }));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
150
|
+
// 设置响应头
|
|
151
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
152
|
+
// 返回 JSON 响应
|
|
153
|
+
res.end(JSON.stringify(responseData));
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
module.exports = {
|
|
152
157
|
setRoute,
|
|
153
|
-
delRoute,
|
|
154
158
|
jsonHandle,
|
|
155
159
|
createSign
|
|
156
160
|
};
|
package/deps/setting.js
CHANGED
package/deps/util.js
CHANGED
|
@@ -256,7 +256,7 @@ function createConsole(uniqueName) {
|
|
|
256
256
|
if ('debug' === key && !global.$DEBUG) {
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
|
-
if (
|
|
259
|
+
if (uniqueName && (quite || isRunningInBackground())) {
|
|
260
260
|
if ((key === 'table' || key === 'nable') && typeof args[0] === "object") {
|
|
261
261
|
args = ['', ...args]
|
|
262
262
|
}
|
|
@@ -702,7 +702,7 @@ function createLimitLogger(fileName, {
|
|
|
702
702
|
if (error) {
|
|
703
703
|
global.$newError = true;
|
|
704
704
|
}
|
|
705
|
-
text = `${pad(3, String(process.pid%1000), ' ')}> ${text}`
|
|
705
|
+
text = `${pad(3, String(process.pid%1000), process.pid >= 1000 ? '0':' ')}> ${text}`
|
|
706
706
|
if (time) {
|
|
707
707
|
text = `${timeStr('YYYY-MM-DD HH:mm:ss.SSS')} ${text}`
|
|
708
708
|
}
|
|
@@ -1854,7 +1854,7 @@ function md5(message) {
|
|
|
1854
1854
|
return crypto.createHash('md5').update(message).digest('hex');
|
|
1855
1855
|
}
|
|
1856
1856
|
|
|
1857
|
-
async function batchAsync(fns = [], asyncNum = 1, limitMs =
|
|
1857
|
+
async function batchAsync(fns = [], asyncNum = 1, limitMs = 49000) {
|
|
1858
1858
|
if (fns.length <= 0 || asyncNum < 1) {
|
|
1859
1859
|
return []
|
|
1860
1860
|
}
|
|
@@ -1920,12 +1920,8 @@ function errorTag(e, tag) {
|
|
|
1920
1920
|
if (!tag) {
|
|
1921
1921
|
return e;
|
|
1922
1922
|
}
|
|
1923
|
-
let newError = new Error(
|
|
1923
|
+
let newError = isError(e) ? e:new Error(e);
|
|
1924
1924
|
let stack = newError.stack
|
|
1925
|
-
if (isError(e)) {
|
|
1926
|
-
newError.name = e.name;
|
|
1927
|
-
stack = e.stack;
|
|
1928
|
-
}
|
|
1929
1925
|
newError.stack = stack + `\n at ${tag}`
|
|
1930
1926
|
return newError
|
|
1931
1927
|
}
|
|
@@ -2025,6 +2021,41 @@ function getJsirTypeKey(fileName) {
|
|
|
2025
2021
|
return fileName.split(/[^a-zA-Z]+/)[0] || ''
|
|
2026
2022
|
}
|
|
2027
2023
|
|
|
2024
|
+
function createDetachedProcess(cmd, args = []) {
|
|
2025
|
+
// 创建子进程
|
|
2026
|
+
const subprocess = spawn(cmd, [...args], {
|
|
2027
|
+
detached: true, // 独立运行
|
|
2028
|
+
stdio: ['ignore']
|
|
2029
|
+
});
|
|
2030
|
+
// 让主进程不再等待子进程
|
|
2031
|
+
subprocess.unref();
|
|
2032
|
+
console.log(`New ${cmd} process started with PID:`, subprocess.pid);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function interceptStdStreams() {
|
|
2036
|
+
// 保存原始的 stdout 和 stderr 写入方法
|
|
2037
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
2038
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
2039
|
+
|
|
2040
|
+
// 重写 process.stdout.write
|
|
2041
|
+
process.stdout.write = (chunk, ...args) => {
|
|
2042
|
+
if(setting.enterOutputs) {
|
|
2043
|
+
setting.enterOutputs.push(chunk.toString());
|
|
2044
|
+
} else {
|
|
2045
|
+
originalStdoutWrite(chunk, ...args); // 保留原始行为
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
|
|
2049
|
+
// 重写 process.stderr.write
|
|
2050
|
+
process.stderr.write = (chunk, ...args) => {
|
|
2051
|
+
if(setting.enterOutputs) {
|
|
2052
|
+
setting.enterOutputs.push(chunk.toString());
|
|
2053
|
+
} else {
|
|
2054
|
+
originalStderrWrite(chunk, ...args); // 保留原始行为
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2028
2059
|
module.exports = {
|
|
2029
2060
|
wrapperJsirText,
|
|
2030
2061
|
run,
|
|
@@ -2131,5 +2162,7 @@ module.exports = {
|
|
|
2131
2162
|
getRoomsDir,
|
|
2132
2163
|
getDataDir,
|
|
2133
2164
|
getJsirTypeKey,
|
|
2134
|
-
isRunningInBackground
|
|
2165
|
+
isRunningInBackground,
|
|
2166
|
+
createDetachedProcess,
|
|
2167
|
+
interceptStdStreams
|
|
2135
2168
|
}
|