如何使用 Nestjs 中的请求范围提供程序动态更改数据库连接?

ack*_*ser 6 mongoose mongodb node.js typescript nestjs

使用Nestjs 6.x、Mongoose、Mongo等开发一个项目...关于后端,在我的用例中,我必须根据来自某些请求的某些条件/参数更改我的数据库之一的连接。

基本上,我这个

mongoose.createConnection('mongodb://127.0.0.1/whatever-a', { useNewUrlParser: true })
Run Code Online (Sandbox Code Playgroud)

我想改为,例如

mongoose.createConnection('mongodb://127.0.0.1/whatever-b', { useNewUrlParser: true })
Run Code Online (Sandbox Code Playgroud)

因此,我在 Nestjs 中有第一个提供者

export const databaseProviders = [
  {
    provide: 'DbConnectionToken',
    useFactory: async (): Promise<typeof mongoose> =>
    await mongoose.createConnection('mongodb://127.0.0.1/whatever', { useNewUrlParser: true })
  }
Run Code Online (Sandbox Code Playgroud)

我研究了一段时间,发现在Nestjs 6.x版本中,提供程序请求允许我动态修改每个请求某些提供程序的注入。

不管怎样,我不知道如何实现我的改变,也不知道它是否会起作用,以防我实现这一目标

有人可以帮助或指导我吗?提前谢谢了。

yah*_*rga 7

您可以使用 Nest 的内置 Mongoose 包执行以下操作:

/*************************
* mognoose.service.ts
*************************/
import { Inject, Injectable, Scope } from '@nestjs/common';
import { MongooseOptionsFactory, MongooseModuleOptions } from '@nestjs/mongoose';
import { REQUEST } from '@nestjs/core';
import { Request } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class MongooseConfigService implements MongooseOptionsFactory {
    constructor(
        @Inject(REQUEST) private readonly request: Request,) {
    }

    createMongooseOptions(): MongooseModuleOptions {
        return {
            uri: request.params.uri, // Change this to whatever you want; you have full access to the request object.
        };
    }
}

/*************************
* mongoose.module.ts
*************************/
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { MongooseConfigService } from 'mognoose.service';

@Module({
    imports: [
        MongooseModule.forRootAsync({
            useClass: MongooseConfigService,
        }),
    ]
})
export class DbModule {}

Run Code Online (Sandbox Code Playgroud)

然后,您可以将任何您想要的内容附加到请求中,并根据请求更改数据库;因此使用Scope.REQUEST. 您可以在其文档中阅读有关Injection Scopes的更多信息。


编辑:如果您遇到 PassportJS(或任何其他包)的问题或请求为空,这似乎是与 PassportJS(或其他包)不支持请求范围相关的错误;您可以在 GitHub 上阅读有关 PassportJS 的更多信息。

  • 我使用了相同的方法,而不是请求,我将中间件中的用户名添加到 mongoose.service 中,但 createMongooseOptions() 函数仅在每个应用程序启动时执行一次,到那时我没有得到用户名...有什么方法可以重新- 按需执行此功能。 (2认同)
  • 这个问题如何解决?如何使用之前创建的相同连接?@阿里尤素夫 (2认同)

小智 3

我为Nest-mongodb做了一个简单的实现,

主要更改在 mongo-core.module.ts 中,我将连接存储在映射中并在可用时使用它们,而不是每次都创建新连接。

import {
    Module,
    Inject,
    Global,
    DynamicModule,
    Provider,
    OnModuleDestroy,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { MongoClient, MongoClientOptions } from 'mongodb';
import {
    DEFAULT_MONGO_CLIENT_OPTIONS,
    MONGO_MODULE_OPTIONS,
    DEFAULT_MONGO_CONTAINER_NAME,
    MONGO_CONTAINER_NAME,
} from './mongo.constants';
import {
    MongoModuleAsyncOptions,
    MongoOptionsFactory,
    MongoModuleOptions,
} from './interfaces';
import { getClientToken, getContainerToken, getDbToken } from './mongo.util';
import * as hash from 'object-hash';

@Global()
@Module({})
export class MongoCoreModule implements OnModuleDestroy {
    constructor(
        @Inject(MONGO_CONTAINER_NAME) private readonly containerName: string,
        private readonly moduleRef: ModuleRef,
    ) {}

    static forRoot(
        uri: string,
        dbName: string,
        clientOptions: MongoClientOptions = DEFAULT_MONGO_CLIENT_OPTIONS,
        containerName: string = DEFAULT_MONGO_CONTAINER_NAME,
    ): DynamicModule {

        const containerNameProvider = {
            provide: MONGO_CONTAINER_NAME,
            useValue: containerName,
        };

        const connectionContainerProvider = {
            provide: getContainerToken(containerName),
            useFactory: () => new Map<any, MongoClient>(),
        };

        const clientProvider = {
            provide: getClientToken(containerName),
            useFactory: async (connections: Map<any, MongoClient>) => {
                const key = hash.sha1({
                    uri: uri,
                    clientOptions: clientOptions,
                });
                if (connections.has(key)) {
                    return connections.get(key);
                }
                const client = new MongoClient(uri, clientOptions);
                connections.set(key, client);
                return await client.connect();
            },
            inject: [getContainerToken(containerName)],
        };

        const dbProvider = {
            provide: getDbToken(containerName),
            useFactory: (client: MongoClient) => client.db(dbName),
            inject: [getClientToken(containerName)],
        };

        return {
            module: MongoCoreModule,
            providers: [
                containerNameProvider,
                connectionContainerProvider,
                clientProvider,
                dbProvider,
            ],
            exports: [clientProvider, dbProvider],
        };
    }

    static forRootAsync(options: MongoModuleAsyncOptions): DynamicModule {
        const mongoContainerName =
            options.containerName || DEFAULT_MONGO_CONTAINER_NAME;

        const containerNameProvider = {
            provide: MONGO_CONTAINER_NAME,
            useValue: mongoContainerName,
        };

        const connectionContainerProvider = {
            provide: getContainerToken(mongoContainerName),
            useFactory: () => new Map<any, MongoClient>(),
        };

        const clientProvider = {
            provide: getClientToken(mongoContainerName),
            useFactory: async (
                connections: Map<any, MongoClient>,
                mongoModuleOptions: MongoModuleOptions,
            ) => {
                const { uri, clientOptions } = mongoModuleOptions;
                const key = hash.sha1({
                    uri: uri,
                    clientOptions: clientOptions,
                });
                if (connections.has(key)) {
                    return connections.get(key);
                }
                const client = new MongoClient(
                    uri,
                    clientOptions || DEFAULT_MONGO_CLIENT_OPTIONS,
                );
                connections.set(key, client);
                return await client.connect();
            },
            inject: [getContainerToken(mongoContainerName), MONGO_MODULE_OPTIONS],
        };

        const dbProvider = {
            provide: getDbToken(mongoContainerName),
            useFactory: (
                mongoModuleOptions: MongoModuleOptions,
                client: MongoClient,
            ) => client.db(mongoModuleOptions.dbName),
            inject: [MONGO_MODULE_OPTIONS, getClientToken(mongoContainerName)],
        };

        const asyncProviders = this.createAsyncProviders(options);

        return {
            module: MongoCoreModule,
            imports: options.imports,
            providers: [
                ...asyncProviders,
                clientProvider,
                dbProvider,
                containerNameProvider,
                connectionContainerProvider,
            ],
            exports: [clientProvider, dbProvider],
        };
    }

    async onModuleDestroy() {
        const clientsMap: Map<any, MongoClient> = this.moduleRef.get<
            Map<any, MongoClient>
        >(getContainerToken(this.containerName));

        if (clientsMap) {
            await Promise.all(
                [...clientsMap.values()].map(connection => connection.close()),
            );
        }
    }

    private static createAsyncProviders(
        options: MongoModuleAsyncOptions,
    ): Provider[] {
        if (options.useExisting || options.useFactory) {
            return [this.createAsyncOptionsProvider(options)];
        } else if (options.useClass) {
            return [
                this.createAsyncOptionsProvider(options),
                {
                    provide: options.useClass,
                    useClass: options.useClass,
                },
            ];
        } else {
            return [];
        }
    }

    private static createAsyncOptionsProvider(
        options: MongoModuleAsyncOptions,
    ): Provider {
        if (options.useFactory) {
            return {
                provide: MONGO_MODULE_OPTIONS,
                useFactory: options.useFactory,
                inject: options.inject || [],
            };
        } else if (options.useExisting) {
            return {
                provide: MONGO_MODULE_OPTIONS,
                useFactory: async (optionsFactory: MongoOptionsFactory) =>
                    await optionsFactory.createMongoOptions(),
                inject: [options.useExisting],
            };
        } else if (options.useClass) {
            return {
                provide: MONGO_MODULE_OPTIONS,
                useFactory: async (optionsFactory: MongoOptionsFactory) =>
                    await optionsFactory.createMongoOptions(),
                inject: [options.useClass],
            };
        } else {
            throw new Error('Invalid MongoModule options');
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

查看完整实施情况