如何使用 Nestjs 中的请求范围提供程序动态更改数据库连接?
Posted
技术标签:
【中文标题】如何使用 Nestjs 中的请求范围提供程序动态更改数据库连接?【英文标题】:How to change a Database connection dynamically with Request Scope Providers in Nestjs? 【发布时间】:2019-08-29 11:34:16 【问题描述】:正在使用 Nestjs 6.x、Mongoose、Mongo 等进行项目... 关于后端,在我的用例中,我必须根据来自一些请求的一些条件/参数来更改我的一个数据库的连接强>。
基本上,我有这个
mongoose.createConnection('mongodb://127.0.0.1/whatever-a', useNewUrlParser: true )
我想改成,例如
mongoose.createConnection('mongodb://127.0.0.1/whatever-b', useNewUrlParser: true )
因此,我在 Nestjs 中有第一个提供者
export const databaseProviders = [
provide: 'DbConnectionToken',
useFactory: async (): Promise<typeof mongoose> =>
await mongoose.createConnection('mongodb://127.0.0.1/whatever', useNewUrlParser: true )
我研究了一段时间,发现在 Nestjs 6.x 版本中有提供者 requests 允许我动态修改 Per-request 注入一些提供者。
无论如何,我不知道如何实现我的改变,如果它会起作用,以防万一我实现它
谁能帮助或指导我? 非常感谢。
【问题讨论】:
为什么不创建两个连接并根据“来自某些请求的某些条件/参数”使用正确的连接? 我已经在与两个提供商的一项服务中这样做了。但是,我正在使用一种系统版本控制,因此对于其他情况,几乎每天都会创建数据库(名称为“whatever....”)......因此我需要能够连接到其中一个根据用户想要的版本,保存任何内容...谢谢 我从来没有使用过 Nest.js,所以我不知道你应该如何处理你的提供者,但对我来说,你必须通过一个方法来创建一个新的提供者似乎是合乎逻辑的一个参数应该是一个数据库名称(或者每个连接的不同名称),返回这个对象 provide: string, useFactory: Function
。通过提供可变参数调用此函数应该为您提供正确的提供者。
是的,没错,但您所说的是在编译时,我需要它在运行时动态更改它,这就是重点:)
【参考方案1】:
您可以使用 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
然后,您可以将任何您想要的内容附加到请求中,并根据请求更改数据库;因此使用Scope.REQUEST
。您可以在他们的文档中了解更多关于 Injection Scopes 的信息。
编辑:如果您遇到 PassportJS(或任何其他包)的问题或请求为空,这似乎是与 PassportJS(或其他包)不支持请求范围有关的错误;你可以阅读更多关于the issue on GitHub regarding PassportJS的信息。
【讨论】:
我使用了相同的方法,而不是请求,我将中间件中的用户名添加到 mongoose.service 中,但 createMongooseOptions() 函数仅在每次应用程序启动时执行一次,到那时我还没有得到用户名。 ..有没有办法按需重新执行这个功能。 默认情况下,注入范围设置为 SINGLETON,这意味着只有在项目运行时才会注入提供程序。将范围更改为 REQUEST 可确保每个请求都注入它,这应该允许代码正常运行。 成功了!但我没有从 this.request.params 获得参数 你为什么使用this
?在 MongooseConfigService
createMongooseOptions
中没有使用这个。我使用request.params.uri
没有这个。
如何解决这个问题?如何使用之前创建的相同连接? @AliYusuf【参考方案2】:
我为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');
查看完整的implementation
【讨论】:
以上是关于如何使用 Nestjs 中的请求范围提供程序动态更改数据库连接?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 passport-ldapauth 库在 Nestjs 应用程序中使用动态 ldap 配置选项?