Javascript 使用模块功能的提供程序创建作用域模块

Javascript 使用模块功能的提供程序创建作用域模块,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

当我多次使用import
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中的门。不需要使用decorator
Inject
,但我对使用这种方法时的性能感兴趣。我不希望它对您的性能有明显的影响,但当然,您只有在测试它时才会知道。我不必担心,只要看看nest v6中引入的请求范围的提供程序,请参阅