如何配置NestJS运行mongosh脚本并实现SaaS用户专属MongoDB动态创建与连接
问题解答:NestJS + MongoDB 实现SaaS用户专属数据库方案
一、NestJS 配置动态创建MongoDB数据库脚本
首先提前安装依赖:@nestjs/mongoose mongoose,分两步完成基础配置:
1. 主数据库连接配置
先配置全局的主数据库连接,用于存储所有用户的专属数据库元信息,在app.module.ts中引入:
import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; @Module({ imports: [ MongooseModule.forRoot('mongodb://localhost:27017/saas-main', { connectionName: 'main', }), // 其他业务模块引入 ], }) export class AppModule {}
2. 注册逻辑嵌入建库脚本
在用户注册的Service逻辑中,直接调用mongoose的连接方法创建新库,同时将库信息写入主库:
import { Injectable } from '@nestjs/common'; import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { Connection, Model, createConnection } from 'mongoose'; import { UserDb, UserDbDocument } from './schemas/user-db.schema'; // 主库存储用户库信息的Schema @Injectable() export class AuthService { constructor( @InjectConnection('main') private mainConnection: Connection, @InjectModel(UserDb.name, 'main') private userDbModel: Model<UserDbDocument>, ) {} async register(userInfo: any) { // 第一步:创建主库的用户基础账号信息 const newUserId = 'xxx'; // 替换为实际生成的用户ID // 第二步:生成用户专属库名,可自定义规则,比如`saas_user_${userId}` const userDbName = `saas_user_${newUserId}`; // 第三步:动态创建数据库连接,写入任意集合一条数据即可触发MongoDB自动建库 const userConnection = await createConnection(`mongodb://localhost:27017/${userDbName}`).asPromise(); // 可按需初始化用户库的默认集合,比如配置表、初始数据 await userConnection.collection('system_config').insertOne({ initStatus: true, createTime: new Date() }); await userConnection.close(); // 第四步:将用户库元信息存入主库,生产环境可配置带权限的数据库访问账号密码 await this.userDbModel.create({ userId: newUserId, dbName: userDbName, dbUri: `mongodb://localhost:27017/${userDbName}`, }); // 后续返回注册结果即可 } }
二、用户登录时动态连接专属数据库
建议新增连接池缓存逻辑,避免每次请求都新建连接消耗性能:
import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Connection, Model, createConnection } from 'mongoose'; import { UserDb, UserDbDocument } from './schemas/user-db.schema'; @Injectable() export class DbConnectionService { // 缓存已创建的连接,key为用户ID private connectionPool: Map<string, Connection> = new Map(); constructor( @InjectModel(UserDb.name, 'main') private userDbModel: Model<UserDbDocument>, ) {} async getUserConnection(userId: string): Promise<Connection> { // 优先读取缓存 if (this.connectionPool.has(userId)) { return this.connectionPool.get(userId); } // 从主库查询用户专属库信息 const userDbInfo = await this.userDbModel.findOne({ userId }); if (!userDbInfo) { throw new Error('用户专属数据库不存在'); } // 新建数据库连接 const connection = await createConnection(userDbInfo.dbUri).asPromise(); // 存入缓存,可额外添加过期回收逻辑,长时间不用的连接自动关闭并清除缓存 this.connectionPool.set(userId, connection); return connection; } }
用户登录后,后续业务请求可从JWT或Session中提取用户ID,调用上述getUserConnection方法拿到对应库的连接,即可操作对应集合完成业务逻辑。
三、场景优化方案参考
你当前的按用户分库方案适合数据隔离要求极高的场景,但如果用户量达到十万级以上,会存在MongoDB实例性能下降、连接数易打满、备份运维成本高的问题,可根据业务实际需求选择更适配的方案:
- 低隔离需求:所有用户数据存在同一个库的同一个集合,每个文档新增
userId字段做区分,查询时强制添加userId过滤条件,开发运维成本最低,性能最优。 - 中等隔离需求:同一个数据库下,每个用户对应独立集合,集合名按
${collectionName}_${userId}规则命名,比全表隔离的隔离性更高,单集合数据量更小,查询性能更好。 - 高隔离需求:保留现有分库方案,优化点为将用户库按分片规则分布到不同MongoDB集群节点,避免单节点压力过大,同时补充连接池自动回收逻辑,控制单实例连接数上限。
内容的提问来源于stack exchange,提问作者Muhammad Khalil
相关产品推荐
相关产品推荐

