gpt4 book ai didi

mongodb - 我如何在 NESTJS 中设置 Multi-Tenancy

转载 作者:行者123 更新时间:2023-12-03 02:12:38 24 4
gpt4 key购买 nike

我想连接到基于子域( 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 一起使用的解决方案

  1. 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 中间件,用于从请求上下文获取租户 ID。您可以在此处创建自己的逻辑,从请求 header 或请求 url 子域中提取租户 ID。请求头提取方法如下所示。
  • 如果您想提取子域,可以通过调用 req.subdomainsRequest 对象中提取子域来完成同样的操作,这将为您提供子域列表然后你就可以从中找到你要找的那个。

    @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 此类用于使用租户 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 这是自定义提供程序,可为您获取租户 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 - 此类将使用模型。您还可以看到此处配置的中间件可根据您的 Tenand 数据库路由进行操作。在这种情况下,所有用户路由都是租户的一部分,并将由租户数据库提供服务。
  • @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/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com