Модуль GridFS
Прежде всего, специальный реквизит идет к tjholowaychuk, который ответил в канале # node.js irc, когда я спросил, повезло ли кому-нибудь использовать GridFS из mongoose . Большая часть моего результирующего кода получена из сущности, которой он поделился со мной. Во всяком случае, к коду. Я опишу, как я использую gridfs, и после установки основы проиллюстрирую, как просто передавать файлы из GridFS.
Я создал модуль gridfs, который в основном обращается к GridStore через mongoose (который я использую во всем приложении), который также может использовать соединение db, созданное при подключении mongoose к серверу mongodb.
1
2
3
4
5
6
|
mongoose = require "mongoose" request = require "request" GridStore = mongoose.mongo.GridStore Grid = mongoose.mongo.Grid ObjectID = mongoose.mongo.BSONPure.ObjectID |
Мы не можем получить файлы из mongodb, если не можем что-то в них поместить, поэтому давайте создадим операцию putFile.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
exports.putFile = (path, name, options..., fn) -> db = mongoose.connection.db options = parse(options) options.metadata.filename = name new GridStore(db, name, "w" , options).open (err, file) -> return fn(err) if err file.writeFile path, fn parse = (options) -> opts = {} if options.length > 0 opts = options[ 0 ] if !opts.metadata opts.metadata = {} opts |
Это действительно просто делегирует операции putFile, которая существует в GridStore как часть модуля mongodb. У меня также есть небольшая логика для анализа параметров, предоставляя значения по умолчанию, если они не были предоставлены. Следует отметить одну интересную особенность: я храню имя файла в метаданных, потому что в то время я столкнулся с забавной проблемой, когда файлы, полученные из gridFS, имели в качестве имени файла идентификатор (хотя взгляд на монго показывает, что имя файла на самом деле база данных).
Теперь операция get. Первоначальная реализация этого просто передавала содержимое в виде буфера предоставленному обратному вызову, вызывая store.readBuffer (), но теперь это изменяется, чтобы передать полученный объект хранилища обратному вызову. Значение в том, что вызывающая сторона может использовать объект хранилища для доступа к метаданным, contentType и другим деталям. Пользователь также может определить, как он хочет прочитать файл (либо в память, либо с помощью ReadableStream).
01
02
03
04
05
06
07
08
09
10
11
12
|
exports.get = (id, fn) -> db = mongoose.connection.db id = new ObjectID(id) store = new GridStore(db, id, "r" , root: "fs" ) store.open (err, store) -> return fn(err) if err # band-aid if "#{store.filename}" == "#{store.fileId}" and store.metadata and store.metadata.filename store.filename = store.metadata.filename fn null , store |
У этого кода есть небольшая ошибка в том, что он проверяет, совпадают ли имя файла и fileId. Если это так, то он проверяет, установлено ли metadata.filename, и устанавливает store.filename в найденное там значение. Я поставил эту проблему на обсуждение позже.
Модель
В моем конкретном случае я хотел прикрепить файлы к модели. В этом примере давайте представим, что у нас есть Приложение для чего-либо (работа, заявка на кредит и т. Д.), К которому мы можем прикрепить любое количество файлов. Подумайте о налоговых поступлениях, заполненном заявлении, других отсканированных документах.
1
2
3
4
5
6
7
8
|
ApplicationSchema = new mongoose.Schema( name: String files: [ mongoose.Schema.Mixed ] ) ApplicationSchema.methods.addFile = (file, options, fn) -> gridfs.putFile file.path, file.filename, options, (err, result) => @files .push result @save fn |
Здесь я определяю файлы как массив типов объектов Mixed (то есть они могут быть чем угодно) и метод addFile, который в основном принимает объект, который по крайней мере содержит атрибут пути и имени файла. Он использует это для сохранения файла в gridfs и сохраняет полученный объект файла gridstore в массиве файлов (он содержит такие вещи, как id, uploadDate, contentType, name, size и т. Д.).
Обработка запросов
Это все подключается к обработчику запросов для обработки отправки формы в / new. Все это влечет за собой создание экземпляра модели приложения, добавление загруженного файла из запроса (в данном случае мы назвали поле файла «file», следовательно, req.files.file ) и сохранение его.
1
2
3
4
5
6
7
|
app.post "/new" , (req, res) -> application = new Application() application.name = req.body.name opts = content_type: req.files.file.type application.addFile req.files.file, opts, (err, result) -> res.redirect "/" |
Теперь сумма всей этой работы позволяет нам пожинать плоды, упрощая загрузку запрошенного файла из gridFS.
1
2
3
4
5
|
app.get "/file/:id" , (req, res) -> gridfs.get req.params.id, (err, file) -> res.header "Content-Type" , file.type res.header "Content-Disposition" , "attachment; filename=#{file.filename}" file.stream( true ).pipe(res) |
Здесь мы просто ищем файл по идентификатору и используем полученный объект файла для установки полей Content-Type и Content-Disposition и, наконец, используем ReadableStream :: pipe для записи файла в объект ответа (который является экземпляром WritableStream ). Это волшебство, которое передает данные из MongoDB на клиентскую сторону.
идеи
Это просто скромное начало. Другие идеи включают в себя полностью инкапсулирование gridfs внутри модели. Продвигаясь дальше, мы можем даже превратить модель gridfs в плагин mongoose, чтобы полностью использовать gridfs в черном ящике.
Не стесняйтесь проверить проект и дайте мне знать, если у вас есть идеи, чтобы пойти еще дальше. Вилка прочь!
Ссылка: потоковая передача файлов из MongoDB GridFS от нашего партнера по JCG Джеймса Карра в блоге Rants and Musings of Agile Developer