Javascript 使用模块功能的提供程序创建作用域模块
当我多次使用importJavascript 使用模块功能的提供程序创建作用域模块,javascript,node.js,typescript,dependency-injection,nestjs,Javascript,Node.js,Typescript,Dependency Injection,Nestjs,当我多次使用importPolicyModule.forFeature时,PolicyModule的下一次导入将覆盖PolicyStorage中的闸门 当我试图通过调用PolicyProvider在CandidateModule的CandidateEducationService中使用PolicyProvider时 await this.policy.denyAccessUnlessGranted('canDelete', education); 我发现实体“CandidateEducatio
PolicyModule.forFeature
时,PolicyModule
的下一次导入将覆盖PolicyStorage
中的闸门当我试图通过调用
PolicyProvider
在CandidateModule
的CandidateEducationService
中使用PolicyProvider
时
await this.policy.denyAccessUnlessGranted('canDelete', education);
我发现实体“CandidateEducationEntity”未找到的异常门
我在CandidateEducationService
中输出PolicyStorage
,并使用JobPolicy
PolicyStorage {
gates:
[ { policy: [Function: JobPolicy], entity: [Function: JobEntity] } ]
}
但我期待着
PolicyStorage {
gates:
[ { policy: [Function: CandidateEducationPolicy], entity: [Function: CandidateEducationEntity] } ]
}
我创建了一个动态模块PolicyModule
@Module({})
export class PolicyModule {
public static forFeature(gates: PolicyGate[]): DynamicModule {
const providers: Provider[] = [
...gates.map(gate => gate.policy),
{
provide: PolicyStorage,
useValue: new PolicyStorage(gates),
},
PolicyProvider,
];
return {
module: PolicyModule,
imports: [
CommonModule,
],
providers,
exports: providers,
};
}
}
@Injectable()
export class PolicyStorage {
constructor(private gates: PolicyGate[]) {
console.log(this.gates);
}
public find(name: string): PolicyGate | null {
return this.gates.find(policy => policy.entity.name === name);
}
}
@Module({
imports: [
TypeOrmModule.forFeature(
[
JobEntity,
],
),
PolicyModule.forFeature([
{
policy: JobPolicy,
entity: JobEntity,
},
]),
],
controllers: [
ManagerJobsController,
],
providers: [
ManagerJobsService,
],
})
export class JobsModule {
}
PolicyStorage
@Module({})
export class PolicyModule {
public static forFeature(gates: PolicyGate[]): DynamicModule {
const providers: Provider[] = [
...gates.map(gate => gate.policy),
{
provide: PolicyStorage,
useValue: new PolicyStorage(gates),
},
PolicyProvider,
];
return {
module: PolicyModule,
imports: [
CommonModule,
],
providers,
exports: providers,
};
}
}
@Injectable()
export class PolicyStorage {
constructor(private gates: PolicyGate[]) {
console.log(this.gates);
}
public find(name: string): PolicyGate | null {
return this.gates.find(policy => policy.entity.name === name);
}
}
@Module({
imports: [
TypeOrmModule.forFeature(
[
JobEntity,
],
),
PolicyModule.forFeature([
{
policy: JobPolicy,
entity: JobEntity,
},
]),
],
controllers: [
ManagerJobsController,
],
providers: [
ManagerJobsService,
],
})
export class JobsModule {
}
PolicyProvider
@Injectable()
export class PolicyProvider<E, P> {
constructor(
private readonly moduleRef: ModuleRef,
private readonly gateStorage: PolicyStorage,
private readonly appContext: AppContextService,
) {
}
public async denyAccessUnlessGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
if (await this.denies(methodNames, entity, customData)) {
throw new ForbiddenException();
}
}
public async allowAccessIfGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
const allowed = await this.allows(methodNames, entity, customData);
if (!allowed) {
throw new ForbiddenException();
}
}
private async allows(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === true);
}
private async denies(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === false);
}
private async getPolicyResults(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean[]> {
const methodNamesArray = Array.isArray(methodNames) ? methodNames : [methodNames];
const gate = this.findByClassName(entity.constructor.name);
const user = this.appContext.get('user');
const policy = await this.moduleRef.get<P>(gate.policy, {strict: false});
const results = [];
for (const methodName of methodNamesArray) {
results.push(!!await policy[methodName as string](entity, user, customData));
}
return results;
}
private findByClassName(name: string) {
const gate = this.gateStorage.find(name);
if (!gate) {
throw new RuntimeException(`Gate by entity '${name}' not found`);
}
return gate;
}
}
候选模块
@Module({
imports: [
TypeOrmModule.forFeature(
[
CandidateEducationEntity,
],
),
PolicyModule.forFeature([
{
policy: CandidateEducationPolicy,
entity: CandidateEducationEntity,
},
]),
],
controllers: [
CandidateEducationController,
],
providers: [
CandidateEducationService,
],
})
export class CandidateModule {
}
更新:
Nest v6引入了请求范围的提供程序,请参阅
所有模块及其提供程序都是单例的。如果在同一模块中两次在同一令牌下注册提供程序,它将被覆盖
如果查看TypeOrmModule,您可以在每个实体的唯一下看到它:
export function getRepositoryToken(entity: Function) {
if (
entity.prototype instanceof Repository ||
entity.prototype instanceof AbstractRepository
) {
return getCustomRepositoryToken(entity);
}
return `${entity.name}Repository`;
}
因此,在您的情况下,您可以使用函数getPolicyProviderToken
和getPolicyStorageToken
,并在这些令牌下注册和注入您的提供者,这些令牌对于每个导入模块都是唯一的。我认为所有服务都是单例it反模式。糟糕,非常糟糕。是的,你需要使用@Injectdecorator(或者创建你自己的)。nestjs的第6版将有临时/请求绑定的提供程序,因此您将有其他选项,而不仅仅是singleton。所有提供程序都是singleton,原因是nestjs在启动应用程序之前创建DI图。我认为通过单例反模式创建所有提供程序,但这对性能很有用。我创建了动态名称令牌门${entityName}gates
,并通过实体名const gates=this.moduleRef.get(entityGatesToken(name))获取PolicyProvider中的门代码>。不需要使用decoratorInject
,但我对使用这种方法时的性能感兴趣。我不希望它对您的性能有明显的影响,但当然,您只有在测试它时才会知道。我不必担心,只要看看nest v6中引入的请求范围的提供程序,请参阅