Skip to content

Commit 12fd5cf

Browse files
authored
feat: impl MultiInstanceProto (#145)
<!-- Thank you for your pull request. Please review below requirements. Bug fixes and new features should include tests and possibly benchmarks. Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md 感谢您贡献代码。请确认下列 checklist 的完成情况。 Bug 修复和新功能必须包含测试,必要时请附上性能测试。 Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md --> ##### Checklist <!-- Remove items that do not apply. For completed items, change [ ] to [x]. --> - [x] `npm test` passes - [x] tests and/or benchmarks are included - [ ] documentation is changed or added - [x] commit message follows commit guidelines ##### Affected core subsystem(s) <!-- Provide affected core subsystem(s). --> ##### Description of change <!-- Provide a description of the change below this comment. --> <!-- - any feature? - close https://github.com/eggjs/egg/ISSUE_URL -->
1 parent fa454c1 commit 12fd5cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+932
-114
lines changed

README.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,172 @@ export class HelloService {
156156
}
157157
```
158158

159+
#### MultiInstanceProto
160+
161+
支持一个类有多个实例。比如说 logger 可能会初始化多个,用来输出到不通文件,db 可能会初始化多个,用来连接不同的数据库。
162+
使用这个注解可以方便的对接外部资源。
163+
164+
##### 定义
165+
```ts
166+
// 静态定义
167+
@MultiInstanceProto(params: {
168+
// 对象的生命周期
169+
// CONTEXT: 每个上下文都有一个实例
170+
// SINGLETON: 整个应用生命周期只有一个实例
171+
initType?: ObjectInitTypeLike;
172+
173+
// 对象是在 module 内可访问还是全局可访问
174+
// PRIVATE: 仅 module 内可访问
175+
// PUBLIC: 全局可访问
176+
// 默认值为 PRIVATE
177+
accessLevel?: AccessLevel;
178+
179+
// 高阶参数,指定类型的实现原型
180+
protoImplType?: string;
181+
182+
// 对象元信息
183+
objects: ObjectInfo[];
184+
})
185+
186+
// 动态定义
187+
@MultiInstanceProto(params: {
188+
// 对象的生命周期
189+
// CONTEXT: 每个上下文都有一个实例
190+
// SINGLETON: 整个应用生命周期只有一个实例
191+
initType?: ObjectInitTypeLike;
192+
193+
// 对象是在 module 内可访问还是全局可访问
194+
// PRIVATE: 仅 module 内可访问
195+
// PUBLIC: 全局可访问
196+
// 默认值为 PRIVATE
197+
accessLevel?: AccessLevel;
198+
199+
// 高阶参数,指定类型的实现原型
200+
protoImplType?: string;
201+
202+
// 动态调用,获取对象元信息
203+
// 仅会调用一次,调用后结果会被缓存
204+
getObjects(ctx: MultiInstancePrototypeGetObjectsContext): ObjectInfo[];
205+
})
206+
```
207+
208+
##### 示例
209+
210+
首先定义一个自定义 Qualifier 注解
211+
```ts
212+
import {
213+
QualifierUtil,
214+
EggProtoImplClass,
215+
} from '@eggjs/tegg';
216+
217+
export const LOG_PATH_ATTRIBUTE = Symbol.for('LOG_PATH_ATTRIBUTE');
218+
219+
export function LogPath(name: string) {
220+
return function(target: any, propertyKey: PropertyKey) {
221+
QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, LOG_PATH_ATTRIBUTE, name);
222+
};
223+
}
224+
225+
```
226+
227+
定一个具体实现
228+
229+
```typescript
230+
import {
231+
MultiInstanceProto,
232+
MultiInstancePrototypeGetObjectsContext,
233+
LifecycleInit,
234+
LifecycleDestroy,
235+
QualifierUtil,
236+
EggProtoImplClass,
237+
} from '@eggjs/tegg';
238+
import { EggObject, ModuleConfigUtil, EggObjectLifeCycleContext } from '@eggjs/tegg/helper';
239+
import fs from 'node:fs';
240+
import { Writable } from 'node:stream';
241+
import path from 'node:path';
242+
import { EOL } from 'node:os';
243+
244+
export const LOG_PATH_ATTRIBUTE = Symbol.for('LOG_PATH_ATTRIBUTE');
245+
246+
@MultiInstanceProto({
247+
// 从 module.yml 中动态获取配置来决定需要初始化几个对象
248+
getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {
249+
const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath);
250+
return (config as any).features.logger.map(name => {
251+
return {
252+
name: 'dynamicLogger',
253+
qualifiers: [{
254+
attribute: LOG_PATH_ATTRIBUTE,
255+
value: name,
256+
}],
257+
}
258+
});
259+
},
260+
})
261+
export class DynamicLogger {
262+
stream: Writable;
263+
loggerName: string;
264+
265+
@LifecycleInit()
266+
async init(ctx: EggObjectLifeCycleContext, obj: EggObject) {
267+
// 获取需要实例化对象的 Qualifieri
268+
const loggerName = obj.proto.getQualifier(LOG_PATH_ATTRIBUTE);
269+
this.loggerName = loggerName as string;
270+
this.stream = fs.createWriteStream(path.join(ctx.loadUnit.unitPath, `${loggerName}.log`));
271+
}
272+
273+
@LifecycleDestroy()
274+
async destroy() {
275+
return new Promise<void>((resolve, reject) => {
276+
this.stream.end(err => {
277+
if (err) {
278+
return reject(err);
279+
}
280+
return resolve();
281+
});
282+
});
283+
}
284+
285+
info(msg: string) {
286+
return new Promise<void>((resolve, reject) => {
287+
this.stream.write(msg + EOL,err => {
288+
if (err) {
289+
return reject(err);
290+
}
291+
return resolve();
292+
});
293+
});
294+
}
295+
296+
}
297+
```
298+
299+
使用 DynamicLogger.
300+
```ts
301+
@SingletonProto()
302+
export class Foo {
303+
@Inject({
304+
name: 'dynamicLogger',
305+
})
306+
// 通过自定义注解来指定 Qualifier
307+
@LogPath('foo')
308+
fooDynamicLogger: DynamicLogger;
309+
310+
@Inject({
311+
name: 'dynamicLogger',
312+
})
313+
@LogPath('bar')
314+
barDynamicLogger: DynamicLogger;
315+
316+
async hello(): Promise<void> {
317+
await this.fooDynamicLogger.info('hello, foo');
318+
await this.barDynamicLogger.info('hello, bar');
319+
}
320+
}
321+
```
322+
323+
324+
159325
#### 生命周期 hook
160326

161327
由于对象的生命周期交给了容器来托管,代码中无法感知什么时候对象初始化,什么时候依赖被注入了。所以提供了生命周期 hook 来实现这些通知的功能。

core/core-decorator/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# `@eggjs/standalon-decorator`
1+
# `@eggjs/core-decorator`
22

33
## Usage
44

5-
Please read [@eggjs/tegg-standalone](../../standalone/standalone)
5+
Please read [@eggjs/tegg-plugin](../../plugin/tegg)

core/core-decorator/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './src/decorator/ModuleQualifier';
55
export * from './src/decorator/ContextProto';
66
export * from './src/decorator/SingletonProto';
77
export * from './src/decorator/EggQualifier';
8+
export * from './src/decorator/MultiInstanceProto';
89

910
export * from './src/enum/AccessLevel';
1011
export * from './src/enum/ObjectInitType';
@@ -13,6 +14,7 @@ export * from './src/enum/EggType';
1314
export * from './src/model/EggPrototypeInfo';
1415
export * from './src/model/InjectObjectInfo';
1516
export * from './src/model/QualifierInfo';
17+
export * from './src/model/EggMultiInstancePrototypeInfo';
1618

1719
export * from './src/util/MetadataUtil';
1820
export * from './src/util/PrototypeUtil';
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { ObjectInitType, ObjectInitTypeLike } from '../enum/ObjectInitType';
2+
import { AccessLevel } from '../enum/AccessLevel';
3+
import { DEFAULT_PROTO_IMPL_TYPE } from './Prototype';
4+
import {
5+
EggMultiInstanceCallbackPrototypeInfo,
6+
EggMultiInstancePrototypeInfo,
7+
MultiInstancePrototypeGetObjectsContext,
8+
ObjectInfo,
9+
} from '../model/EggMultiInstancePrototypeInfo';
10+
import { EggProtoImplClass } from '../model/EggPrototypeInfo';
11+
import { PrototypeUtil } from '../util/PrototypeUtil';
12+
import { StackUtil } from '@eggjs/tegg-common-util';
13+
14+
const DEFAULT_PARAMS = {
15+
initType: ObjectInitType.SINGLETON,
16+
accessLevel: AccessLevel.PRIVATE,
17+
protoImplType: DEFAULT_PROTO_IMPL_TYPE,
18+
};
19+
20+
export interface BaseMultiInstancePrototypeCallbackParams {
21+
/**
22+
* obj init type
23+
*/
24+
initType?: ObjectInitTypeLike;
25+
/**
26+
* access level
27+
*/
28+
accessLevel?: AccessLevel;
29+
/**
30+
* EggPrototype implement type
31+
*/
32+
protoImplType?: string;
33+
}
34+
35+
export interface MultiInstancePrototypeCallbackParams extends BaseMultiInstancePrototypeCallbackParams {
36+
getObjects(ctx: MultiInstancePrototypeGetObjectsContext): ObjectInfo[];
37+
}
38+
39+
export interface MultiInstancePrototypeStaticParams extends BaseMultiInstancePrototypeCallbackParams {
40+
/**
41+
* object info list
42+
*/
43+
objects: ObjectInfo[];
44+
}
45+
46+
export type MultiInstancePrototypeParams = MultiInstancePrototypeCallbackParams | MultiInstancePrototypeStaticParams;
47+
48+
export function MultiInstanceProto(param: MultiInstancePrototypeParams) {
49+
return function(clazz: EggProtoImplClass) {
50+
PrototypeUtil.setIsEggMultiInstancePrototype(clazz);
51+
if ((param as MultiInstancePrototypeStaticParams).objects) {
52+
const property: EggMultiInstancePrototypeInfo = {
53+
...DEFAULT_PARAMS,
54+
...param as MultiInstancePrototypeStaticParams,
55+
};
56+
PrototypeUtil.setMultiInstanceStaticProperty(clazz, property);
57+
} else if ((param as MultiInstancePrototypeCallbackParams).getObjects) {
58+
const property: EggMultiInstanceCallbackPrototypeInfo = {
59+
...DEFAULT_PARAMS,
60+
...param as MultiInstancePrototypeCallbackParams,
61+
};
62+
PrototypeUtil.setMultiInstanceCallbackProperty(clazz, property);
63+
}
64+
65+
66+
// './tegg/core/common-util/src/StackUtil.ts',
67+
// './tegg/core/core-decorator/src/decorator/Prototype.ts',
68+
// './tegg/core/core-decorator/node_modules/[email protected]@reflect-metadata/Reflect.js',
69+
// './tegg/core/core-decorator/node_modules/[email protected]@reflect-metadata/Reflect.js',
70+
// './tegg/core/core-decorator/test/fixtures/decators/CacheService.ts',
71+
PrototypeUtil.setFilePath(clazz, StackUtil.getCalleeFromStack(false, 4));
72+
};
73+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ObjectInitTypeLike } from '../enum/ObjectInitType';
2+
import { AccessLevel } from '../enum/AccessLevel';
3+
import { EggPrototypeName } from './EggPrototypeInfo';
4+
import { QualifierInfo } from './QualifierInfo';
5+
6+
export interface ObjectInfo {
7+
name: EggPrototypeName;
8+
qualifiers: QualifierInfo[];
9+
}
10+
11+
export interface MultiInstancePrototypeGetObjectsContext {
12+
unitPath: string;
13+
}
14+
15+
export interface EggMultiInstancePrototypeInfo {
16+
/**
17+
* obj init type
18+
*/
19+
initType: ObjectInitTypeLike;
20+
/**
21+
* access level
22+
*/
23+
accessLevel: AccessLevel;
24+
/**
25+
* EggPrototype implement type
26+
*/
27+
protoImplType: string;
28+
29+
/**
30+
* object info list
31+
*/
32+
objects: ObjectInfo[];
33+
}
34+
35+
export interface EggMultiInstanceCallbackPrototypeInfo {
36+
/**
37+
* obj init type
38+
*/
39+
initType: ObjectInitTypeLike;
40+
/**
41+
* access level
42+
*/
43+
accessLevel: AccessLevel;
44+
/**
45+
* EggPrototype implement type
46+
*/
47+
protoImplType: string;
48+
49+
/**
50+
* get object callback
51+
* @param ctx
52+
*/
53+
getObjects(ctx: MultiInstancePrototypeGetObjectsContext): ObjectInfo[];
54+
}

core/core-decorator/src/model/EggPrototypeInfo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ObjectInitTypeLike } from '../enum/ObjectInitType';
22
import { AccessLevel } from '../enum/AccessLevel';
3+
import { QualifierInfo } from './QualifierInfo';
34

45
export type EggProtoImplClass<T = object> = new(...args: any[]) => T;
56
export type EggPrototypeName = PropertyKey;
@@ -21,4 +22,8 @@ export interface EggPrototypeInfo {
2122
* EggPrototype implement type
2223
*/
2324
protoImplType: string;
25+
/**
26+
* EggPrototype qualifiers
27+
*/
28+
qualifiers?: QualifierInfo[];
2429
}

0 commit comments

Comments
 (0)