oak-backend-base 3.2.1 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/AppLoader.d.ts +13 -8
- package/lib/AppLoader.js +127 -91
- package/lib/ClusterAppLoader.d.ts +17 -0
- package/lib/ClusterAppLoader.js +142 -0
- package/lib/DbStore.d.ts +4 -2
- package/lib/DbStore.js +8 -2
- package/lib/cluster/DataSubscriber.d.ts +12 -7
- package/lib/cluster/DataSubscriber.js +45 -124
- package/lib/cluster/env.js +19 -5
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/package.json +5 -4
package/lib/AppLoader.d.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
3
|
-
import { AppLoader as GeneralAppLoader, EntityDict, OpRecord } from "oak-domain/lib/types";
|
|
3
|
+
import { AppLoader as GeneralAppLoader, Trigger, EntityDict, Watcher, OpRecord } from "oak-domain/lib/types";
|
|
4
4
|
import { DbStore } from "./DbStore";
|
|
5
5
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
|
6
6
|
import { IncomingHttpHeaders, IncomingMessage } from 'http';
|
|
7
7
|
import { Namespace } from 'socket.io';
|
|
8
|
-
import
|
|
8
|
+
import DataSubscriber from './cluster/DataSubscriber';
|
|
9
9
|
export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
|
|
10
|
-
|
|
10
|
+
protected dbStore: DbStore<ED, Cxt>;
|
|
11
11
|
private aspectDict;
|
|
12
12
|
private externalDependencies;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
protected dataSubscriber?: DataSubscriber<ED, Cxt>;
|
|
14
|
+
protected contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>;
|
|
15
15
|
private requireSth;
|
|
16
|
-
|
|
16
|
+
protected makeContext(cxtStr?: string, headers?: IncomingHttpHeaders): Promise<Cxt>;
|
|
17
|
+
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, ns?: Namespace, nsServer?: Namespace);
|
|
18
|
+
protected registerTrigger(trigger: Trigger<ED, keyof ED, Cxt>): void;
|
|
17
19
|
initTriggers(): void;
|
|
18
|
-
startWatchers(): void;
|
|
19
20
|
mount(initialize?: true): Promise<void>;
|
|
20
21
|
unmount(): Promise<void>;
|
|
21
|
-
execAspect(name: string,
|
|
22
|
+
execAspect(name: string, headers?: IncomingHttpHeaders, contextString?: string, params?: any): Promise<{
|
|
22
23
|
opRecords: OpRecord<ED>[];
|
|
23
24
|
result: any;
|
|
24
25
|
message?: string;
|
|
@@ -26,6 +27,10 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
|
|
|
26
27
|
initialize(dropIfExists?: boolean): Promise<void>;
|
|
27
28
|
getStore(): DbStore<ED, Cxt>;
|
|
28
29
|
getEndpoints(prefix: string): [string, "get" | "post" | "put" | "delete", string, (params: Record<string, string>, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise<any>][];
|
|
30
|
+
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
|
31
|
+
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt): Promise<Partial<ED[T]["Schema"]>[]>;
|
|
32
|
+
protected execWatcher(watcher: Watcher<ED, keyof ED, Cxt>): Promise<void>;
|
|
33
|
+
startWatchers(): void;
|
|
29
34
|
startTimers(): void;
|
|
30
35
|
execStartRoutines(): Promise<void>;
|
|
31
36
|
execRoutine(routine: (context: Cxt) => Promise<void>): Promise<void>;
|
package/lib/AppLoader.js
CHANGED
|
@@ -86,23 +86,36 @@ class AppLoader extends types_1.AppLoader {
|
|
|
86
86
|
Object.assign(sthOut, sth);
|
|
87
87
|
return sthOut;
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
async makeContext(cxtStr, headers) {
|
|
90
|
+
const context = await this.contextBuilder(cxtStr)(this.dbStore);
|
|
91
|
+
context.clusterInfo = (0, env_2.getClusterInfo)();
|
|
92
|
+
context.headers = headers;
|
|
93
|
+
return context;
|
|
94
|
+
}
|
|
95
|
+
constructor(path, contextBuilder, ns, nsServer) {
|
|
90
96
|
super(path);
|
|
91
97
|
const dbConfig = require((0, path_1.join)(path, '/configuration/mysql.json'));
|
|
92
98
|
const { storageSchema } = require(`${path}/lib/oak-app-domain/Storage`);
|
|
93
99
|
const { authDeduceRelationMap, selectFreeEntities, updateFreeDict } = require(`${path}/lib/config/relation`);
|
|
94
100
|
this.externalDependencies = require((0, env_1.OAK_EXTERNAL_LIBS_FILEPATH)((0, path_1.join)(path, 'lib')));
|
|
95
101
|
this.aspectDict = Object.assign({}, index_1.default, this.requireSth('lib/aspects/index'));
|
|
96
|
-
this.dbStore = new DbStore_1.DbStore(storageSchema,
|
|
102
|
+
this.dbStore = new DbStore_1.DbStore(storageSchema, (cxtStr) => this.makeContext(cxtStr), dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
|
97
103
|
if (ns) {
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
|
|
104
|
+
(0, assert_1.default)(nsServer);
|
|
105
|
+
this.dataSubscriber = new DataSubscriber_1.default(ns, nsServer, (scene) => this.contextBuilder(scene)(this.dbStore));
|
|
106
|
+
this.contextBuilder = (scene) => async (store) => {
|
|
107
|
+
const context = await contextBuilder(scene)(store);
|
|
101
108
|
// 注入在提交前向dataSubscribe
|
|
102
109
|
const originCommit = context.commit;
|
|
103
110
|
context.commit = async () => {
|
|
104
|
-
|
|
111
|
+
const { eventOperationMap, opRecords } = context;
|
|
105
112
|
await originCommit.call(context);
|
|
113
|
+
Object.keys(eventOperationMap).forEach((event) => {
|
|
114
|
+
const ids = eventOperationMap[event];
|
|
115
|
+
const opRecordsToPublish = opRecords.filter((ele) => !!ele.id && ids.includes(ele.id));
|
|
116
|
+
(0, assert_1.default)(opRecordsToPublish.length === ids.length, '要推送的事件的operation数量不足,请检查确保');
|
|
117
|
+
this.dataSubscriber.publishEvent(event, opRecordsToPublish, context.getSubscriberId());
|
|
118
|
+
});
|
|
106
119
|
};
|
|
107
120
|
return context;
|
|
108
121
|
};
|
|
@@ -111,79 +124,19 @@ class AppLoader extends types_1.AppLoader {
|
|
|
111
124
|
this.contextBuilder = contextBuilder;
|
|
112
125
|
}
|
|
113
126
|
}
|
|
127
|
+
registerTrigger(trigger) {
|
|
128
|
+
this.dbStore.registerTrigger(trigger);
|
|
129
|
+
}
|
|
114
130
|
initTriggers() {
|
|
115
131
|
const triggers = this.requireSth('lib/triggers/index');
|
|
116
132
|
const checkers = this.requireSth('lib/checkers/index');
|
|
117
133
|
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
|
118
134
|
const { triggers: adTriggers, checkers: adCheckers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
|
119
|
-
triggers.forEach((trigger) => this.
|
|
120
|
-
adTriggers.forEach((trigger) => this.
|
|
135
|
+
triggers.forEach((trigger) => this.registerTrigger(trigger));
|
|
136
|
+
adTriggers.forEach((trigger) => this.registerTrigger(trigger));
|
|
121
137
|
checkers.forEach((checker) => this.dbStore.registerChecker(checker));
|
|
122
138
|
adCheckers.forEach((checker) => this.dbStore.registerChecker(checker));
|
|
123
139
|
}
|
|
124
|
-
startWatchers() {
|
|
125
|
-
const watchers = this.requireSth('lib/watchers/index');
|
|
126
|
-
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
|
127
|
-
const { watchers: adWatchers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
|
128
|
-
const totalWatchers = watchers.concat(adWatchers);
|
|
129
|
-
let count = 0;
|
|
130
|
-
const doWatchers = async () => {
|
|
131
|
-
count++;
|
|
132
|
-
const start = Date.now();
|
|
133
|
-
const context = await this.contextBuilder()(this.dbStore, undefined, (0, env_2.getClusterInfo)());
|
|
134
|
-
for (const w of totalWatchers) {
|
|
135
|
-
await context.begin();
|
|
136
|
-
try {
|
|
137
|
-
if (w.hasOwnProperty('actionData')) {
|
|
138
|
-
const { entity, action, filter, actionData } = w;
|
|
139
|
-
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
140
|
-
const data = typeof actionData === 'function' ? await actionData() : actionData; // 这里有个奇怪的编译错误,不理解 by Xc
|
|
141
|
-
const result = await this.dbStore.operate(entity, {
|
|
142
|
-
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
143
|
-
action,
|
|
144
|
-
data,
|
|
145
|
-
filter: filter2
|
|
146
|
-
}, context, {
|
|
147
|
-
dontCollect: true,
|
|
148
|
-
});
|
|
149
|
-
console.log(`执行了watcher【${w.name}】,结果是:`, result);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
const { entity, projection, fn, filter } = w;
|
|
153
|
-
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
154
|
-
const projection2 = typeof projection === 'function' ? await projection() : projection;
|
|
155
|
-
const rows = await this.dbStore.select(entity, {
|
|
156
|
-
data: projection2,
|
|
157
|
-
filter: filter2,
|
|
158
|
-
}, context, {
|
|
159
|
-
dontCollect: true,
|
|
160
|
-
blockTrigger: true,
|
|
161
|
-
});
|
|
162
|
-
if (rows.length > 0) {
|
|
163
|
-
const result = await fn(context, rows);
|
|
164
|
-
console.log(`执行了watcher【${w.name}】,结果是:`, result);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
await context.commit();
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
await context.rollback();
|
|
171
|
-
console.error(`执行了watcher【${w.name}】,发生错误:`, err);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const duration = Date.now() - start;
|
|
175
|
-
console.log(`第${count}次执行watchers,共执行${watchers.length}个,耗时${duration}毫秒`);
|
|
176
|
-
const now = Date.now();
|
|
177
|
-
try {
|
|
178
|
-
await this.dbStore.checkpoint(process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000);
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
console.error(`执行了checkpoint,发生错误:`, err);
|
|
182
|
-
}
|
|
183
|
-
setTimeout(() => doWatchers(), 120000);
|
|
184
|
-
};
|
|
185
|
-
doWatchers();
|
|
186
|
-
}
|
|
187
140
|
async mount(initialize) {
|
|
188
141
|
const { path } = this;
|
|
189
142
|
if (!initialize) {
|
|
@@ -197,8 +150,8 @@ class AppLoader extends types_1.AppLoader {
|
|
|
197
150
|
(0, index_1.clearPorts)();
|
|
198
151
|
this.dbStore.disconnect();
|
|
199
152
|
}
|
|
200
|
-
async execAspect(name,
|
|
201
|
-
const context = await this.
|
|
153
|
+
async execAspect(name, headers, contextString, params) {
|
|
154
|
+
const context = await this.makeContext(contextString, headers);
|
|
202
155
|
const fn = this.aspectDict[name];
|
|
203
156
|
if (!fn) {
|
|
204
157
|
throw new Error(`不存在的接口名称: ${name}`);
|
|
@@ -209,7 +162,7 @@ class AppLoader extends types_1.AppLoader {
|
|
|
209
162
|
await context.commit();
|
|
210
163
|
await context.refineOpRecords();
|
|
211
164
|
return {
|
|
212
|
-
opRecords: context.opRecords,
|
|
165
|
+
opRecords: context.opRecords.map(ele => (0, lodash_1.omit)(ele, 'id')),
|
|
213
166
|
message: context.getMessage(),
|
|
214
167
|
result,
|
|
215
168
|
};
|
|
@@ -272,7 +225,7 @@ class AppLoader extends types_1.AppLoader {
|
|
|
272
225
|
}
|
|
273
226
|
}
|
|
274
227
|
endPointRouters.push([name, method, url, async (params, headers, req, body) => {
|
|
275
|
-
const context = await this.
|
|
228
|
+
const context = await this.makeContext(undefined, headers);
|
|
276
229
|
await context.begin();
|
|
277
230
|
try {
|
|
278
231
|
const result = await fn(context, params, headers, req, body);
|
|
@@ -297,18 +250,96 @@ class AppLoader extends types_1.AppLoader {
|
|
|
297
250
|
}
|
|
298
251
|
return endPointRouters;
|
|
299
252
|
}
|
|
253
|
+
operateInWatcher(entity, operation, context) {
|
|
254
|
+
return this.dbStore.operate(entity, operation, context, {
|
|
255
|
+
dontCollect: true,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
selectInWatcher(entity, selection, context) {
|
|
259
|
+
return this.dbStore.select(entity, selection, context, {
|
|
260
|
+
dontCollect: true,
|
|
261
|
+
blockTrigger: true,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async execWatcher(watcher) {
|
|
265
|
+
const context = await this.makeContext();
|
|
266
|
+
await context.begin();
|
|
267
|
+
try {
|
|
268
|
+
if (watcher.hasOwnProperty('actionData')) {
|
|
269
|
+
const { entity, action, filter, actionData } = watcher;
|
|
270
|
+
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
271
|
+
const data = typeof actionData === 'function' ? await (actionData)() : actionData;
|
|
272
|
+
const result = await this.operateInWatcher(entity, {
|
|
273
|
+
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
274
|
+
action,
|
|
275
|
+
data,
|
|
276
|
+
filter: filter2
|
|
277
|
+
}, context);
|
|
278
|
+
console.log(`执行了watcher【${watcher.name}】,结果是:`, result);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const { entity, projection, fn, filter } = watcher;
|
|
282
|
+
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
283
|
+
const projection2 = typeof projection === 'function' ? await projection() : projection;
|
|
284
|
+
const rows = await this.selectInWatcher(entity, {
|
|
285
|
+
data: projection2,
|
|
286
|
+
filter: filter2,
|
|
287
|
+
}, context);
|
|
288
|
+
if (rows.length > 0) {
|
|
289
|
+
const result = await fn(context, rows);
|
|
290
|
+
console.log(`执行了watcher【${watcher.name}】,结果是:`, result);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
await context.commit();
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
await context.rollback();
|
|
297
|
+
console.error(`执行了watcher【${watcher.name}】,发生错误:`, err);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
startWatchers() {
|
|
301
|
+
const watchers = this.requireSth('lib/watchers/index');
|
|
302
|
+
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
|
303
|
+
const { watchers: adWatchers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
|
304
|
+
const totalWatchers = watchers.concat(adWatchers);
|
|
305
|
+
let count = 0;
|
|
306
|
+
const doWatchers = async () => {
|
|
307
|
+
count++;
|
|
308
|
+
const start = Date.now();
|
|
309
|
+
for (const w of totalWatchers) {
|
|
310
|
+
await this.execWatcher(w);
|
|
311
|
+
}
|
|
312
|
+
const duration = Date.now() - start;
|
|
313
|
+
console.log(`第${count}次执行watchers,共执行${watchers.length}个,耗时${duration}毫秒`);
|
|
314
|
+
const now = Date.now();
|
|
315
|
+
try {
|
|
316
|
+
await this.dbStore.checkpoint(process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000);
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
console.error(`执行了checkpoint,发生错误:`, err);
|
|
320
|
+
}
|
|
321
|
+
setTimeout(() => doWatchers(), 120000);
|
|
322
|
+
};
|
|
323
|
+
doWatchers();
|
|
324
|
+
}
|
|
300
325
|
startTimers() {
|
|
301
326
|
const timers = this.requireSth('lib/timers/index');
|
|
302
327
|
for (const timer of timers) {
|
|
303
|
-
const { cron,
|
|
328
|
+
const { cron, name } = timer;
|
|
304
329
|
(0, node_schedule_1.scheduleJob)(name, cron, async (date) => {
|
|
305
330
|
const start = Date.now();
|
|
306
|
-
const context = await this.
|
|
331
|
+
const context = await this.makeContext();
|
|
307
332
|
await context.begin();
|
|
308
333
|
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}】`);
|
|
309
334
|
try {
|
|
310
|
-
|
|
311
|
-
|
|
335
|
+
if (timer.hasOwnProperty('entity')) {
|
|
336
|
+
await this.execWatcher(timer);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const { timer: timerFn } = timer;
|
|
340
|
+
const result = await timerFn(context);
|
|
341
|
+
console.log(`定时器【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
|
342
|
+
}
|
|
312
343
|
await context.commit();
|
|
313
344
|
}
|
|
314
345
|
catch (err) {
|
|
@@ -321,23 +352,28 @@ class AppLoader extends types_1.AppLoader {
|
|
|
321
352
|
async execStartRoutines() {
|
|
322
353
|
const routines = this.requireSth('lib/routines/start');
|
|
323
354
|
for (const routine of routines) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const start = Date.now();
|
|
327
|
-
await context.begin();
|
|
328
|
-
try {
|
|
329
|
-
const result = await fn(context);
|
|
330
|
-
console.log(`例程【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
|
331
|
-
await context.commit();
|
|
355
|
+
if (routine.hasOwnProperty('entity')) {
|
|
356
|
+
this.execWatcher(routine);
|
|
332
357
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
await
|
|
358
|
+
else {
|
|
359
|
+
const { name, routine: routineFn } = routine;
|
|
360
|
+
const context = await this.makeContext();
|
|
361
|
+
const start = Date.now();
|
|
362
|
+
await context.begin();
|
|
363
|
+
try {
|
|
364
|
+
const result = await routineFn(context);
|
|
365
|
+
console.log(`例程【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
|
366
|
+
await context.commit();
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
console.warn(`例程【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
|
370
|
+
await context.rollback();
|
|
371
|
+
}
|
|
336
372
|
}
|
|
337
373
|
}
|
|
338
374
|
}
|
|
339
375
|
async execRoutine(routine) {
|
|
340
|
-
const context = await this.
|
|
376
|
+
const context = await this.makeContext();
|
|
341
377
|
await routine(context);
|
|
342
378
|
}
|
|
343
379
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
2
|
+
import { EntityDict, OperationResult, Trigger } from 'oak-domain/lib/types';
|
|
3
|
+
import { BackendRuntimeContext } from 'oak-frontend-base';
|
|
4
|
+
import { AppLoader } from './AppLoader';
|
|
5
|
+
import { DbStore } from './DbStore';
|
|
6
|
+
import { Namespace } from 'socket.io';
|
|
7
|
+
import { Socket } from 'socket.io-client';
|
|
8
|
+
export declare class ClusterAppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends AppLoader<ED, Cxt> {
|
|
9
|
+
protected socket: Socket;
|
|
10
|
+
private csTriggers;
|
|
11
|
+
private connect;
|
|
12
|
+
private sub;
|
|
13
|
+
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, nsDs: Namespace, nsServer: Namespace, socketPath: string);
|
|
14
|
+
protected registerTrigger(trigger: Trigger<ED, keyof ED, Cxt>): void;
|
|
15
|
+
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt): Promise<OperationResult<ED>>;
|
|
16
|
+
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt): Promise<Partial<ED[T]['Schema']>[]>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClusterAppLoader = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
|
6
|
+
const filter_1 = require("oak-domain/lib/store/filter");
|
|
7
|
+
const env_1 = require("./cluster/env");
|
|
8
|
+
const AppLoader_1 = require("./AppLoader");
|
|
9
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
10
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
11
|
+
class ClusterAppLoader extends AppLoader_1.AppLoader {
|
|
12
|
+
socket;
|
|
13
|
+
csTriggers;
|
|
14
|
+
connect() {
|
|
15
|
+
const { instanceId } = (0, env_1.getClusterInfo)();
|
|
16
|
+
this.socket.on('connect', () => {
|
|
17
|
+
const csTriggerNames = Object.keys(this.csTriggers).map(ele => `${ele}-${instanceId}`);
|
|
18
|
+
if (csTriggerNames.length > 0) {
|
|
19
|
+
this.socket.emit('sub', csTriggerNames);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
this.socket.on('disconnect', () => {
|
|
23
|
+
const csTriggerNames = Object.keys(this.csTriggers).map(ele => `${ele}-${instanceId}`);
|
|
24
|
+
if (csTriggerNames.length > 0) {
|
|
25
|
+
this.socket.connect();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
this.socket.on('data', async (entity, name, ids, cxtStr, option) => {
|
|
29
|
+
const context = await this.makeContext(cxtStr);
|
|
30
|
+
await context.begin();
|
|
31
|
+
try {
|
|
32
|
+
await this.dbStore.execVolatileTrigger(entity, name, ids, context, option);
|
|
33
|
+
await context.commit();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
await context.rollback();
|
|
37
|
+
console.error('在集群环境下,处理来自其它实例的trigger数据,execVolatileTrigger异常', entity, name, ids, option, err);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
this.socket.connect();
|
|
41
|
+
}
|
|
42
|
+
sub(name) {
|
|
43
|
+
const { instanceId } = (0, env_1.getClusterInfo)();
|
|
44
|
+
(0, assert_1.default)(!this.csTriggers[name], `命名为${name}的trigger出现了多次,请检查`);
|
|
45
|
+
this.csTriggers[name] = 1;
|
|
46
|
+
if (this.socket.connected) {
|
|
47
|
+
this.socket.emit('sub', [`${name}-${instanceId}`]);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.socket.connect();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
constructor(path, contextBuilder, nsDs, nsServer, socketPath) {
|
|
54
|
+
super(path, contextBuilder, nsDs, nsServer);
|
|
55
|
+
this.dbStore.setOnVolatileTrigger(async (entity, trigger, ids, cxtStr, option) => {
|
|
56
|
+
const execLocal = async (ids2) => {
|
|
57
|
+
const context = await this.makeContext(cxtStr);
|
|
58
|
+
await context.begin();
|
|
59
|
+
try {
|
|
60
|
+
await this.dbStore.execVolatileTrigger(entity, trigger.name, ids2, context, option);
|
|
61
|
+
await context.commit();
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
await context.rollback();
|
|
65
|
+
console.error('execVolatileTrigger异常', entity, trigger.name, ids2, option, err);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
if (trigger.cs) {
|
|
69
|
+
// 如果是cluster sensative的触发器,需要发送到相应的instance上被处理
|
|
70
|
+
const context = await this.makeContext();
|
|
71
|
+
const rows = await context.select(entity, {
|
|
72
|
+
data: {
|
|
73
|
+
id: 1,
|
|
74
|
+
$$seq$$: 1,
|
|
75
|
+
},
|
|
76
|
+
filter: {
|
|
77
|
+
id: { $in: ids },
|
|
78
|
+
}
|
|
79
|
+
}, { dontCollect: true });
|
|
80
|
+
await context.commit();
|
|
81
|
+
const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
|
|
82
|
+
const grouped = (0, lodash_1.groupBy)(rows, (ele) => ele.$$seq$$ % instanceCount);
|
|
83
|
+
for (const seqMod in grouped) {
|
|
84
|
+
const ids2 = grouped[seqMod].map(ele => ele.id);
|
|
85
|
+
if (parseInt(seqMod) === instanceId) {
|
|
86
|
+
await execLocal(ids2);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.dataSubscriber.publishVolatileTrigger(entity, trigger.name, seqMod, ids2, cxtStr, option);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
await execLocal(ids);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const { name } = nsServer;
|
|
98
|
+
const socketUrl = `http://localhost:${process.env.PM2_PORT || 8080}${name}`;
|
|
99
|
+
this.socket = (0, socket_io_client_1.io)(socketUrl, {
|
|
100
|
+
path: socketPath,
|
|
101
|
+
});
|
|
102
|
+
this.connect();
|
|
103
|
+
this.csTriggers = {};
|
|
104
|
+
}
|
|
105
|
+
registerTrigger(trigger) {
|
|
106
|
+
// 如果是cluster sensative的trigger,注册到socket事件上
|
|
107
|
+
if (trigger.when === 'commit' && trigger.cs) {
|
|
108
|
+
const { name } = trigger;
|
|
109
|
+
this.sub(name);
|
|
110
|
+
}
|
|
111
|
+
this.dbStore.registerTrigger(trigger);
|
|
112
|
+
}
|
|
113
|
+
operateInWatcher(entity, operation, context) {
|
|
114
|
+
const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
|
|
115
|
+
(0, assert_1.default)(instanceCount && typeof instanceId === 'number');
|
|
116
|
+
const { filter } = operation;
|
|
117
|
+
const filter2 = (0, filter_1.combineFilters)(entity, this.dbStore.getSchema(), [filter, {
|
|
118
|
+
$$seq$$: {
|
|
119
|
+
$mod: [instanceCount, instanceId]
|
|
120
|
+
}
|
|
121
|
+
}]);
|
|
122
|
+
return super.operateInWatcher(entity, {
|
|
123
|
+
...operation,
|
|
124
|
+
filter: filter2,
|
|
125
|
+
}, context);
|
|
126
|
+
}
|
|
127
|
+
selectInWatcher(entity, selection, context) {
|
|
128
|
+
const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
|
|
129
|
+
(0, assert_1.default)(instanceCount && typeof instanceId === 'number');
|
|
130
|
+
const { filter } = selection;
|
|
131
|
+
const filter2 = (0, filter_1.combineFilters)(entity, this.dbStore.getSchema(), [filter, {
|
|
132
|
+
$$seq$$: {
|
|
133
|
+
$mod: [instanceCount, instanceId]
|
|
134
|
+
}
|
|
135
|
+
}]);
|
|
136
|
+
return super.selectInWatcher(entity, {
|
|
137
|
+
...selection,
|
|
138
|
+
filter: filter2,
|
|
139
|
+
}, context);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.ClusterAppLoader = ClusterAppLoader;
|
package/lib/DbStore.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MysqlStore, MySqlSelectOption, MysqlOperateOption } from 'oak-db';
|
|
2
|
-
import { EntityDict, StorageSchema, Trigger, Checker, SelectOption, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap } from 'oak-domain/lib/types';
|
|
2
|
+
import { EntityDict, StorageSchema, Trigger, Checker, SelectOption, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap, VolatileTrigger, OperateOption } from 'oak-domain/lib/types';
|
|
3
3
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
4
4
|
import { MySQLConfiguration } from 'oak-db/lib/MySQL/types/Configuration';
|
|
5
5
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
|
@@ -7,12 +7,14 @@ import { AsyncContext, AsyncRowStore } from 'oak-domain/lib/store/AsyncRowStore'
|
|
|
7
7
|
export declare class DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends MysqlStore<ED, Cxt> implements AsyncRowStore<ED, Cxt> {
|
|
8
8
|
private executor;
|
|
9
9
|
private relationAuth;
|
|
10
|
-
constructor(storageSchema: StorageSchema<ED>, contextBuilder: (scene?: string) =>
|
|
10
|
+
constructor(storageSchema: StorageSchema<ED>, contextBuilder: (scene?: string) => Promise<Cxt>, mysqlConfiguration: MySQLConfiguration, authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: SelectFreeEntities<ED>, updateFreeDict?: UpdateFreeDict<ED>, onVolatileTrigger?: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>);
|
|
11
11
|
protected cascadeUpdateAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, option: MysqlOperateOption): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
|
12
12
|
operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: MysqlOperateOption): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
|
13
13
|
select<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: MySqlSelectOption): Promise<Partial<ED[T]["Schema"]>[]>;
|
|
14
14
|
count<T extends keyof ED>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: SelectOption): Promise<number>;
|
|
15
15
|
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>): void;
|
|
16
16
|
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>): void;
|
|
17
|
+
setOnVolatileTrigger(onVolatileTrigger: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>): void;
|
|
18
|
+
execVolatileTrigger<T extends keyof ED>(entity: T, name: string, ids: string[], context: Cxt, option: OperateOption): Promise<void>;
|
|
17
19
|
checkpoint(ts: number): Promise<number>;
|
|
18
20
|
}
|
package/lib/DbStore.js
CHANGED
|
@@ -7,9 +7,9 @@ const RelationAuth_1 = require("oak-domain/lib/store/RelationAuth");
|
|
|
7
7
|
class DbStore extends oak_db_1.MysqlStore {
|
|
8
8
|
executor;
|
|
9
9
|
relationAuth;
|
|
10
|
-
constructor(storageSchema, contextBuilder, mysqlConfiguration, authDeduceRelationMap, selectFreeEntities = [], updateFreeDict = {}) {
|
|
10
|
+
constructor(storageSchema, contextBuilder, mysqlConfiguration, authDeduceRelationMap, selectFreeEntities = [], updateFreeDict = {}, onVolatileTrigger) {
|
|
11
11
|
super(storageSchema, mysqlConfiguration);
|
|
12
|
-
this.executor = new TriggerExecutor_1.TriggerExecutor((scene) => contextBuilder(scene)
|
|
12
|
+
this.executor = new TriggerExecutor_1.TriggerExecutor((scene) => contextBuilder(scene), undefined, onVolatileTrigger);
|
|
13
13
|
this.relationAuth = new RelationAuth_1.RelationAuth(storageSchema, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
|
14
14
|
}
|
|
15
15
|
async cascadeUpdateAsync(entity, operation, context, option) {
|
|
@@ -109,6 +109,12 @@ class DbStore extends oak_db_1.MysqlStore {
|
|
|
109
109
|
registerChecker(checker) {
|
|
110
110
|
this.executor.registerChecker(checker);
|
|
111
111
|
}
|
|
112
|
+
setOnVolatileTrigger(onVolatileTrigger) {
|
|
113
|
+
this.executor.setOnVolatileTrigger(onVolatileTrigger);
|
|
114
|
+
}
|
|
115
|
+
async execVolatileTrigger(entity, name, ids, context, option) {
|
|
116
|
+
return this.executor.execVolatileTrigger(entity, name, ids, context, option);
|
|
117
|
+
}
|
|
112
118
|
checkpoint(ts) {
|
|
113
119
|
return this.executor.checkpoint(ts);
|
|
114
120
|
}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { EntityDict } from 'oak-domain/lib/types';
|
|
1
|
+
import { EntityDict, OperateOption, OpRecord } from 'oak-domain/lib/types';
|
|
2
2
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
3
3
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
|
4
4
|
import { Namespace } from 'socket.io';
|
|
5
|
+
/**
|
|
6
|
+
* 集群行为备忘:
|
|
7
|
+
* 当socket.io通过adapter在集群间通信时,测试行为如下(测试环境为pm2 + cluster-adapter,其它adpater启用时需要再测一次):
|
|
8
|
+
* 1)当client连接到node1并join room1时,只有node1上会有create room事件(room结构本身在结点间并不共享)
|
|
9
|
+
* 2)当某一个node执行 .adapter.to('room1').emit()时,连接到任一结点的client均能收到消息(但使用room可以实现跨结点推包)
|
|
10
|
+
* 3) serverSideEmit执行时如果有callback,而不是所有的接收者都执行callback的话,会抛出一个异常(意味着不需要本结点来判定是否收到全部的返回值了)
|
|
11
|
+
*/
|
|
5
12
|
export default class DataSubscriber<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> {
|
|
6
13
|
private ns;
|
|
14
|
+
private nsServer;
|
|
7
15
|
private contextBuilder;
|
|
8
|
-
|
|
9
|
-
private idEntityMap;
|
|
10
|
-
constructor(ns: Namespace, contextBuilder: (scene?: string) => Promise<Context>);
|
|
11
|
-
private formCreateRoomRoutine;
|
|
16
|
+
constructor(ns: Namespace, nsServer: Namespace, contextBuilder: (scene?: string) => Promise<Context>);
|
|
12
17
|
/**
|
|
13
18
|
* 来自外部的socket连接,监听数据变化
|
|
14
19
|
*/
|
|
15
20
|
private startup;
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
publishEvent(event: string, records: OpRecord<ED>[], sid?: string): void;
|
|
22
|
+
publishVolatileTrigger(entity: keyof ED, name: string, instanceNumber: string, ids: string[], cxtStr: string, option: OperateOption): void;
|
|
18
23
|
}
|
|
@@ -1,41 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const env_1 = require("./env");
|
|
4
|
+
/**
|
|
5
|
+
* 集群行为备忘:
|
|
6
|
+
* 当socket.io通过adapter在集群间通信时,测试行为如下(测试环境为pm2 + cluster-adapter,其它adpater启用时需要再测一次):
|
|
7
|
+
* 1)当client连接到node1并join room1时,只有node1上会有create room事件(room结构本身在结点间并不共享)
|
|
8
|
+
* 2)当某一个node执行 .adapter.to('room1').emit()时,连接到任一结点的client均能收到消息(但使用room可以实现跨结点推包)
|
|
9
|
+
* 3) serverSideEmit执行时如果有callback,而不是所有的接收者都执行callback的话,会抛出一个异常(意味着不需要本结点来判定是否收到全部的返回值了)
|
|
10
|
+
*/
|
|
5
11
|
class DataSubscriber {
|
|
6
12
|
ns;
|
|
13
|
+
nsServer;
|
|
7
14
|
contextBuilder;
|
|
8
|
-
|
|
9
|
-
idEntityMap;
|
|
10
|
-
constructor(ns, contextBuilder) {
|
|
15
|
+
constructor(ns, nsServer, contextBuilder) {
|
|
11
16
|
this.ns = ns;
|
|
17
|
+
this.nsServer = nsServer;
|
|
12
18
|
this.contextBuilder = contextBuilder;
|
|
13
19
|
this.startup();
|
|
14
|
-
this.filterMap = {};
|
|
15
|
-
this.idEntityMap = {};
|
|
16
|
-
}
|
|
17
|
-
formCreateRoomRoutine(def) {
|
|
18
|
-
const { id, entity, filter } = def;
|
|
19
|
-
return (room) => {
|
|
20
|
-
if (room === id) {
|
|
21
|
-
console.log('instance:', process.env.NODE_APP_INSTANCE, 'add filter', room);
|
|
22
|
-
// 本房间不存在,说明这个filter是新出现的
|
|
23
|
-
if (this.filterMap[entity]) {
|
|
24
|
-
// id的唯一性由前台保证,重复则无视
|
|
25
|
-
Object.assign(this.filterMap[entity], {
|
|
26
|
-
[id]: filter,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
Object.assign(this.filterMap, {
|
|
31
|
-
[entity]: {
|
|
32
|
-
[id]: filter,
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
this.idEntityMap[id] = entity;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
20
|
}
|
|
40
21
|
/**
|
|
41
22
|
* 来自外部的socket连接,监听数据变化
|
|
@@ -43,39 +24,14 @@ class DataSubscriber {
|
|
|
43
24
|
startup() {
|
|
44
25
|
this.ns.on('connection', async (socket) => {
|
|
45
26
|
try {
|
|
46
|
-
const {
|
|
47
|
-
|
|
48
|
-
socket.
|
|
49
|
-
|
|
50
|
-
socket.idMap = {};
|
|
51
|
-
socket.on('sub', async (data) => {
|
|
52
|
-
try {
|
|
53
|
-
console.log('instance:', process.env.NODE_APP_INSTANCE, 'on sub', JSON.stringify(data));
|
|
54
|
-
await Promise.all(data.map(async (ele) => {
|
|
55
|
-
const { id, entity, filter } = ele;
|
|
56
|
-
// 尝试select此filter,如果失败说明权限越界
|
|
57
|
-
await context.select(entity, {
|
|
58
|
-
data: {
|
|
59
|
-
id: 1,
|
|
60
|
-
},
|
|
61
|
-
filter,
|
|
62
|
-
}, {});
|
|
63
|
-
}));
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
socket.emit('error', err.toString());
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
data.forEach((ele) => {
|
|
70
|
-
const createRoomRoutine = this.formCreateRoomRoutine(ele);
|
|
71
|
-
this.ns.adapter.on('create-room', createRoomRoutine);
|
|
72
|
-
socket.join(ele.id);
|
|
73
|
-
this.ns.adapter.off('create-room', createRoomRoutine);
|
|
74
|
-
});
|
|
27
|
+
const { instanceId } = (0, env_1.getClusterInfo)();
|
|
28
|
+
// console.log('on connection', instanceId);
|
|
29
|
+
socket.on('sub', async (events) => {
|
|
30
|
+
events.forEach((event) => socket.join(event));
|
|
75
31
|
});
|
|
76
|
-
socket.on('unsub', (
|
|
32
|
+
socket.on('unsub', (events) => {
|
|
77
33
|
// console.log('instance:', process.env.NODE_APP_INSTANCE, 'on unsub', JSON.stringify(ids));
|
|
78
|
-
|
|
34
|
+
events.forEach((id) => {
|
|
79
35
|
socket.leave(id);
|
|
80
36
|
});
|
|
81
37
|
});
|
|
@@ -84,75 +40,40 @@ class DataSubscriber {
|
|
|
84
40
|
socket.emit('error', err.toString());
|
|
85
41
|
}
|
|
86
42
|
});
|
|
87
|
-
this.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
(
|
|
92
|
-
|
|
43
|
+
this.nsServer.on('connection', async (socket) => {
|
|
44
|
+
try {
|
|
45
|
+
const { instanceId } = (0, env_1.getClusterInfo)();
|
|
46
|
+
console.log('on nsServer connection', instanceId);
|
|
47
|
+
socket.on('sub', async (events) => {
|
|
48
|
+
console.log('on nsServer sub', instanceId, events);
|
|
49
|
+
events.forEach((event) => socket.join(event));
|
|
50
|
+
});
|
|
51
|
+
socket.on('unsub', (events) => {
|
|
52
|
+
// console.log('instance:', process.env.NODE_APP_INSTANCE, 'on unsub', JSON.stringify(ids));
|
|
53
|
+
events.forEach((id) => {
|
|
54
|
+
socket.leave(id);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
socket.emit('error', err.toString());
|
|
93
60
|
}
|
|
94
|
-
});
|
|
95
|
-
this.ns.on('sendRecord', (entity, filter, record, isCreate) => {
|
|
96
|
-
console.log('instance:', process.env.NODE_APP_INSTANCE, 'get record from another', JSON.stringify(entity));
|
|
97
61
|
});
|
|
98
62
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
63
|
+
publishEvent(event, records, sid) {
|
|
64
|
+
const { instanceId } = (0, env_1.getClusterInfo)();
|
|
65
|
+
// console.log('publishEvent', instanceId);
|
|
66
|
+
if (sid) {
|
|
67
|
+
this.ns.to(event).except(sid).emit('data', records);
|
|
102
68
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Object.keys(this.filterMap[entity]).forEach(async (room) => {
|
|
106
|
-
const context = await this.contextBuilder();
|
|
107
|
-
const filter2 = this.filterMap[entity][room];
|
|
108
|
-
let needSend = false;
|
|
109
|
-
if (isCreate) {
|
|
110
|
-
// 如果是插入数据肯定是单行,使用相容性检测
|
|
111
|
-
const contained = await (0, oak_domain_1.checkFilterContains)(entity, context, filter2, filter, true);
|
|
112
|
-
needSend = contained;
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const repeled = await (0, oak_domain_1.checkFilterRepel)(entity, context, filter, filter2, true);
|
|
116
|
-
needSend = !repeled;
|
|
117
|
-
}
|
|
118
|
-
if (needSend) {
|
|
119
|
-
// console.log('instance:', process.env.NODE_APP_INSTANCE, 'needSend', JSON.stringify(room));
|
|
120
|
-
if (sid) {
|
|
121
|
-
this.ns.to(room).except(sid).emit('data', [record], [room]);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
this.ns.to(room).emit('data', [record], [room]);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
69
|
+
else {
|
|
70
|
+
this.ns.to(event).emit('data', records);
|
|
128
71
|
}
|
|
129
72
|
}
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const { a } = record;
|
|
135
|
-
switch (a) {
|
|
136
|
-
case 'c': {
|
|
137
|
-
const { e, d } = record;
|
|
138
|
-
this.sendRecord(e, d, record, sid, true);
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
case 'u': {
|
|
142
|
-
const { e, d, f } = record;
|
|
143
|
-
this.sendRecord(e, f, record, sid);
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
case 'r': {
|
|
147
|
-
const { e, f } = record;
|
|
148
|
-
this.sendRecord(e, f, record, sid);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
default: {
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
});
|
|
73
|
+
publishVolatileTrigger(entity, name, instanceNumber, ids, cxtStr, option) {
|
|
74
|
+
const { instanceId } = (0, env_1.getClusterInfo)();
|
|
75
|
+
console.log('publishVolatileTrigger', instanceId, instanceNumber);
|
|
76
|
+
this.nsServer.to(`${name}-${instanceNumber}`).emit('data', entity, name, ids, cxtStr, option);
|
|
156
77
|
}
|
|
157
78
|
}
|
|
158
79
|
exports.default = DataSubscriber;
|
package/lib/cluster/env.js
CHANGED
|
@@ -14,13 +14,27 @@ function getProcessEnvOption(option) {
|
|
|
14
14
|
return process.env[upperCase];
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
//
|
|
17
|
+
// 初始化判定集群状态,需要在环境变量中注入两个值
|
|
18
|
+
/** pm2注入方法,见:https://pm2.fenxianglu.cn/docs/general/environment-variables
|
|
19
|
+
* apps: [
|
|
20
|
+
{
|
|
21
|
+
name: 'xxx',
|
|
22
|
+
script: "xxxjs",
|
|
23
|
+
instances: "2",
|
|
24
|
+
increment_var: "OAK_INSTANCE_ID",
|
|
25
|
+
env: {
|
|
26
|
+
OAK_INSTANCE_CNT: 9,
|
|
27
|
+
OAK_INSTANCE_ID: 8,
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
**/
|
|
18
32
|
function initialize() {
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
33
|
+
const instanceIdStr = getProcessEnvOption('OAK_INSTANCE_ID');
|
|
34
|
+
if (instanceIdStr) {
|
|
21
35
|
const usingCluster = true;
|
|
22
|
-
const instanceId = parseInt(
|
|
23
|
-
const instanceCount = parseInt(getProcessEnvOption('
|
|
36
|
+
const instanceId = parseInt(instanceIdStr);
|
|
37
|
+
const instanceCount = parseInt(getProcessEnvOption('OAK_INSTANCE_CNT'));
|
|
24
38
|
return {
|
|
25
39
|
usingCluster,
|
|
26
40
|
instanceCount,
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AppLoader = void 0;
|
|
3
|
+
exports.ClusterAppLoader = exports.AppLoader = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
var AppLoader_1 = require("./AppLoader");
|
|
6
6
|
Object.defineProperty(exports, "AppLoader", { enumerable: true, get: function () { return AppLoader_1.AppLoader; } });
|
|
7
|
+
var ClusterAppLoader_1 = require("./ClusterAppLoader");
|
|
8
|
+
Object.defineProperty(exports, "ClusterAppLoader", { enumerable: true, get: function () { return ClusterAppLoader_1.ClusterAppLoader; } });
|
|
7
9
|
tslib_1.__exportStar(require("./cluster/env"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oak-backend-base",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "oak-backend-base",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"author": {
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
"mysql2": "^2.3.3",
|
|
22
22
|
"node-schedule": "^2.1.0",
|
|
23
23
|
"oak-common-aspect": "^2.2.3",
|
|
24
|
-
"oak-
|
|
25
|
-
"oak-
|
|
26
|
-
"oak-
|
|
24
|
+
"oak-db": "^3.0.4",
|
|
25
|
+
"oak-domain": "^4.0.1",
|
|
26
|
+
"oak-frontend-base": "^4.0.2",
|
|
27
27
|
"socket.io": "^4.7.2",
|
|
28
|
+
"socket.io-client": "^4.7.2",
|
|
28
29
|
"uuid": "^8.3.2"
|
|
29
30
|
},
|
|
30
31
|
"license": "ISC",
|