如何使用Node.js和Mongoose来使用GridFS存储图片

31

我是Node.js新手,能否给我提供一个使用Node.js和Mongoose存储和获取二进制数据(例如图片)的GridFS示例?我需要直接访问GridFS吗?


你是在寻找Mongoose的示例还是可以接受其他库? - Chris Biscardi
我正在使用Node.js和MongoDB来存储和检索数据。但是在存储和检索图像文件方面遇到了问题,因此我想使用GridFS来实现相同的目的。我对如何实现这一点感到困惑。 - Dar Hamid
我正在使用mongoose,express和connect-form。 - Dar Hamid
5个回答

31

我对这里得到的最高评价答案不满意,因此提供一个新的答案: 最终我使用了node模块'gridfs-stream' (那里有很好的文档!),可以通过npm安装。 结合mongoose,代码看起来可能是这样的:

var fs = require('fs');
var mongoose = require("mongoose");
var Grid = require('gridfs-stream');
var GridFS = Grid(mongoose.connection.db, mongoose.mongo);

function putFile(path, name, callback) {
    var writestream = GridFS.createWriteStream({
        filename: name
    });
    writestream.on('close', function (file) {
      callback(null, file);
    });
    fs.createReadStream(path).pipe(writestream);
}

请注意,path是本地系统上文件的路径。

至于我的文件读取函数,在我的情况下,我只需要将文件流式传输到浏览器(使用express):

try {
    var readstream = GridFS.createReadStream({_id: id});
    readstream.pipe(res);
} catch (err) {
    log.error(err);
    return next(errors.create(404, "File not found."));
}

2
这个 gridfs-stream 包太旧了,已经不再维护。Riky_Tree 的方法更适合。 - Sheece Gardazi
Sheece Gardazi是正确的,我实现了gridfs-stream,现在由于无数个原因,我不得不重新做所有事情。 - Curious Flower
2
StackOverflow有没有关于如何处理过时答案的指南?简单删除它们对于正在使用旧系统的人没有好处。虽然我不再使用mongodb,但gridfs-stream每周在NPM上仍有36K次下载。 - Mitja

26

迄今为止的答案都很好,但我认为在这里记录使用官方的 mongodb nodejs driver 如何执行此操作将是有益的,而不是依赖于进一步的抽象,例如“gridfs-stream”。

之前的回答确实使用了官方的mongodb驱动程序,但他们使用了已经被弃用的Gridstore API,请参见此处。我的示例将使用新的GridFSBucket API

问题很广泛,因此我的答案将是一个完整的nodejs程序。这将包括设置express服务器、mongodb驱动程序、定义路由和处理GET和POST路由。

使用的Npm包

  • express(nodejs web应用程序框架,以简化此代码片段)
  • multer(用于处理multipart/form-data请求)
  • mongodb(官方的mongodb nodejs驱动程序)

GET照片路由接受Mongo ObjectID作为参数来检索图像。

我配置multer将上传的文件保留在内存中。这意味着照片文件不会在任何时候写入文件系统,而是直接从内存流式传输到GridFS。


/**
 * NPM Module dependencies.
 */
const express = require('express');
const photoRoute = express.Router();

const multer = require('multer');
var storage = multer.memoryStorage()
var upload = multer({ storage: storage, limits: { fields: 1, fileSize: 6000000, files: 1, parts: 2 }});

const mongodb = require('mongodb');
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
let db;

/**
 * NodeJS Module dependencies.
 */
const { Readable } = require('stream');

/**
 * Create Express server && Routes configuration.
 */
const app = express();
app.use('/photos', photoRoute);

/**
 * Connect Mongo Driver to MongoDB.
 */
MongoClient.connect('mongodb://localhost/photoDB', (err, database) => {
  if (err) {
    console.log('MongoDB Connection Error. Please make sure that MongoDB is running.');
    process.exit(1);
  }
  db = database;
});

/**
 * GET photo by ID Route
 */
photoRoute.get('/:photoID', (req, res) => {
  try {
    var photoID = new ObjectID(req.params.photoID);
  } catch(err) {
    return res.status(400).json({ message: "Invalid PhotoID in URL parameter. Must be a single String of 12 bytes or a string of 24 hex characters" }); 
  }

  let bucket = new mongodb.GridFSBucket(db, {
    bucketName: 'photos'
  });

  let downloadStream = bucket.openDownloadStream(photoID);

  downloadStream.on('data', (chunk) => {
    res.write(chunk);
  });

  downloadStream.on('error', () => {
    res.sendStatus(404);
  });

  downloadStream.on('end', () => {
    res.end();
  });
});

/**
 * POST photo Route
 */
photoRoute.post('/', (req, res) => {
  upload.single('photo')(req, res, (err) => {
    if (err) {
      return res.status(400).json({ message: "Upload Request Validation Failed" });
    } else if(!req.body.name) {
      return res.status(400).json({ message: "No photo name in request body" });
    }
    
    let photoName = req.body.name;
    
    // Covert buffer to Readable Stream
    const readablePhotoStream = new Readable();
    readablePhotoStream.push(req.file.buffer);
    readablePhotoStream.push(null);

    let bucket = new mongodb.GridFSBucket(db, {
      bucketName: 'photos'
    });

    let uploadStream = bucket.openUploadStream(photoName);
    let id = uploadStream.id;
    readablePhotoStream.pipe(uploadStream);

    uploadStream.on('error', () => {
      return res.status(500).json({ message: "Error uploading file" });
    });

    uploadStream.on('finish', () => {
      return res.status(201).json({ message: "File uploaded successfully, stored under Mongo ObjectID: " + id });
    });
  });
});

app.listen(3005, () => {
  console.log("App listening on port 3005!");
});

我写了一篇关于这个主题的博客文章,它是对我的回答的阐述。在这里可以找到。

更多阅读/灵感:


9
我建议您查看这个问题:使用Node.JS保存文件的MongoDB GridFS问题 以下是答案中的示例(由christkv提供):
// You can use an object id as well as filename now
var gs = new mongodb.GridStore(this.db, filename, "w", {
  "chunk_size": 1024*4,
  metadata: {
    hashpath:gridfs_name,
    hash:hash,
    name: name
  }
});

gs.open(function(err,store) {
  // Write data and automatically close on finished write
  gs.writeBuffer(data, true, function(err,chunk) {
    // Each file has an md5 in the file structure
    cb(err,hash,chunk);
  });
});

6
我对上面例子中的 this.db 部分感到困惑,不确定如何从 Mongoose 中获取 MongoDB 数据库对象。 - Howard M. Lewis Ship
1
我认为需要使用("mongoose").connection.db。 - Mitja

3

看起来writeBuffer已经被弃用了。

/Users/kmandrup/private/repos/node-mongodb-native/HISTORY:
   82  * Fixed dereference method on Db class to correctly dereference Db reference objects. 
   83  * Moved connect object onto Db class(Db.connect) as well as keeping backward compatibility.
   84: * Removed writeBuffer method from gridstore, write handles switching automatically now.
   85  * Changed readBuffer to read on Gridstore, Gridstore now only supports Binary Buffers no Strings anymore.

2
看起来这个问题很容易解决,只需要切换到简单的“写入”即可。 - Eric Martindale

0

移除文件上传库,如果出现一些多部分头相关的错误,则从头中移除内容类型。


目前你的回答不够清晰,请[编辑]以添加更多细节,帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接