根本原因是pdf二进制流被误作字符串处理,导致非ascii字节错误解码;必须用buffer/uint8array原始读取、设置contenttype、显式构造gridfsbucket、正确处理下载流与objectid类型转换。

GridFS 存 PDF 时为什么文件变大或损坏
根本原因通常是把 PDF 的二进制流误当成字符串处理,比如用 fs.readFileSync(path, 'utf8') 读取后直接传给 gridFSBucket.uploadFromStream() —— 这会让非 ASCII 字节被错误解码,PDF 头部(如 %PDF-1.4)一乱,下游就打不开。
- 必须用
Buffer或Uint8Array原始二进制方式读取:例如fs.readFileSync(path)(不带编码参数)或fs.createReadStream(path) - 上传时指定
contentType: 'application/pdf',否则默认是application/octet-stream,某些前端库可能不自动触发 PDF 预览 - 别在上传前对 Buffer 做
.toString()、JSON.stringify()或任何字符串转换
用 Node.js MongoDB Driver v5+ 上传 PDF 到 GridFS
v5+ 的 MongoClient 默认启用新驱动 API,GridFSBucket 不再从 db 上直接挂载,需显式构造;同时 uploadFromStream() 返回的是 Promise,不是 callback。
- 初始化 bucket 要传
{ bucketName: 'pdfs' }显式命名,否则默认用fs,容易和别的业务冲突 - 上传流必须是可读流(
fs.createReadStream())或Buffer,不能是路径字符串 - 推荐加
metadata字段存业务信息,比如{ originalName: 'report.pdf', userId: 'u123' },后续查起来方便
const stream = fs.createReadStream('./report.pdf');
const uploadStream = bucket.uploadFromStream(
'report.pdf',
stream,
{
contentType: 'application/pdf',
metadata: { originalName: 'report.pdf', userId: 'u123' }
}
);
const { _id } = await uploadStream;
根据 fileId 查 PDF 并返回给 HTTP 响应
常见错误是调用 bucket.openDownloadStream() 后没设响应头,或者用 toArray() 把整个文件加载进内存——PDF 动辄几 MB,Node.js 进程容易 OOM。
- 必须用
res.setHeader('Content-Type', 'application/pdf')和res.setHeader('Content-Disposition', 'inline; filename="..."),否则浏览器可能下载而非预览 - 直接把 download stream
pipe给res,不要toArray()或read() - 注意处理文件不存在:当
bucket.openDownloadStream(fileId)返回 null,要手动res.status(404).end()
const downloadStream = bucket.openDownloadStream(fileId);
if (!downloadStream) {
return res.status(404).end();
}
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'inline; filename="report.pdf"');
downloadStream.pipe(res);
用 ObjectId 查询 GridFS 文件元数据但查不到
files 集合里存的 _id 是 ObjectId,但如果你用字符串(比如 URL 参数里传的 "65a1b2c3d4e5f67890123456")直接查,MongoDB 不会自动转类型,结果为空。
- 查询前必须用
new ObjectId(idString)转换,且确保idString是合法 24 位十六进制 - 查元数据用
bucket.find({ _id: objectId }),不是collection.find()—— GridFS 元数据存在pdfs.files集合,但 driver 封装了这层细节 - 如果用
findOne(),记得await,它的返回值是文档对象,含length、uploadDate、metadata等字段
最容易被忽略的是:GridFS 的 files 和 chunks 集合名默认是 fs.files 和 fs.chunks,但如果你初始化 bucket 时指定了 bucketName: 'pdfs',那实际集合就是 pdfs.files 和 pdfs.chunks —— 直接去 shell 里查错地方,永远找不到。










