Commit 94092e42 authored by nanahira's avatar nanahira

add WireContextService

parent 8d0e3366
...@@ -119,7 +119,7 @@ export class AppService implements OnModuleInit { ...@@ -119,7 +119,7 @@ export class AppService implements OnModuleInit {
### 注入上下文 ### 注入上下文
```ts ```ts
import { Injectable } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectContext } from 'koishi-nestjs'; import { InjectContext } from 'koishi-nestjs';
import { Context } from 'koishi'; import { Context } from 'koishi';
...@@ -136,7 +136,7 @@ export class AppService implements OnModuleInit { ...@@ -136,7 +136,7 @@ export class AppService implements OnModuleInit {
### 注入某一特定上下文 ### 注入某一特定上下文
```ts ```ts
import { Injectable } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectContextGuild } from 'koishi-nestjs'; import { InjectContextGuild } from 'koishi-nestjs';
import { Context } from 'koishi'; import { Context } from 'koishi';
...@@ -201,6 +201,45 @@ export class AppModule {} ...@@ -201,6 +201,45 @@ export class AppModule {}
* `values` 作用域值。例如 `getContextProvideToken('platform', ['onebot'])` 等价于 `ctx.platform('onebot')` . * `values` 作用域值。例如 `getContextProvideToken('platform', ['onebot'])` 等价于 `ctx.platform('onebot')` .
### 注入上下文 Service
您可以使用装饰器与 Koishi 的 Service 系统进行交互。
```ts
import { Injectable, OnApplicationBootstrap } from "@nestjs/common";
import { WireContextService, UseEvent } from 'koishi-nestjs';
import { Cache } from 'koishi';
@Injectable()
export class AppService implements OnApplicationBootstrap {
constructor(@InjectContextGuild('1111111111') private ctx: Context) {
}
// 注入 Service
@WireContextService('cache')
private cache2: Cache;
// 成员变量名与 Service 名称一致时 name 可省略。
@WireContextService()
private cache: Cache;
async onApplicationBootstrap() {
// 可以在 onApplicationBootstrap 访问上下文 Service
const user = this.cache.get('user', '111111111');
}
@UseEvent('service/cache')
async onCacheAvailable() {
// 建议监听 Service 事件
const user = this.cache.get('user', '111111112');
}
}
```
#### 定义
* `@WireContextService(name?: string)` 在提供者类某一属性注入特定上下文 Service 。 `name` 默认为类方法名。
## 使用装饰器注册 Koishi 指令 ## 使用装饰器注册 Koishi 指令
您也可以在完全不注入任何 Koishi 上下文的情况下注册 Koishi 指令,只需要在提供者类中使用装饰器即可。下面是一个例子。 您也可以在完全不注入任何 Koishi 上下文的情况下注册 Koishi 指令,只需要在提供者类中使用装饰器即可。下面是一个例子。
......
{ {
"name": "koishi-nestjs", "name": "koishi-nestjs",
"version": "1.1.6", "version": "1.2.0",
"description": "Koishi.js as Nest.js Module", "description": "Koishi.js as Nest.js Module",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
......
...@@ -4,6 +4,8 @@ import { ...@@ -4,6 +4,8 @@ import {
KoishiCommandDefinition, KoishiCommandDefinition,
KoishiDoRegister, KoishiDoRegister,
KoishiOnContextScope, KoishiOnContextScope,
KoishiServiceWireKeys,
KoishiServiceWireProperty,
} from './utility/koishi.constants'; } from './utility/koishi.constants';
import { import {
CommandDefinitionFun, CommandDefinitionFun,
...@@ -150,3 +152,21 @@ export const CommandOption = ( ...@@ -150,3 +152,21 @@ export const CommandOption = (
desc: string, desc: string,
config: Argv.OptionConfig = {}, config: Argv.OptionConfig = {},
) => CommandDef((cmd) => cmd.option(name, desc, config)); ) => CommandDef((cmd) => cmd.option(name, desc, config));
// Service
export function WireContextService(name?: string): PropertyDecorator {
return (obj, key) => {
const objClass = obj.constructor;
const properties: string[] =
Reflect.getMetadata(KoishiServiceWireKeys, objClass) || [];
properties.push(key.toString());
Reflect.defineMetadata(KoishiServiceWireKeys, properties, objClass);
Reflect.defineMetadata(
KoishiServiceWireProperty,
name || key,
objClass,
key,
);
};
}
...@@ -50,6 +50,7 @@ export class KoishiService ...@@ -50,6 +50,7 @@ export class KoishiService
async onModuleInit() { async onModuleInit() {
await this.setHttpServer(); await this.setHttpServer();
await this.metascan.preRegisterContext(this.any());
if (this.koishiModuleOptions.usePlugins) { if (this.koishiModuleOptions.usePlugins) {
for (const pluginDesc of this.koishiModuleOptions.usePlugins) { for (const pluginDesc of this.koishiModuleOptions.usePlugins) {
const ctx = applySelector(this, pluginDesc); const ctx = applySelector(this, pluginDesc);
......
...@@ -13,11 +13,14 @@ import { ...@@ -13,11 +13,14 @@ import {
KoishiCommandDefinition, KoishiCommandDefinition,
KoishiDoRegister, KoishiDoRegister,
KoishiOnContextScope, KoishiOnContextScope,
KoishiServiceWireKeys,
KoishiServiceWireProperty,
} from '../utility/koishi.constants'; } from '../utility/koishi.constants';
import { import {
CommandDefinitionFun, CommandDefinitionFun,
ContextFunction, ContextFunction,
DoRegisterConfig, DoRegisterConfig,
EventName,
EventNameAndPrepend, EventNameAndPrepend,
KoishiModulePlugin, KoishiModulePlugin,
OnContextFunction, OnContextFunction,
...@@ -25,6 +28,7 @@ import { ...@@ -25,6 +28,7 @@ import {
import { applySelector } from '../utility/koishi.utility'; import { applySelector } from '../utility/koishi.utility';
import _ from 'lodash'; import _ from 'lodash';
import { KoishiContextService } from './koishi-context.service'; import { KoishiContextService } from './koishi-context.service';
import { Module } from '@nestjs/core/injector/module';
@Injectable() @Injectable()
export class KoishiMetascanService { export class KoishiMetascanService {
...@@ -45,7 +49,7 @@ export class KoishiMetascanService { ...@@ -45,7 +49,7 @@ export class KoishiMetascanService {
} }
} }
private async handleInstance( private async handleInstanceRegistration(
ctx: Context, ctx: Context,
instance: Record<string, any>, instance: Record<string, any>,
methodKey: string, methodKey: string,
...@@ -88,9 +92,17 @@ export class KoishiMetascanService { ...@@ -88,9 +92,17 @@ export class KoishiMetascanService {
const { const {
data: eventData, data: eventData,
} = regData as DoRegisterConfig<EventNameAndPrepend>; } = regData as DoRegisterConfig<EventNameAndPrepend>;
const eventName = eventData.name;
baseContext.on(eventData.name, (...args: any[]) => baseContext.on(eventData.name, (...args: any[]) =>
methodFun.call(instance, ...args), methodFun.call(instance, ...args),
); );
// special events
if (typeof eventName === 'string' && eventName.startsWith('service/')) {
const serviceName = eventName.slice(8);
if (baseContext[serviceName] != null) {
methodFun.call(instance);
}
}
break; break;
case 'plugin': case 'plugin':
const pluginDesc: KoishiModulePlugin<any> = await methodFun.call( const pluginDesc: KoishiModulePlugin<any> = await methodFun.call(
...@@ -125,26 +137,79 @@ export class KoishiMetascanService { ...@@ -125,26 +137,79 @@ export class KoishiMetascanService {
} }
} }
private registerOnService(
ctx: Context,
instance: any,
property: string,
name: string,
) {
const preObject = ctx[name];
if (preObject) {
instance[property] = preObject;
}
ctx.on(
<EventName>`service/${name}`,
() => {
instance[property] = ctx[name];
},
true,
);
}
private scanInstanceForService(ctx: Context, instance: any) {
const instanceClass = instance.constructor;
const properties: string[] = Reflect.getMetadata(
KoishiServiceWireKeys,
instanceClass,
);
if (!properties) {
return;
}
for (const property of properties) {
const serviceName = Reflect.getMetadata(
KoishiServiceWireProperty,
instanceClass,
property,
);
this.registerOnService(ctx, instance, property, serviceName);
}
}
private getAllActiveProvidersFromModule(module: Module) {
return [
...Array.from(module.routes.values()),
...Array.from(module.providers.values()),
]
.filter((wrapper) => wrapper.isDependencyTreeStatic())
.filter((wrapper) => wrapper.instance);
}
async preRegisterContext(ctx: Context) {
for (const module of this.moduleContainer.values()) {
const moduleCtx = this.ctxService.getModuleCtx(ctx, module);
const allProviders = this.getAllActiveProvidersFromModule(module);
for (const provider of allProviders) {
return this.scanInstanceForService(moduleCtx, provider.instance);
}
}
}
async registerContext(ctx: Context) { async registerContext(ctx: Context) {
const modules = Array.from(this.moduleContainer.values()); const modules = Array.from(this.moduleContainer.values());
await Promise.all( await Promise.all(
_.flatten( _.flatten(
modules.map((module) => { modules.map((module) => {
const moduleCtx = this.ctxService.getModuleCtx(ctx, module); const moduleCtx = this.ctxService.getModuleCtx(ctx, module);
return [ const allProviders = this.getAllActiveProvidersFromModule(module);
...Array.from(module.routes.values()), return allProviders.map((wrapper: InstanceWrapper) => {
...Array.from(module.providers.values()),
]
.filter((wrapper) => wrapper.isDependencyTreeStatic())
.filter((wrapper) => wrapper.instance)
.map((wrapper: InstanceWrapper) => {
const { instance } = wrapper; const { instance } = wrapper;
this.scanInstanceForService(moduleCtx, instance);
const prototype = Object.getPrototypeOf(instance); const prototype = Object.getPrototypeOf(instance);
return this.metadataScanner.scanFromPrototype( return this.metadataScanner.scanFromPrototype(
instance, instance,
prototype, prototype,
(methodKey: string) => (methodKey: string) =>
this.handleInstance(moduleCtx, instance, methodKey), this.handleInstanceRegistration(moduleCtx, instance, methodKey),
); );
}); });
}), }),
......
...@@ -6,3 +6,6 @@ export const KOISHI_CONTEXT = 'KOISHI_CONTEXT'; ...@@ -6,3 +6,6 @@ export const KOISHI_CONTEXT = 'KOISHI_CONTEXT';
export const KoishiOnContextScope = 'KoishiOnContextScope'; export const KoishiOnContextScope = 'KoishiOnContextScope';
export const KoishiDoRegister = 'KoishiDoRegister'; export const KoishiDoRegister = 'KoishiDoRegister';
export const KoishiCommandDefinition = 'KoishiCommandDefinition'; export const KoishiCommandDefinition = 'KoishiCommandDefinition';
export const KoishiServiceWireProperty = Symbol('KoishiServiceWireProperty');
export const KoishiServiceWireKeys = Symbol('KoishiServiceWireKeys');
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment