alemonjs 2.1.0 → 2.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/README_CONFIG.md +228 -45
- package/lib/cbp/connects/client.js +13 -32
- package/lib/cbp/connects/connect.js +3 -3
- package/lib/cbp/connects/platform.js +2 -1
- package/lib/cbp/core/connection-manager.d.ts +18 -0
- package/lib/cbp/core/connection-manager.js +67 -0
- package/lib/cbp/core/constants.d.ts +19 -0
- package/lib/cbp/core/constants.js +23 -0
- package/lib/cbp/core/load-balancer.d.ts +37 -0
- package/lib/cbp/core/load-balancer.js +119 -0
- package/lib/cbp/processor/actions.d.ts +3 -0
- package/lib/cbp/processor/actions.js +13 -24
- package/lib/cbp/processor/api.d.ts +3 -0
- package/lib/cbp/processor/api.js +13 -24
- package/lib/cbp/processor/config.d.ts +44 -16
- package/lib/cbp/processor/config.js +271 -20
- package/lib/cbp/processor/request-handler.d.ts +17 -0
- package/lib/cbp/processor/request-handler.js +65 -0
- package/lib/cbp/routers/router.js +9 -0
- package/lib/cbp/server/main.d.ts +18 -1
- package/lib/cbp/server/main.js +141 -166
- package/lib/cbp/server/testone.js +1 -5
- package/lib/main.d.ts +18 -1
- package/lib/main.js +13 -1
- package/package.json +1 -1
- package/lib/app.d.ts +0 -18
- package/lib/app.js +0 -82
- package/lib/cbp/processor/hello.html.d.ts +0 -1
- package/lib/cbp/processor/hello.html.js +0 -47
- package/lib/cbp/routers/middleware.d.ts +0 -2
- package/lib/cbp/routers/middleware.js +0 -40
- package/lib/cbp/routers/utils.d.ts +0 -2
- package/lib/cbp/routers/utils.js +0 -39
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { getHealthyConnectionIds } from './connection-manager.js';
|
|
2
|
+
|
|
3
|
+
const LoadBalanceStrategyEnum = {
|
|
4
|
+
ROUND_ROBIN: 'round-robin',
|
|
5
|
+
LEAST_CONNECTIONS: 'least-connections',
|
|
6
|
+
RANDOM: 'random',
|
|
7
|
+
FIRST_AVAILABLE: 'first-available'
|
|
8
|
+
};
|
|
9
|
+
const createLoadBalancerState = (strategy = 'round-robin') => ({
|
|
10
|
+
strategy,
|
|
11
|
+
roundRobinIndex: 0,
|
|
12
|
+
bindingCounts: new Map()
|
|
13
|
+
});
|
|
14
|
+
const selectByRoundRobin = (availableIds, currentIndex) => {
|
|
15
|
+
if (availableIds.length === 0) {
|
|
16
|
+
return { selectedId: null, nextIndex: currentIndex };
|
|
17
|
+
}
|
|
18
|
+
const selectedId = availableIds[currentIndex % availableIds.length];
|
|
19
|
+
const nextIndex = (currentIndex + 1) % availableIds.length;
|
|
20
|
+
return { selectedId, nextIndex };
|
|
21
|
+
};
|
|
22
|
+
const selectByLeastConnections = (availableIds, bindingCounts) => {
|
|
23
|
+
if (availableIds.length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
let selectedId = availableIds[0];
|
|
27
|
+
let minConnections = bindingCounts.get(selectedId) || 0;
|
|
28
|
+
for (let i = 1; i < availableIds.length; i++) {
|
|
29
|
+
const id = availableIds[i];
|
|
30
|
+
const connections = bindingCounts.get(id) || 0;
|
|
31
|
+
if (connections < minConnections) {
|
|
32
|
+
minConnections = connections;
|
|
33
|
+
selectedId = id;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return selectedId;
|
|
37
|
+
};
|
|
38
|
+
const selectByRandom = (availableIds) => {
|
|
39
|
+
if (availableIds.length === 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const randomIndex = Math.floor(Math.random() * availableIds.length);
|
|
43
|
+
return availableIds[randomIndex];
|
|
44
|
+
};
|
|
45
|
+
const selectFirstAvailable = (availableIds) => {
|
|
46
|
+
return availableIds.length > 0 ? availableIds[0] : null;
|
|
47
|
+
};
|
|
48
|
+
const selectConnection = (connectionState, balancerState) => {
|
|
49
|
+
const availableIds = getHealthyConnectionIds(connectionState);
|
|
50
|
+
if (availableIds.length === 0) {
|
|
51
|
+
return { selectedId: null, newBalancerState: balancerState };
|
|
52
|
+
}
|
|
53
|
+
let selectedId = null;
|
|
54
|
+
const newBalancerState = { ...balancerState };
|
|
55
|
+
switch (balancerState.strategy) {
|
|
56
|
+
case 'round-robin': {
|
|
57
|
+
const result = selectByRoundRobin(availableIds, balancerState.roundRobinIndex);
|
|
58
|
+
selectedId = result.selectedId;
|
|
59
|
+
newBalancerState.roundRobinIndex = result.nextIndex;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'least-connections': {
|
|
63
|
+
selectedId = selectByLeastConnections(availableIds, balancerState.bindingCounts);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'random': {
|
|
67
|
+
selectedId = selectByRandom(availableIds);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'first-available': {
|
|
71
|
+
selectedId = selectFirstAvailable(availableIds);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
default: {
|
|
75
|
+
const result = selectByRoundRobin(availableIds, balancerState.roundRobinIndex);
|
|
76
|
+
selectedId = result.selectedId;
|
|
77
|
+
newBalancerState.roundRobinIndex = result.nextIndex;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { selectedId, newBalancerState };
|
|
82
|
+
};
|
|
83
|
+
const incrementBindingCount = (balancerState, connectionId) => {
|
|
84
|
+
const newBindingCounts = new Map(balancerState.bindingCounts);
|
|
85
|
+
const currentCount = newBindingCounts.get(connectionId) || 0;
|
|
86
|
+
newBindingCounts.set(connectionId, currentCount + 1);
|
|
87
|
+
return {
|
|
88
|
+
...balancerState,
|
|
89
|
+
bindingCounts: newBindingCounts
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const decrementBindingCount = (balancerState, connectionId) => {
|
|
93
|
+
const newBindingCounts = new Map(balancerState.bindingCounts);
|
|
94
|
+
const currentCount = newBindingCounts.get(connectionId) || 0;
|
|
95
|
+
if (currentCount > 0) {
|
|
96
|
+
newBindingCounts.set(connectionId, currentCount - 1);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
newBindingCounts.delete(connectionId);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...balancerState,
|
|
103
|
+
bindingCounts: newBindingCounts
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
const getLoadBalancerStats = (balancerState) => {
|
|
107
|
+
const totalBindings = Array.from(balancerState.bindingCounts.values())
|
|
108
|
+
.reduce((sum, count) => sum + count, 0);
|
|
109
|
+
return {
|
|
110
|
+
strategy: balancerState.strategy,
|
|
111
|
+
roundRobinIndex: balancerState.roundRobinIndex,
|
|
112
|
+
totalConnections: balancerState.bindingCounts.size,
|
|
113
|
+
totalBindings,
|
|
114
|
+
bindingCounts: Object.fromEntries(balancerState.bindingCounts),
|
|
115
|
+
lastUpdate: Date.now()
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export { LoadBalanceStrategyEnum, createLoadBalancerState, decrementBindingCount, getLoadBalancerStats, incrementBindingCount, selectByLeastConnections, selectByRandom, selectByRoundRobin, selectConnection, selectFirstAvailable };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { Result } from '../../core';
|
|
2
2
|
import type { Actions } from '../../types';
|
|
3
3
|
export declare const sendAction: (data: Actions) => Promise<Result[]>;
|
|
4
|
+
export declare const handleActionResponse: (actionId: string, results: Result[]) => void;
|
|
5
|
+
export declare const getPendingActionCount: () => number;
|
|
6
|
+
export declare const destroyActionHandler: () => void;
|
|
@@ -1,28 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ResultCode } from '../../core/variable.js';
|
|
3
|
-
import 'fs';
|
|
4
|
-
import 'path';
|
|
5
|
-
import 'yaml';
|
|
6
|
-
import { createResult } from '../../core/utils.js';
|
|
7
|
-
import { generateUniqueId, deviceId, actionResolves, actionTimeouts, timeoutTime } from './config.js';
|
|
1
|
+
import { handleRequestResponse, sendRequest, getPendingRequestCount, destroyRequestHandler, createRequestHandlerState } from './request-handler.js';
|
|
8
2
|
|
|
3
|
+
const actionHandlerState = createRequestHandlerState('行为', 'actionId');
|
|
9
4
|
const sendAction = (data) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
actionResolves.delete(actionId);
|
|
21
|
-
actionTimeouts.delete(actionId);
|
|
22
|
-
resolve([createResult(ResultCode.Fail, '行为超时', null)]);
|
|
23
|
-
}, timeoutTime);
|
|
24
|
-
actionTimeouts.set(actionId, timeout);
|
|
25
|
-
});
|
|
5
|
+
return sendRequest(actionHandlerState, data);
|
|
6
|
+
};
|
|
7
|
+
const handleActionResponse = (actionId, results) => {
|
|
8
|
+
handleRequestResponse(actionHandlerState, actionId, results);
|
|
9
|
+
};
|
|
10
|
+
const getPendingActionCount = () => {
|
|
11
|
+
return getPendingRequestCount(actionHandlerState);
|
|
12
|
+
};
|
|
13
|
+
const destroyActionHandler = () => {
|
|
14
|
+
destroyRequestHandler(actionHandlerState);
|
|
26
15
|
};
|
|
27
16
|
|
|
28
|
-
export { sendAction };
|
|
17
|
+
export { destroyActionHandler, getPendingActionCount, handleActionResponse, sendAction };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { Result } from '../../core';
|
|
2
2
|
import type { Apis } from '../../types';
|
|
3
3
|
export declare const sendAPI: (data: Apis) => Promise<Result[]>;
|
|
4
|
+
export declare const handleApiResponse: (apiId: string, results: Result[]) => void;
|
|
5
|
+
export declare const getPendingApiCount: () => number;
|
|
6
|
+
export declare const destroyApiHandler: () => void;
|
package/lib/cbp/processor/api.js
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import 'fs';
|
|
3
|
-
import 'path';
|
|
4
|
-
import 'yaml';
|
|
5
|
-
import { createResult } from '../../core/utils.js';
|
|
6
|
-
import { generateUniqueId, deviceId, apiResolves, apiTimeouts, timeoutTime } from './config.js';
|
|
7
|
-
import * as flattedJSON from 'flatted';
|
|
1
|
+
import { handleRequestResponse, sendRequest, getPendingRequestCount, destroyRequestHandler, createRequestHandlerState } from './request-handler.js';
|
|
8
2
|
|
|
3
|
+
const apiHandlerState = createRequestHandlerState('接口', 'apiId');
|
|
9
4
|
const sendAPI = (data) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
apiResolves.delete(ApiId);
|
|
21
|
-
apiTimeouts.delete(ApiId);
|
|
22
|
-
resolve([createResult(ResultCode.Fail, '接口超时', null)]);
|
|
23
|
-
}, timeoutTime);
|
|
24
|
-
apiTimeouts.set(ApiId, timeout);
|
|
25
|
-
});
|
|
5
|
+
return sendRequest(apiHandlerState, data);
|
|
6
|
+
};
|
|
7
|
+
const handleApiResponse = (apiId, results) => {
|
|
8
|
+
handleRequestResponse(apiHandlerState, apiId, results);
|
|
9
|
+
};
|
|
10
|
+
const getPendingApiCount = () => {
|
|
11
|
+
return getPendingRequestCount(apiHandlerState);
|
|
12
|
+
};
|
|
13
|
+
const destroyApiHandler = () => {
|
|
14
|
+
destroyRequestHandler(apiHandlerState);
|
|
26
15
|
};
|
|
27
16
|
|
|
28
|
-
export { sendAPI };
|
|
17
|
+
export { destroyApiHandler, getPendingApiCount, handleApiResponse, sendAPI };
|
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
2
|
import { Result } from '../../core';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare const fullClient: Map<string, WebSocket>;
|
|
6
|
-
export declare const deviceId: string;
|
|
7
|
-
export declare const USER_AGENT_HEADER = "user-agent";
|
|
8
|
-
export declare const USER_AGENT_HEADER_VALUE_MAP: {
|
|
9
|
-
platform: string;
|
|
10
|
-
client: string;
|
|
11
|
-
testone: string;
|
|
12
|
-
};
|
|
13
|
-
export declare const DEVICE_ID_HEADER = "x-device-id";
|
|
14
|
-
export declare const FULL_RECEIVE_HEADER = "x-full-receive";
|
|
3
|
+
import { LoadBalanceStrategy } from '../core/load-balancer';
|
|
4
|
+
import { USER_AGENT_HEADER, USER_AGENT_HEADER_VALUE_MAP, DEVICE_ID_HEADER, FULL_RECEIVE_HEADER } from '../core/constants';
|
|
15
5
|
type actionResolvesValue = Result[] | PromiseLike<Result[]>;
|
|
16
6
|
type actionResolvesValueFunc = (value: actionResolvesValue) => void;
|
|
17
7
|
export declare const actionResolves: Map<string, actionResolvesValueFunc>;
|
|
18
8
|
export declare const apiResolves: Map<string, actionResolvesValueFunc>;
|
|
19
9
|
export declare const actionTimeouts: Map<string, NodeJS.Timeout>;
|
|
20
10
|
export declare const apiTimeouts: Map<string, NodeJS.Timeout>;
|
|
21
|
-
export declare const
|
|
22
|
-
export
|
|
11
|
+
export declare const deviceId: string;
|
|
12
|
+
export { USER_AGENT_HEADER, USER_AGENT_HEADER_VALUE_MAP, DEVICE_ID_HEADER, FULL_RECEIVE_HEADER };
|
|
23
13
|
export declare const timeoutTime: number;
|
|
24
14
|
export declare const reconnectInterval: number;
|
|
25
|
-
export declare const
|
|
26
|
-
export
|
|
15
|
+
export declare const addChildrenClient: (id: string, ws: WebSocket) => void;
|
|
16
|
+
export declare const removeChildrenClient: (id: string) => boolean;
|
|
17
|
+
export declare const getChildrenClient: (id: string) => WebSocket | undefined;
|
|
18
|
+
export declare const hasChildrenClient: (id: string) => boolean;
|
|
19
|
+
export declare const clearChildrenClients: () => void;
|
|
20
|
+
export declare const getChildrenClientCount: () => number;
|
|
21
|
+
export declare const forEachChildrenClient: (callback: (ws: WebSocket, id: string) => void) => void;
|
|
22
|
+
export declare const getChildrenClientIds: () => string[];
|
|
23
|
+
export declare const getAllChildrenClients: () => Map<string, WebSocket>;
|
|
24
|
+
export declare const addPlatformClient: (id: string, ws: WebSocket) => void;
|
|
25
|
+
export declare const removePlatformClient: (id: string) => boolean;
|
|
26
|
+
export declare const getPlatformClient: (id: string) => WebSocket | undefined;
|
|
27
|
+
export declare const getPlatformClientCount: () => number;
|
|
28
|
+
export declare const clearPlatformClients: () => void;
|
|
29
|
+
export declare const forEachPlatformClient: (callback: (ws: WebSocket, id: string) => void) => void;
|
|
30
|
+
export declare const addFullClient: (id: string, ws: WebSocket) => void;
|
|
31
|
+
export declare const removeFullClient: (id: string) => boolean;
|
|
32
|
+
export declare const getFullClient: (id: string) => WebSocket | undefined;
|
|
33
|
+
export declare const getFullClientCount: () => number;
|
|
34
|
+
export declare const clearFullClients: () => void;
|
|
35
|
+
export declare const forEachFullClient: (callback: (ws: WebSocket, id: string) => void) => void;
|
|
36
|
+
export declare const getHealthyClients: () => string[];
|
|
37
|
+
export declare const selectClientByStrategy: (_sessionId: string, _key?: string) => string | null;
|
|
38
|
+
export declare const bindSessionToClient: (sessionId: string, key?: string) => string | null;
|
|
39
|
+
export declare const unbindSession: (sessionId: string) => void;
|
|
40
|
+
export declare const setLoadBalanceStrategy: (strategy: LoadBalanceStrategy) => void;
|
|
41
|
+
export declare const getLoadBalanceStats: () => {
|
|
42
|
+
totalClients: number;
|
|
43
|
+
healthyClients: number;
|
|
44
|
+
totalBindings: number;
|
|
45
|
+
bindingCounts: {
|
|
46
|
+
[k: string]: number;
|
|
47
|
+
};
|
|
48
|
+
strategy: LoadBalanceStrategy;
|
|
49
|
+
uptime: number;
|
|
50
|
+
};
|
|
51
|
+
export declare const performHealthCheck: () => void;
|
|
52
|
+
export declare const startHealthCheckTimer: () => void;
|
|
53
|
+
export declare const stopHealthCheckTimer: () => void;
|
|
54
|
+
export declare const destroyLoadBalancer: () => void;
|
|
@@ -1,27 +1,278 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { getConnectionStats, createConnectionState, addConnection, getConnection, removeConnection, clearAllConnections, getHealthyConnectionIds } from '../core/connection-manager.js';
|
|
3
|
+
import { getLoadBalancerStats, createLoadBalancerState, incrementBindingCount, decrementBindingCount, selectConnection } from '../core/load-balancer.js';
|
|
4
|
+
import { generateUniqueId, TIME_CONFIG } from '../core/constants.js';
|
|
5
|
+
export { DEVICE_ID_HEADER, FULL_RECEIVE_HEADER, USER_AGENT_HEADER, USER_AGENT_HEADER_VALUE_MAP } from '../core/constants.js';
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const USER_AGENT_HEADER_VALUE_MAP = {
|
|
9
|
-
platform: 'platform',
|
|
10
|
-
client: 'client',
|
|
11
|
-
testone: 'testone'
|
|
12
|
-
};
|
|
13
|
-
const DEVICE_ID_HEADER = 'x-device-id';
|
|
14
|
-
const FULL_RECEIVE_HEADER = 'x-full-receive';
|
|
7
|
+
let childrenConnectionState = createConnectionState();
|
|
8
|
+
let platformConnectionState = createConnectionState();
|
|
9
|
+
let fullConnectionState = createConnectionState();
|
|
10
|
+
let loadBalancerState = createLoadBalancerState('least-connections');
|
|
11
|
+
const sessionBindings = new Map();
|
|
15
12
|
const actionResolves = new Map();
|
|
16
13
|
const apiResolves = new Map();
|
|
17
14
|
const actionTimeouts = new Map();
|
|
18
15
|
const apiTimeouts = new Map();
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
16
|
+
const deviceId = generateUniqueId();
|
|
17
|
+
const timeoutTime = TIME_CONFIG.TIMEOUT;
|
|
18
|
+
const reconnectInterval = TIME_CONFIG.RECONNECT_INTERVAL;
|
|
19
|
+
const addChildrenClient = (id, ws) => {
|
|
20
|
+
childrenConnectionState = addConnection(childrenConnectionState, id, ws);
|
|
21
|
+
loadBalancerState = incrementBindingCount(loadBalancerState, id);
|
|
22
|
+
logger.debug({
|
|
23
|
+
code: 200,
|
|
24
|
+
message: `子客户端已添加: ${id}`,
|
|
25
|
+
data: { totalClients: childrenConnectionState.connections.size }
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const removeChildrenClient = (id) => {
|
|
29
|
+
const existed = getConnection(childrenConnectionState, id) !== undefined;
|
|
30
|
+
if (existed) {
|
|
31
|
+
childrenConnectionState = removeConnection(childrenConnectionState, id);
|
|
32
|
+
loadBalancerState = decrementBindingCount(loadBalancerState, id);
|
|
33
|
+
const bindingsToRemove = [];
|
|
34
|
+
for (const [sessionId, boundClientId] of sessionBindings.entries()) {
|
|
35
|
+
if (boundClientId === id) {
|
|
36
|
+
bindingsToRemove.push(sessionId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
bindingsToRemove.forEach(sessionId => sessionBindings.delete(sessionId));
|
|
40
|
+
logger.debug({
|
|
41
|
+
code: 200,
|
|
42
|
+
message: `子客户端已移除: ${id}`,
|
|
43
|
+
data: { cleanedBindings: bindingsToRemove.length }
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return existed;
|
|
47
|
+
};
|
|
48
|
+
const getChildrenClient = (id) => {
|
|
49
|
+
return getConnection(childrenConnectionState, id);
|
|
50
|
+
};
|
|
51
|
+
const hasChildrenClient = (id) => {
|
|
52
|
+
return getConnection(childrenConnectionState, id) !== undefined;
|
|
53
|
+
};
|
|
54
|
+
const clearChildrenClients = () => {
|
|
55
|
+
childrenConnectionState = clearAllConnections(childrenConnectionState);
|
|
56
|
+
loadBalancerState = createLoadBalancerState(loadBalancerState.strategy);
|
|
57
|
+
sessionBindings.clear();
|
|
58
|
+
};
|
|
59
|
+
const getChildrenClientCount = () => {
|
|
60
|
+
return childrenConnectionState.connections.size;
|
|
61
|
+
};
|
|
62
|
+
const forEachChildrenClient = (callback) => {
|
|
63
|
+
childrenConnectionState.connections.forEach(callback);
|
|
64
|
+
};
|
|
65
|
+
const getChildrenClientIds = () => {
|
|
66
|
+
return Array.from(childrenConnectionState.connections.keys());
|
|
67
|
+
};
|
|
68
|
+
const getAllChildrenClients = () => {
|
|
69
|
+
return new Map(childrenConnectionState.connections);
|
|
70
|
+
};
|
|
71
|
+
const addPlatformClient = (id, ws) => {
|
|
72
|
+
platformConnectionState = addConnection(platformConnectionState, id, ws);
|
|
73
|
+
logger.debug({
|
|
74
|
+
code: 200,
|
|
75
|
+
message: `平台客户端已添加: ${id}`,
|
|
76
|
+
data: { totalClients: platformConnectionState.connections.size }
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const removePlatformClient = (id) => {
|
|
80
|
+
const existed = getConnection(platformConnectionState, id) !== undefined;
|
|
81
|
+
if (existed) {
|
|
82
|
+
platformConnectionState = removeConnection(platformConnectionState, id);
|
|
83
|
+
logger.debug({
|
|
84
|
+
code: 200,
|
|
85
|
+
message: `平台客户端已移除: ${id}`,
|
|
86
|
+
data: null
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return existed;
|
|
90
|
+
};
|
|
91
|
+
const getPlatformClient = (id) => {
|
|
92
|
+
return getConnection(platformConnectionState, id);
|
|
93
|
+
};
|
|
94
|
+
const getPlatformClientCount = () => {
|
|
95
|
+
return platformConnectionState.connections.size;
|
|
96
|
+
};
|
|
97
|
+
const clearPlatformClients = () => {
|
|
98
|
+
platformConnectionState = clearAllConnections(platformConnectionState);
|
|
99
|
+
};
|
|
100
|
+
const forEachPlatformClient = (callback) => {
|
|
101
|
+
platformConnectionState.connections.forEach(callback);
|
|
102
|
+
};
|
|
103
|
+
const addFullClient = (id, ws) => {
|
|
104
|
+
fullConnectionState = addConnection(fullConnectionState, id, ws);
|
|
105
|
+
logger.debug({
|
|
106
|
+
code: 200,
|
|
107
|
+
message: `全接收客户端已添加: ${id}`,
|
|
108
|
+
data: { totalClients: fullConnectionState.connections.size }
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const removeFullClient = (id) => {
|
|
112
|
+
const existed = getConnection(fullConnectionState, id) !== undefined;
|
|
113
|
+
if (existed) {
|
|
114
|
+
fullConnectionState = removeConnection(fullConnectionState, id);
|
|
115
|
+
logger.debug({
|
|
116
|
+
code: 200,
|
|
117
|
+
message: `全接收客户端已移除: ${id}`,
|
|
118
|
+
data: null
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return existed;
|
|
122
|
+
};
|
|
123
|
+
const getFullClient = (id) => {
|
|
124
|
+
return getConnection(fullConnectionState, id);
|
|
125
|
+
};
|
|
126
|
+
const getFullClientCount = () => {
|
|
127
|
+
return fullConnectionState.connections.size;
|
|
128
|
+
};
|
|
129
|
+
const clearFullClients = () => {
|
|
130
|
+
fullConnectionState = clearAllConnections(fullConnectionState);
|
|
131
|
+
};
|
|
132
|
+
const forEachFullClient = (callback) => {
|
|
133
|
+
fullConnectionState.connections.forEach(callback);
|
|
134
|
+
};
|
|
135
|
+
const getHealthyClients = () => {
|
|
136
|
+
return getHealthyConnectionIds(childrenConnectionState);
|
|
137
|
+
};
|
|
138
|
+
const selectClientByStrategy = (_sessionId, _key) => {
|
|
139
|
+
const result = selectConnection(childrenConnectionState, loadBalancerState);
|
|
140
|
+
if (result.selectedId) {
|
|
141
|
+
loadBalancerState = result.newBalancerState;
|
|
142
|
+
return result.selectedId;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
146
|
+
const bindSessionToClient = (sessionId, key) => {
|
|
147
|
+
if (sessionBindings.has(sessionId)) {
|
|
148
|
+
const clientId = sessionBindings.get(sessionId);
|
|
149
|
+
const ws = getChildrenClient(clientId);
|
|
150
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
151
|
+
return clientId;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
unbindSession(sessionId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const selectedClient = selectClientByStrategy();
|
|
158
|
+
if (selectedClient) {
|
|
159
|
+
sessionBindings.set(sessionId, selectedClient);
|
|
160
|
+
loadBalancerState = incrementBindingCount(loadBalancerState, selectedClient);
|
|
161
|
+
logger.debug({
|
|
162
|
+
code: 200,
|
|
163
|
+
message: `会话已绑定: ${sessionId} -> ${selectedClient}`,
|
|
164
|
+
data: {
|
|
165
|
+
strategy: loadBalancerState.strategy,
|
|
166
|
+
connections: loadBalancerState.bindingCounts.get(selectedClient) || 0
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return selectedClient;
|
|
171
|
+
};
|
|
172
|
+
const unbindSession = (sessionId) => {
|
|
173
|
+
const clientId = sessionBindings.get(sessionId);
|
|
174
|
+
if (clientId) {
|
|
175
|
+
sessionBindings.delete(sessionId);
|
|
176
|
+
loadBalancerState = decrementBindingCount(loadBalancerState, clientId);
|
|
177
|
+
logger.debug({
|
|
178
|
+
code: 200,
|
|
179
|
+
message: `会话已解绑: ${sessionId}`,
|
|
180
|
+
data: {
|
|
181
|
+
clientId,
|
|
182
|
+
remainingConnections: loadBalancerState.bindingCounts.get(clientId) || 0
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const setLoadBalanceStrategy = (strategy) => {
|
|
188
|
+
loadBalancerState = { ...loadBalancerState, strategy, roundRobinIndex: 0 };
|
|
189
|
+
logger.info(`负载均衡策略已切换: ${strategy}`);
|
|
190
|
+
};
|
|
191
|
+
const getLoadBalanceStats = () => {
|
|
192
|
+
const connectionStats = getConnectionStats(childrenConnectionState);
|
|
193
|
+
const balancerStats = getLoadBalancerStats(loadBalancerState);
|
|
194
|
+
return {
|
|
195
|
+
totalClients: connectionStats.total,
|
|
196
|
+
healthyClients: connectionStats.healthy,
|
|
197
|
+
totalBindings: sessionBindings.size,
|
|
198
|
+
bindingCounts: balancerStats.bindingCounts,
|
|
199
|
+
strategy: balancerStats.strategy,
|
|
200
|
+
uptime: Date.now() - balancerStats.lastUpdate
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
const performHealthCheck = () => {
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
const healthCheckPromises = [];
|
|
206
|
+
forEachChildrenClient((ws, clientId) => {
|
|
207
|
+
const healthCheckPromise = (async () => {
|
|
208
|
+
try {
|
|
209
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
210
|
+
const pingPromise = new Promise((resolve) => {
|
|
211
|
+
const timeout = setTimeout(() => resolve(false), 5000);
|
|
212
|
+
ws.ping();
|
|
213
|
+
ws.once('pong', () => {
|
|
214
|
+
clearTimeout(timeout);
|
|
215
|
+
resolve(true);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
const isResponding = await pingPromise;
|
|
219
|
+
if (!isResponding) {
|
|
220
|
+
logger.warn({
|
|
221
|
+
code: 503,
|
|
222
|
+
message: `客户端健康检查失败: ${clientId}`,
|
|
223
|
+
data: { timestamp: now }
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
logger.warn({
|
|
229
|
+
code: 503,
|
|
230
|
+
message: `客户端连接已断开: ${clientId}`,
|
|
231
|
+
data: { readyState: ws.readyState }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
logger.error({
|
|
237
|
+
code: 500,
|
|
238
|
+
message: `客户端健康检查异常: ${clientId}`,
|
|
239
|
+
data: error
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
healthCheckPromises.push(healthCheckPromise);
|
|
244
|
+
});
|
|
245
|
+
Promise.all(healthCheckPromises).catch(error => {
|
|
246
|
+
logger.error({
|
|
247
|
+
code: 500,
|
|
248
|
+
message: '健康检查批量执行失败',
|
|
249
|
+
data: error
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
let healthCheckTimer = null;
|
|
254
|
+
const startHealthCheckTimer = () => {
|
|
255
|
+
if (healthCheckTimer) {
|
|
256
|
+
clearInterval(healthCheckTimer);
|
|
257
|
+
}
|
|
258
|
+
healthCheckTimer = setInterval(() => {
|
|
259
|
+
performHealthCheck();
|
|
260
|
+
}, 30000);
|
|
261
|
+
};
|
|
262
|
+
const stopHealthCheckTimer = () => {
|
|
263
|
+
if (healthCheckTimer) {
|
|
264
|
+
clearInterval(healthCheckTimer);
|
|
265
|
+
healthCheckTimer = null;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const destroyLoadBalancer = () => {
|
|
269
|
+
stopHealthCheckTimer();
|
|
270
|
+
clearChildrenClients();
|
|
271
|
+
platformConnectionState = clearAllConnections(platformConnectionState);
|
|
272
|
+
fullConnectionState = clearAllConnections(fullConnectionState);
|
|
273
|
+
sessionBindings.clear();
|
|
274
|
+
loadBalancerState = createLoadBalancerState(loadBalancerState.strategy);
|
|
275
|
+
logger.info('负载均衡器已清理');
|
|
22
276
|
};
|
|
23
|
-
const timeoutTime = 1000 * 60 * 3;
|
|
24
|
-
const reconnectInterval = 1000 * 6;
|
|
25
|
-
const HEARTBEAT_INTERVAL = 1000 * 18;
|
|
26
277
|
|
|
27
|
-
export {
|
|
278
|
+
export { actionResolves, actionTimeouts, addChildrenClient, addFullClient, addPlatformClient, apiResolves, apiTimeouts, bindSessionToClient, clearChildrenClients, clearFullClients, clearPlatformClients, destroyLoadBalancer, deviceId, forEachChildrenClient, forEachFullClient, forEachPlatformClient, getAllChildrenClients, getChildrenClient, getChildrenClientCount, getChildrenClientIds, getFullClient, getFullClientCount, getHealthyClients, getLoadBalanceStats, getPlatformClient, getPlatformClientCount, hasChildrenClient, performHealthCheck, reconnectInterval, removeChildrenClient, removeFullClient, removePlatformClient, selectClientByStrategy, setLoadBalanceStrategy, startHealthCheckTimer, stopHealthCheckTimer, timeoutTime, unbindSession };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Result } from '../../core';
|
|
2
|
+
type ResolversValue = (value: Result[]) => void;
|
|
3
|
+
export interface RequestHandlerState {
|
|
4
|
+
resolvers: Map<string, ResolversValue>;
|
|
5
|
+
timeouts: Map<string, NodeJS.Timeout>;
|
|
6
|
+
requestType: string;
|
|
7
|
+
idField: 'actionId' | 'apiId';
|
|
8
|
+
}
|
|
9
|
+
export declare const createRequestHandlerState: (requestType: string, idField: "actionId" | "apiId") => RequestHandlerState;
|
|
10
|
+
export declare const sendRequest: <T>(state: RequestHandlerState, data: T & {
|
|
11
|
+
DeviceId?: string;
|
|
12
|
+
}) => Promise<Result[]>;
|
|
13
|
+
export declare const handleRequestResponse: (state: RequestHandlerState, requestId: string, results: Result[]) => void;
|
|
14
|
+
export declare const handleRequestTimeout: (state: RequestHandlerState, requestId: string) => void;
|
|
15
|
+
export declare const getPendingRequestCount: (state: RequestHandlerState) => number;
|
|
16
|
+
export declare const destroyRequestHandler: (state: RequestHandlerState) => void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as flattedJSON from 'flatted';
|
|
2
|
+
import { ResultCode } from '../../core/variable.js';
|
|
3
|
+
import 'fs';
|
|
4
|
+
import 'path';
|
|
5
|
+
import 'yaml';
|
|
6
|
+
import { createResult } from '../../core/utils.js';
|
|
7
|
+
import { deviceId, timeoutTime } from './config.js';
|
|
8
|
+
import { generateUniqueId } from '../core/constants.js';
|
|
9
|
+
|
|
10
|
+
const createRequestHandlerState = (requestType, idField) => ({
|
|
11
|
+
resolvers: new Map(),
|
|
12
|
+
timeouts: new Map(),
|
|
13
|
+
requestType,
|
|
14
|
+
idField
|
|
15
|
+
});
|
|
16
|
+
const sendRequest = (state, data) => {
|
|
17
|
+
const requestId = generateUniqueId();
|
|
18
|
+
return new Promise(resolve => {
|
|
19
|
+
data[state.idField] = requestId;
|
|
20
|
+
data.DeviceId = deviceId;
|
|
21
|
+
global.chatbotClient.send(flattedJSON.stringify(data));
|
|
22
|
+
state.resolvers.set(requestId, resolve);
|
|
23
|
+
const timeout = setTimeout(() => {
|
|
24
|
+
handleRequestTimeout(state, requestId);
|
|
25
|
+
}, timeoutTime);
|
|
26
|
+
state.timeouts.set(requestId, timeout);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const handleRequestResponse = (state, requestId, results) => {
|
|
30
|
+
const resolver = state.resolvers.get(requestId);
|
|
31
|
+
const timeout = state.timeouts.get(requestId);
|
|
32
|
+
if (resolver) {
|
|
33
|
+
state.resolvers.delete(requestId);
|
|
34
|
+
if (timeout) {
|
|
35
|
+
clearTimeout(timeout);
|
|
36
|
+
state.timeouts.delete(requestId);
|
|
37
|
+
}
|
|
38
|
+
resolver(results);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const handleRequestTimeout = (state, requestId) => {
|
|
42
|
+
const resolver = state.resolvers.get(requestId);
|
|
43
|
+
if (resolver) {
|
|
44
|
+
state.resolvers.delete(requestId);
|
|
45
|
+
state.timeouts.delete(requestId);
|
|
46
|
+
const timeoutResult = createResult(ResultCode.Fail, `${state.requestType}请求超时`, null);
|
|
47
|
+
resolver([timeoutResult]);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const getPendingRequestCount = (state) => {
|
|
51
|
+
return state.resolvers.size;
|
|
52
|
+
};
|
|
53
|
+
const destroyRequestHandler = (state) => {
|
|
54
|
+
for (const timeout of state.timeouts.values()) {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
}
|
|
57
|
+
for (const [_requestId, resolver] of state.resolvers.entries()) {
|
|
58
|
+
const cancelResult = createResult(ResultCode.FailInternal, `${state.requestType}请求已取消`, null);
|
|
59
|
+
resolver([cancelResult]);
|
|
60
|
+
}
|
|
61
|
+
state.resolvers.clear();
|
|
62
|
+
state.timeouts.clear();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export { createRequestHandlerState, destroyRequestHandler, getPendingRequestCount, handleRequestResponse, handleRequestTimeout, sendRequest };
|