graphql resolver 不能直接返回 gridfs 的 readable 流,须消费为 buffer/字符串(小文件)或通过 express 路由流式响应(大文件),避免超时与二进制兼容问题。

GraphQL Resolver 怎么读取 GridFS 文件流
直接用 GridFSBucket 的 openDownloadStream 返回的是 Node.js 可读流(Readable),但 GraphQL resolver 必须返回 Promise 或同步值,不能直接 return 流。强行 return 会触发 “Promise rejected with value” 或解析为空对象。
- 必须在 resolver 内部消费流,转成 Buffer 或字符串再返回 —— 适合小文件(
- 大文件别在 resolver 里拼 Buffer,内存爆得快;应改用
stream.pipeline+ HTTP 响应直传(见下一条) - 注意
openDownloadStream需要_id(ObjectId)或filename,传错类型(比如字符串没转ObjectId)会静默失败或抛FileNotFound - 示例:用
stream.promises.pipeline把 GridFS 流写入PassThrough,再挂到 Express 响应上,resolver 只负责触发和返回元数据
怎么让 GraphQL 查询带文件下载头(Content-Disposition)
GraphQL 规范本身不支持二进制响应头,HTTP 层(如 Express)才管这个。resolver 无法设置 Content-Disposition,硬塞会报错或被忽略。
- 正确做法是:resolver 返回文件元信息(
filename、size、_id),前端用额外 GET 请求(非 GraphQL endpoint)拉取文件 - 或用 GraphQL over POST + 自定义 HTTP 处理中间件:在 Express 中拦截特定路径(如
/graphql/download/:id),查 GridFS 后手动设res.setHeader('Content-Disposition', `attachment; filename="${file.filename}"`) - 别试图在 resolver 里调
res.setHeader—— resolver 没有res对象,上下文里也没有
GraphQL 输入类型怎么校验 GridFS 文件 ID 或 filename
ObjectId 字符串和真实 ObjectId 在 MongoDB 查询中行为不同:传错格式会导致查不到,但不报错,容易误判为“文件不存在”。
- 用
graphql-scalars的ObjectIdscalar,自动校验并转成ObjectId实例 - 如果用
String类型接收_id,务必在 resolver 开头加if (!ObjectId.isValid(id)) throw new Error('Invalid ObjectId') - 按
filename查时注意大小写和 URL 编码:前端传的name=report%20v2.pdf,后端要用decodeURIComponent再查,否则匹配失败 - GridFS 的
filename字段默认不建索引,高频查询建议在fs.files集合上加{ filename: 1 }索引
为什么 download resolver 总是超时或内存溢出
常见原因是 resolver 同步读取整个 GridFS 文件到内存,尤其处理视频、压缩包等大文件时,Node.js 进程很快 OOM 或触发 Express 默认 120s 超时。
- 永远不要用
streamToBuffer或readable.toString()加载 >10MB 的文件 - 用
pipeline直接把GridFSBucket.openDownloadStream()接到res,跳过 JS 层缓冲 - 确保 Express 的
responseTimeout和反向代理(Nginx/Cloudflare)的超时时间一致,否则中间层先断连 - GridFS 默认 chunkSizeBytes 是 255KB,查大文件时网络传输时间长,别用 GraphQL Playground 测下载 —— 它不支持二进制响应体渲染
真正卡住的地方往往不是 GraphQL schema 设计,而是流控制和 HTTP 生命周期没对齐:resolver 不是 request handler,它不该接管 response.write 或 setHeader。把下载逻辑拆出去,用轻量路由兜底,比硬塞进 GraphQL 更稳。










