Skip to content

Commit 3385d57

Browse files
authored
feat: use SingletonProto for egg ctx object (#92)
* feat: use SingletonProto for egg ctx object
1 parent e14bdb2 commit 3385d57

File tree

19 files changed

+213
-105
lines changed

19 files changed

+213
-105
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,31 @@ export class HelloService {
350350
}
351351
```
352352

353+
### egg 内 ctx/app 命名冲突
354+
355+
egg 内可能出现 ctx 和 app 上有同名对象的存在,我们可以通过使用 `EggQualifier` 来明确指定注入的对象来自 ctx 还是 app。不指定时,默认注入 app 上的对象。
356+
357+
###### 定义
358+
359+
```typescript
360+
@EggQualifier(eggType: EggType)
361+
```
362+
363+
###### 示例
364+
365+
```typescript
366+
import { EggLogger } from 'egg';
367+
import { Inject, EggQualifier, EggType } from '@eggjs/tegg';
368+
369+
@ContextProto()
370+
export class HelloService {
371+
@Inject()
372+
// 明确指定注入 ctx 上的 foo 而不是 app 上的 foo
373+
@EggQualifier(EggType.CONTEXT)
374+
foo: Foo;
375+
}
376+
```
377+
353378
### 单测
354379

355380
#### 单测 Context

core/core-decorator/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ export * from './src/decorator/InitTypeQualifier';
44
export * from './src/decorator/ModuleQualifier';
55
export * from './src/decorator/ContextProto';
66
export * from './src/decorator/SingletonProto';
7+
export * from './src/decorator/EggQualifier';
78

89
export * from './src/enum/AccessLevel';
910
export * from './src/enum/ObjectInitType';
11+
export * from './src/enum/EggType';
1012

1113
export * from './src/model/EggPrototypeInfo';
1214
export * from './src/model/InjectObjectInfo';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { QualifierUtil } from '../util/QualifierUtil';
2+
import { EggProtoImplClass } from '../model/EggPrototypeInfo';
3+
import { EggType } from '../enum/EggType';
4+
5+
export const EggQualifierAttribute = Symbol.for('Qualifier.Egg');
6+
7+
export function EggQualifier(eggType: EggType) {
8+
return function(target: any, propertyKey: PropertyKey) {
9+
QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, EggQualifierAttribute, eggType);
10+
};
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum EggType {
2+
APP = 'APP',
3+
CONTEXT = 'CONTEXT',
4+
}

core/eventbus-runtime/src/SingletonEventBus.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AccessLevel, InitTypeQualifier, Inject, ObjectInitType, SingletonProto } from '@eggjs/core-decorator';
1+
import { AccessLevel, Inject, SingletonProto } from '@eggjs/core-decorator';
22
import { EventBus, Events, EventWaiter, EventName, CORK_ID } from '@eggjs/eventbus-decorator';
33
import { ContextHandler, EggContext } from '@eggjs/tegg-runtime';
44
import type { EggLogger } from 'egg';
@@ -42,27 +42,12 @@ export class SingletonEventBus implements EventBus, EventWaiter {
4242
@Inject({
4343
name: 'logger',
4444
})
45-
@InitTypeQualifier(ObjectInitType.CONTEXT)
46-
private readonly ctxLogger: EggLogger;
47-
48-
@Inject({
49-
name: 'logger',
50-
})
51-
@InitTypeQualifier(ObjectInitType.SINGLETON)
52-
private readonly singletonLogger: EggLogger;
45+
private readonly logger: EggLogger;
5346

5447
private corkIdSequence = 0;
5548

5649
private readonly corkedEvents = new Map<string /* corkId */, CorkEvents>();
5750

58-
get logger(): EggLogger {
59-
try {
60-
return this.ctxLogger;
61-
} catch (_) {
62-
return this.singletonLogger;
63-
}
64-
}
65-
6651
/**
6752
* only use for ensure event will happen
6853
*/

plugin/eventbus/lib/EventbusLoadUnitHook.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LifecycleHook } from '@eggjs/tegg';
1+
import { EggQualifierAttribute, EggType, LifecycleHook, QualifierUtil } from '@eggjs/tegg';
22
import {
33
EggLoadUnitType,
44
EggPrototypeCreatorFactory,
@@ -14,6 +14,9 @@ const REGISTER_CLAZZ = [
1414
SingletonEventBus,
1515
];
1616

17+
// EggQualifier only for egg plugin
18+
QualifierUtil.addProperQualifier(SingletonEventBus, 'logger', EggQualifierAttribute, EggType.APP);
19+
1720
export class EventbusLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> {
1821
async postCreate(_ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> {
1922
if (loadUnit.type === EggLoadUnitType.APP) {

plugin/tegg/app.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { CompatibleUtil } from './lib/CompatibleUtil';
77
import { ModuleHandler } from './lib/ModuleHandler';
88
import { EggContextHandler } from './lib/EggContextHandler';
99
import { hijackRunInBackground } from './lib/run_in_background';
10+
import { EggQualifierProtoHook } from './lib/EggQualifierProtoHook';
1011

1112
export default class App {
1213
private readonly app: Application;
1314
private compatibleHook?: EggContextCompatibleHook;
1415
private eggContextHandler: EggContextHandler;
16+
private eggQualifierProtoHook: EggQualifierProtoHook;
1517

1618
constructor(app: Application) {
1719
this.app = app;
@@ -24,7 +26,9 @@ export default class App {
2426
configDidLoad() {
2527
this.eggContextHandler = new EggContextHandler(this.app);
2628
this.app.eggContextHandler = this.eggContextHandler;
29+
this.eggQualifierProtoHook = new EggQualifierProtoHook(this.app);
2730
this.eggContextHandler.register();
31+
this.app.loadUnitLifecycleUtil.registerLifecycle(this.eggQualifierProtoHook);
2832
this.app.moduleHandler = new ModuleHandler(this.app);
2933
}
3034

@@ -41,5 +45,8 @@ export default class App {
4145
if (this.compatibleHook) {
4246
this.app.eggContextLifecycleUtil.deleteLifecycle(this.compatibleHook);
4347
}
48+
if (this.eggQualifierProtoHook) {
49+
this.app.loadUnitLifecycleUtil.deleteLifecycle(this.eggQualifierProtoHook);
50+
}
4451
}
4552
}

plugin/tegg/lib/EggAppLoader.ts

Lines changed: 37 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Application } from 'egg';
22
import { Loader, TeggError } from '@eggjs/tegg-metadata';
33
import {
44
AccessLevel,
5-
EggProtoImplClass, InitTypeQualifierAttribute, LoadUnitNameQualifierAttribute,
5+
EggProtoImplClass,
6+
EggQualifierAttribute,
7+
EggType,
8+
InitTypeQualifierAttribute,
9+
LoadUnitNameQualifierAttribute,
610
ObjectInitType,
711
PrototypeUtil,
812
QualifierUtil,
@@ -12,9 +16,9 @@ import { COMPATIBLE_PROTO_IMPLE_TYPE } from './EggCompatibleProtoImpl';
1216
import { BackgroundTaskHelper } from '@eggjs/tegg-background-task';
1317
import { EggObjectFactory } from '@eggjs/tegg-dynamic-inject-runtime';
1418

15-
const APP_CLAZZ_BLACK_LIST = [ 'eggObjectFactory' ];
16-
const DEFAULT_APP_CLAZZ = [];
17-
const DEFAULT_CONTEXT_CLAZZ = [
19+
export const APP_CLAZZ_BLACK_LIST = [ 'eggObjectFactory' ];
20+
export const DEFAULT_APP_CLAZZ: string[] = [];
21+
export const DEFAULT_CONTEXT_CLAZZ = [
1822
'user',
1923
];
2024

@@ -25,46 +29,41 @@ export class EggAppLoader implements Loader {
2529
this.app = app;
2630
}
2731

28-
private buildAppClazz(name: string): EggProtoImplClass {
32+
private buildClazz(name: string, eggType: EggType): EggProtoImplClass {
2933
const app = this.app;
30-
const func: EggProtoImplClass = function() {
31-
return app[name];
32-
} as any;
33-
PrototypeUtil.setIsEggPrototype(func);
34-
PrototypeUtil.setFilePath(func, 'mock_file_path');
35-
PrototypeUtil.setProperty(func, {
36-
name,
37-
initType: ObjectInitType.SINGLETON,
38-
accessLevel: AccessLevel.PUBLIC,
39-
protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,
40-
});
41-
QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');
42-
QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);
43-
return func;
44-
}
45-
46-
private buildCtxClazz(name: string): EggProtoImplClass {
47-
const temp = {
48-
[name]: function(ctx) {
34+
let func: EggProtoImplClass;
35+
if (eggType === EggType.APP) {
36+
func = function() {
37+
return app[name];
38+
} as any;
39+
} else {
40+
func = function() {
41+
const ctx = app.currentContext;
4942
if (!ctx) {
5043
// ctx has been destroyed, throw humanize error info
5144
throw TeggError.create(`Can not read property \`${name}\` because egg ctx has been destroyed`, 'read_after_ctx_destroyed');
5245
}
5346

5447
return ctx[name];
55-
} as any,
56-
};
57-
const func = temp[name];
48+
} as any;
49+
}
50+
Object.defineProperty(func, 'name', {
51+
value: name,
52+
writable: false,
53+
enumerable: false,
54+
configurable: true,
55+
});
5856
PrototypeUtil.setIsEggPrototype(func);
5957
PrototypeUtil.setFilePath(func, 'mock_file_path');
6058
PrototypeUtil.setProperty(func, {
6159
name,
62-
initType: ObjectInitType.CONTEXT,
60+
initType: ObjectInitType.SINGLETON,
6361
accessLevel: AccessLevel.PUBLIC,
6462
protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,
6563
});
6664
QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');
67-
QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.CONTEXT);
65+
QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);
66+
QualifierUtil.addProtoQualifier(func, EggQualifierAttribute, eggType);
6867
return func;
6968
}
7069

@@ -73,6 +72,12 @@ export class EggAppLoader implements Loader {
7372
const func: EggProtoImplClass = function() {
7473
return app.getLogger(name);
7574
} as any;
75+
Object.defineProperty(func, 'name', {
76+
value: name,
77+
writable: false,
78+
enumerable: false,
79+
configurable: true,
80+
});
7681
PrototypeUtil.setIsEggPrototype(func);
7782
PrototypeUtil.setFilePath(func, 'mock_file_path');
7883
PrototypeUtil.setProperty(func, {
@@ -83,26 +88,7 @@ export class EggAppLoader implements Loader {
8388
});
8489
QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');
8590
QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.SINGLETON);
86-
return func;
87-
}
88-
89-
private buildCtxLoggerClazz(name: string): EggProtoImplClass {
90-
const temp = {
91-
[name]: function(ctx) {
92-
return ctx.getLogger(name);
93-
} as any,
94-
};
95-
const func = temp[name];
96-
PrototypeUtil.setIsEggPrototype(func);
97-
PrototypeUtil.setFilePath(func, 'mock_file_path');
98-
PrototypeUtil.setProperty(func, {
99-
name,
100-
initType: ObjectInitType.CONTEXT,
101-
accessLevel: AccessLevel.PUBLIC,
102-
protoImplType: COMPATIBLE_PROTO_IMPLE_TYPE,
103-
});
104-
QualifierUtil.addProtoQualifier(func, LoadUnitNameQualifierAttribute, 'app');
105-
QualifierUtil.addProtoQualifier(func, InitTypeQualifierAttribute, ObjectInitType.CONTEXT);
91+
QualifierUtil.addProtoQualifier(func, EggQualifierAttribute, EggType.APP);
10692
return func;
10793
}
10894

@@ -128,16 +114,14 @@ export class EggAppLoader implements Loader {
128114
...DEFAULT_CONTEXT_CLAZZ,
129115
]));
130116
const loggerNames = this.getLoggerNames(allContextClazzNames);
131-
const allSingletonClazzs = allSingletonClazzNames.map(name => this.buildAppClazz(name));
132-
const allContextClazzs = allContextClazzNames.map(name => this.buildCtxClazz(name));
117+
const allSingletonClazzs = allSingletonClazzNames.map(name => this.buildClazz(name, EggType.APP));
118+
const allContextClazzs = allContextClazzNames.map(name => this.buildClazz(name, EggType.CONTEXT));
133119
const appLoggerClazzs = loggerNames.map(name => this.buildAppLoggerClazz(name));
134-
const ctxLoggerClazzs = loggerNames.map(name => this.buildCtxLoggerClazz(name));
135120

136121
return [
137122
...allSingletonClazzs,
138123
...allContextClazzs,
139124
...appLoggerClazzs,
140-
...ctxLoggerClazzs,
141125

142126
// inner helper class list
143127
// TODO: should auto the inner class

plugin/tegg/lib/EggCompatibleObject.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { EggCompatibleProtoImpl } from './EggCompatibleProtoImpl';
22
import {
3-
ContextHandler,
43
EggObject,
54
EggObjectFactory,
65
} from '@eggjs/tegg-runtime';
@@ -19,16 +18,15 @@ export class EggCompatibleObject implements EggObject {
1918
constructor(name: EggObjectName, proto: EggCompatibleProtoImpl) {
2019
this.proto = proto;
2120
this.name = name;
22-
const ctx = ContextHandler.getContext();
23-
this.id = IdenticalUtil.createObjectId(this.proto.id, ctx?.id);
21+
this.id = IdenticalUtil.createObjectId(this.proto.id);
2422
}
2523

2624
// If the egg object is a getter,
2725
// access may have side effect.
2826
// So access egg object lazy.
2927
get obj() {
3028
if (!this[OBJ]) {
31-
this[OBJ] = this.proto.constructorEggCompatibleObject();
29+
this[OBJ] = this.proto.constructEggObject();
3230
}
3331
return this[OBJ];
3432
}

plugin/tegg/lib/EggCompatibleProtoImpl.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ import {
1010
Id,
1111
IdenticalUtil,
1212
} from '@eggjs/tegg';
13-
import { ContextHandler } from '@eggjs/tegg-runtime';
1413
import {
1514
EggPrototype,
1615
InjectObjectProto,
1716
EggPrototypeLifecycleContext,
1817
} from '@eggjs/tegg-metadata';
19-
import { EGG_CONTEXT } from '@eggjs/egg-module-common';
2018

2119

2220
export const COMPATIBLE_PROTO_IMPLE_TYPE = 'EGG_COMPATIBLE';
@@ -65,13 +63,7 @@ export class EggCompatibleProtoImpl implements EggPrototype {
6563
}
6664

6765
constructEggObject(): object {
68-
return {};
69-
}
70-
71-
constructorEggCompatibleObject() {
72-
const teggContext = ContextHandler.getContext();
73-
const ctx = teggContext?.get(EGG_CONTEXT);
74-
return Reflect.apply(this.clazz, null, [ ctx ]);
66+
return Reflect.apply(this.clazz, null, []);
7567
}
7668

7769
getMetaData<T>(metadataKey: MetaDataKey): T | undefined {

0 commit comments

Comments
 (0)