GridFS多租户必须使用独立bucketName实现物理隔离,禁止共享fs默认集合;需动态创建GridFSBucket实例、按租户绑定专属数据库用户并精确授权,删除租户时须成对清理files和chunks集合。

GridFS 多租户必须用独立 bucketName,不能只靠 metadata.tenantId
硬隔离是唯一可靠方式。GridFS 本身无租户概念,fs.files 和 fs.chunks 是默认集合名,所有租户共用即等于裸奔——一旦查询漏写 tenantId、索引缺失、或运维直连 shell,数据就全暴露。物理分离才是底线:每个租户对应一组专属集合,如 tenant_acme_files.files 和 tenant_acme_files.chunks。这要求显式传入 bucketName 参数,否则实例默认绑定到 fs,全局复用就等于把所有租户塞进同一个桶里。
动态构造 GridFSBucket 实例,禁止全局单例或模块级缓存
常见错误是初始化一个 GridFSBucket 然后到处复用。这样 bucketName 就固定死了,无法随租户切换。正确做法是在每次请求上下文内按需创建:
- 从 JWT 或请求头提取
tenantId,做白名单校验(只保留字母、数字、下划线),例如tenantId.replace(/[^a-z0-9_]/g, '_') - 拼接合法
bucketName,如`tenant_${normalizedTenantId}` - 调用
new mongodb.GridFSBucket(db, { bucketName: ... }),生命周期仅限本次请求 - 若担心高频创建开销,可用
Map缓存已构造的实例(key 为规范化后的bucketName),但必须设 TTL 或监听内存压力,否则易泄漏
权限必须落到集合级,不能依赖数据库角色
即使集合已隔离,若账号权限过大,照样能跨桶读写。MongoDB 的 RBAC 不校验租户上下文,readWrite 数据库角色允许访问该库下所有集合,包括你刚建的 tenant_xyz_files.files。必须为每个租户创建专用数据库用户,并在目标库中执行 createUser:
- 先
use tenant_acme_001,再运行db.runCommand({ createUser: "acme_user", roles: [{ role: "readWrite", db: "tenant_acme_001" }] }) - 连接串必须同时指定
database和authSource,且二者一致,例如mongodb://acme_user:pwd@host:27017/tenant_acme_001?authSource=tenant_acme_001 - 权限范围精确到两个集合:
tenant_acme_001.files和tenant_acme_001.chunks,禁用通配符权限
租户删除时要同步清理 files 和 chunks 集合
删租户 ≠ 删文档。如果只删掉业务库或跳过 GridFS 清理,残留的 tenant_xxx_files.* 集合会持续占用磁盘、干扰备份、甚至被误复用。清理必须成对执行:
- 调用
db.collection('tenant_xyz_files.files').drop() - 再调用
db.collection('tenant_xyz_files.chunks').drop() - 不能只删
files,因为chunks中的孤立块无法被自动回收 - 建议封装为原子操作,配合事务(MongoDB 4.0+ 单库事务)或幂等脚本,避免清理中断导致半残状态

















