我想连接到基于子域( Multi-Tenancy )的任何数据库,但我不知道该怎么做。
我的代码在应用程序启动时运行,但我不知道如何根据子域更改数据源。
PS:我根据每个请求创建了中间件,但是我不知道如何更改源。
我的数据库有以下代码:
import { connect, createConnection } from 'mongoose';
import { SERVER_CONFIG, DB_CONNECTION_TOKEN } from '../server.constants';
const opts = {
useCreateIndex: true,
useNewUrlParser: true,
keepAlive: true,
socketTimeoutMS: 30000,
poolSize: 100,
reconnectTries: Number.MAX_VALUE,
reconnectInterval: 500,
autoReconnect: true,
};
export const databaseProviders = [
{
provide: DB_CONNECTION_TOKEN,
useFactory: async () => {
try {
console.log(`Connecting to ${ SERVER_CONFIG.db }`);
return await createConnection(`${SERVER_CONFIG.db}`, opts);
} catch (ex) {
console.log(ex);
}
},
}
];
我想根据子域( Multi-Tenancy )在每个请求中更改我的数据源
最佳答案
这是我与 Mongoose 一起使用的解决方案
TenantsService
用于管理应用程序中的所有租户 @Injectable()
export class TenantsService {
constructor(
@InjectModel('Tenant') private readonly tenantModel: Model<ITenant>,
) {}
/**
* Save tenant data
*
* @param {CreateTenantDto} createTenant
* @returns {Promise<ITenant>}
* @memberof TenantsService
*/
async create(createTenant: CreateTenantDto): Promise<ITenant> {
try {
const dataToPersist = new this.tenantModel(createTenant);
// Persist the data
return await dataToPersist.save();
} catch (error) {
throw new HttpException(error, HttpStatus.BAD_REQUEST);
}
}
/**
* Find details of a tenant by name
*
* @param {string} name
* @returns {Promise<ITenant>}
* @memberof TenantsService
*/
async findByName(name: string): Promise<ITenant> {
return await this.tenantModel.findOne({ name });
}
}
TenantAwareMiddleware
中间件,用于从请求上下文中获取 tenant id
。您可以在此处创建自己的逻辑以从请求 header 或请求 url 子域中提取 tenant id
。请求头提取方法如下所示。 如果要提取子域,则可以通过调用
Request
从req.subdomains
对象中提取子域来完成,这将为您提供子域列表,然后您可以从中找到想要的子域。@Injectable()
export class TenantAwareMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
// Extract from the request object
const { subdomains, headers } = req;
// Get the tenant id from header
const tenantId = headers['X-TENANT-ID'] || headers['x-tenant-id'];
if (!tenantId) {
throw new HttpException('`X-TENANT-ID` not provided', HttpStatus.NOT_FOUND);
}
// Set the tenant id in the header
req['tenantId'] = tenantId.toString();
next();
}
}
TenantConnection
此类用于使用 tenant id
创建新连接,如果现有连接可用,它将返回相同的连接(以避免创建额外的连接)。 @Injectable()
export class TenantConnection {
private _tenantId: string;
constructor(
private tenantService: TenantsService,
private configService: ConfigService,
) {}
/**
* Set the context of the tenant
*
* @memberof TenantConnection
*/
set tenantId(tenantId: string) {
this._tenantId = tenantId;
}
/**
* Get the connection details
*
* @param {ITenant} tenant
* @returns
* @memberof TenantConnection
*/
async getConnection(): Connection {
// Get the tenant details from the database
const tenant = await this.tenantService.findByName(this._tenantId);
// Validation check if tenant exist
if (!tenant) {
throw new HttpException('Tenant not found', HttpStatus.NOT_FOUND);
}
// Get the underlying mongoose connections
const connections: Connection[] = mongoose.connections;
// Find existing connection
const foundConn = connections.find((con: Connection) => {
return con.name === `tenantDB_${tenant.name}`;
});
// Check if connection exist and is ready to execute
if (foundConn && foundConn.readyState === 1) {
return foundConn;
}
// Create a new connection
return await this.createConnection(tenant);
}
/**
* Create new connection
*
* @private
* @param {ITenant} tenant
* @returns {Connection}
* @memberof TenantConnection
*/
private async createConnection(tenant: ITenant): Promise<Connection> {
// Create or Return a mongo connection
return await mongoose.createConnection(`${tenant.uri}`, this.configService.get('tenant.dbOptions'));
}
}
TenantConnectionFactory
这是自定义提供程序,它为您提供 tenant id
并有助于创建连接 // Tenant creation factory
export const TenantConnectionFactory = [
{
provide: 'TENANT_CONTEXT',
scope: Scope.REQUEST,
inject: [REQUEST],
useFactory: (req: Request): ITenantContext => {
const { tenantId } = req as any;
return new TenantContext(tenantId);
},
},
{
provide: 'TENANT_CONNECTION',
useFactory: async (context: ITenantContext, connection: TenantConnection): Promise<typeof mongoose> => {
// Set tenant context
connection.tenantId = context.tenantId;
// Return the connection
return connection.getConnection();
},
inject: ['TENANT_CONTEXT', TenantConnection],
},
];
TenantsModule
- 在这里您可以看到 TenantConnectionFactory
作为提供者添加并被导出以在其他模块中使用。 @Module({
imports: [
CoreModule,
],
controllers: [TenantsController],
providers: [
TenantsService,
TenantConnection,
...TenantConnectionFactory,
],
exports: [
...TenantConnectionFactory,
],
})
export class TenantsModule {}
TenantModelProviders
- 由于您的租户模型取决于租户连接,您的模型必须通过提供者定义,然后包含在您初始化它们的模块中。 export const TenantModelProviders = [
{
provide: 'USER_MODEL',
useFactory: (connection: Connection) => connection.model('User', UserSchema),
inject: ['TENANT_CONNECTION'],
},
];
UsersModule
- 这个类将使用模型。您还可以看到这里配置的中间件对您的租户数据库路由起作用。在这种情况下,所有 user
路由都是租户的一部分,将由租户 db 提供服务。 @Module({
imports: [
CoreModule,
TenantsModule,
],
providers: [
UsersService,
...TenantModelProviders,
],
controllers: [UsersController],
})
export class UsersModule implements NestModule {
configure(context: MiddlewareConsumer) {
context.apply(TenantAwareMiddleware).forRoutes('/users');
}
}
UsersService
- 从用户模块 @Injectable()
export class UsersService {
constructor(
@Inject('TENANT_CONTEXT') readonly tenantContext: ITenantContext,
@Inject('USER_MODEL') private userModel: Model<IUser>,
) {
Logger.debug(`Current tenant: ${this.tenantContext.tenantId}`);
}
/**
* Create a new user
*
* @param {CreateUserDto} user
* @returns {Promise<IUser>}
* @memberof UsersService
*/
async create(user: CreateUserDto): Promise<IUser> {
try {
const dataToPersist = new this.userModel(user);
// Persist the data
return await dataToPersist.save();
} catch (error) {
throw new HttpException(error, HttpStatus.BAD_REQUEST);
}
}
/**
* Get the list of all users
*
* @returns {Promise<IUser>}
* @memberof UsersService
*/
async findAll(): Promise<IUser> {
return await this.userModel.find({});
}
}
关于mongodb - 我如何在 NESTJS 中设置 Multi-Tenancy ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56085705/