Я новичок в узле. Я создаю маршрут для загрузки фотографии в файловую систему. Я получил реализацию от загрузки фотографий Digital Ocean
Я использую архитектуру MVC. Вот код для контроллеров
exports.uploadImage = asyncHandler (async (req, res, next) =>{
const event = await Event.findById(req.params.id);
if(!event){
return next (new ErrorResponse (`No event found with id of ${req.params.id}`, 404));
}
// Check if the user is the event owner or an admin
if(event.user.toString()!==req.user.id && req.user.role !=='admin'){
return next(new ErrorResponse(`User with id ${req.user.id} is not allowed to update this event`, 401));
}
// Check if files exist
if (!req.files) {
return next (new ErrorResponse (`Please upload photo for event`, 400));
}
let files;
const file = req.file.filename;
const matches = file.match(/^(.+?)_.+?\.(.+)$/i);
if (matches) {
files = _.map(['lg', 'md', 'sm'], function(size) {
return matches[1] + '_' + size + '.' + matches[2];
});
} else {
files = [file];
}
files = _.map(files, function(file) {
const port = req.app.get('port');
const base = req.protocol + '://' + req.hostname + (port ? ':' + port : '');
const url = path.join(req.file.baseUrl, file).replace(/[\\\/]+/g, '/').replace(/^[\/]+/g, '');
return (req.file.storage == 'local' ? base : '') + '/' + url;
});
await Event.findByIdAndUpdate(req.params.id, { photo: files });
res.status(200).json({
success: true,
data: files
});
});
Вот импорт molule
const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/asyncHandler');
const Event = require('../models/Event');
// File upload modules
const _ = require('lodash');
const path = require('path');
const multer = require('multer');
const ImageStorage = require('../utils/imageStorage');
Файл маршрута
const express = require ('express');
const {uploadImage} = require('../controllers/events');
const Event = require('../models/Event');
// Protect and authorize routes requiring authentication
const { protect, authorize } = require('../middleware/auth');
const router = express.Router();
router.route('/:id/uploads/photo').put(protect, authorize('admin', 'agent'), uploadImage);
Загрузка фото находится в папке utils
// Load dependencies
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const Jimp = require('jimp');
const crypto = require('crypto');
const mkdirp = require('mkdirp');
const concat = require('concat-stream');
const streamifier = require('streamifier');
// Configure UPLOAD_PATH
// process.env.IMAGE_FIELD contains uploads/images
const UPLOAD_PATH = path.resolve(__dirname, '..', process.env.IMAGE_FIELD);
// create a multer storage engine
const ImageStorage = function(options) {
// this serves as a constructor
function ImageStorage(opts) {
const baseUrl = process.env.IMAGE_BASE_URL;
const allowedStorageSystems = ['local']; //Use AWS for production
const allowedOutputFormats = ['jpg', 'png', 'gif'];
// fallback for the options
const defaultOptions = {
storage: process.env.STORAGE_LOCATION, //Add
output: 'png',
greyscale: false,
quality: 70,
square:false,
threshold: 500,
responsive: true,
};
// extend default options with passed options
let options = (opts && _.isObject(opts)) ? _.pick(opts, _.keys(defaultOptions)) : {
};
options = _.extend(defaultOptions, options);
// check the options for correct values and use fallback value where necessary
this.options = _.forIn(options, function(value, key, object) {
switch (key) {
case 'square':
case 'greyscale':
case 'responsive':
object[key] = _.isBoolean(value) ? value : defaultOptions[key];
break;
case 'storage':
value = String(value).toLowerCase();
object[key] = _.includes(allowedStorageSystems, value) ? value : defaultOptions[key];
break;
case 'output':
value = String(value).toLowerCase();
object[key] = _.includes(allowedOutputFormats, value) ? value : defaultOptions[key];
break;
case 'quality':
value = _.isFinite(value) ? value : Number(value);
object[key] = (value && value >= 0 && value <= 100) ? value : defaultOptions[key];
break;
case 'threshold':
value = _.isFinite(value) ? value : Number(value);
object[key] = (value && value >= 0) ? value : defaultOptions[key];
break;
}
});
// set the upload path
this.uploadPath = this.options.responsive ? path.join(UPLOAD_PATH, 'responsive') : UPLOAD_PATH;
// set the upload base url
this.uploadBaseUrl = this.options.responsive ? path.join(baseUrl, 'responsive') : baseUrl;
if (this.options.storage == 'local') {
// if upload path does not exist, create the upload path structure
!fs.existsSync(this.uploadPath) && mkdirp.sync(this.uploadPath);
}
}
// this generates a random cryptographic filename
ImageStorage.prototype._generateRandomFilename = function() {
// create pseudo random bytes
const bytes = crypto.pseudoRandomBytes(32);
// create the md5 hash of the random bytes
const checksum = crypto.createHash('MD5').update(bytes).digest('hex');
// return as filename the hash with the output extension
return checksum + '.' + this.options.output;
};
// this creates a Writable stream for a filepath
ImageStorage.prototype._createOutputStream = function(filepath, cb) {
// create a reference for this to use in local functions
const that = this;
// create a writable stream from the filepath
const output = fs.createWriteStream(filepath);
// set callback fn as handler for the error event
output.on('error', cb);
// set handler for the finish event
output.on('finish', function() {
cb(null, {
destination: that.uploadPath,
baseUrl: that.uploadBaseUrl,
filename: path.basename(filepath),
storage: that.options.storage
});
});
// return the output stream
return output;
};
// this processes the Jimp image buffer
ImageStorage.prototype._processImage = function(image, cb) {
// create a reference for this to use in local functions
const that = this;
let batch = [];
// the responsive sizes
const sizes = ['lg', 'md', 'sm'];
const filename = this._generateRandomFilename();
let mime = Jimp.MIME_PNG;
// The default value of the mime type to be an .pgn
// create a clone of the Jimp image
let clone = image.clone();
// fetch the Jimp image dimensions
const width = clone.bitmap.width;
const height = clone.bitmap.height;
const square = Math.min(width, height);
const threshold = this.options.threshold;
// resolve the Jimp output mime type
switch (this.options.output) {
case 'jpg':
mime = Jimp.MIME_JPEG;
break;
case 'png':
default:
mime = Jimp.MIME_PNG;
break;
}
// auto scale the image dimensions to fit the threshold requirement
if (threshold && square > threshold) {
clone = (square == width) ? clone.resize(threshold, Jimp.AUTO) : clone.resize(Jimp.AUTO, threshold);
}
// // crop the image to a square if enabled
// if (this.options.square) {
// if (threshold) {
// square = Math.min(square, threshold);
// }
// // // fetch the new image dimensions and crop
// // clone = clone.crop((clone.bitmap.width: square) / 2, (clone.bitmap.height: square) / 2, square, square);
// }
// convert the image to greyscale if enabled
if (this.options.greyscale) {
clone = clone.greyscale();
}
// set the image output quality
clone = clone.quality(this.options.quality);
if (this.options.responsive) {
// map through the responsive sizes and push them to the batch
batch = _.map(sizes, function(size) {
let outputStream;
let image = null;
let filepath = filename.split('.');
// create the complete filepath and create a writable stream for it
filepath = filepath[0] + '_' + size + '.' + filepath[1];
filepath = path.join(that.uploadPath, filepath);
outputStream = that._createOutputStream(filepath, cb);
// scale the image based on the size
switch (size) {
case 'sm':
image = clone.clone().scale(0.3);
break;
case 'md':
image = clone.clone().scale(0.7);
break;
case 'lg':
image = clone.clone();
break;
}
// return an object of the stream and the Jimp image
return {
stream: outputStream,
image
};
});
} else {
// push an object of the writable stream and Jimp image to the batch
batch.push({
stream: that._createOutputStream(path.join(that.uploadPath, filename), cb),
image: clone
});
}
// process the batch sequence
_.each(batch, function(current) {
// get the buffer of the Jimp image using the output mime type
current.image.getBuffer(mime, function(err, buffer) {
if (that.options.storage == 'local') {
// create a read stream from the buffer and pipe it to the output stream
streamifier.createReadStream(buffer).pipe(current.stream);
}
});
});
};
// multer requires this for handling the uploaded file
ImageStorage.prototype._handleFile = function(req, file, cb) {
// create a reference for this to use in local functions
let that = this;
// create a writable stream using concat-stream that will
// concatenate all the buffers written to it and pass the
// complete buffer to a callback fn
let fileManipulate = concat(function(imageData) {
// read the image buffer with Jimp
// it returns a promise
Jimp.read(imageData)
.then(function(image) {
// process the Jimp image buffer
that._processImage(image, cb);
})
.catch(cb);
});
// write the uploaded file buffer to the fileManipulate stream
file.stream.pipe(fileManipulate);
};
// multer requires this for destroying file
ImageStorage.prototype._removeFile = function(req, file, cb) {
let matches, pathsplit;
const filename = file.filename;
const _path = path.join(this.uploadPath, filename);
let paths = [];
// delete the file properties
delete file.filename;
delete file.destination;
delete file.baseUrl;
delete file.storage;
// create paths for responsive images
if (this.options.responsive) {
pathsplit = _path.split('/');
matches = pathsplit.pop().match(/^(.+?)_.+?\.(.+)$/i);
if (matches) {
paths = _.map(['lg', 'md', 'sm'], function(size) {
return pathsplit.join('/') + '/' + (matches[1] + '_' + size + '.' + matches[2]);
});
}
} else {
paths = [_path];
}
// delete the files from the filesystem
_.each(paths, function(_path) {
fs.unlink(_path, cb);
});
};
// create a new instance with the passed options and return it
return new ImageStorage(options);
};
// export the storage engine
module.exports = ImageStorage;
Вот папка промежуточного программного обеспечения const ImageStorage = require ('../utils/imageStorage');
const _ = require('lodash');
const path = require('path');
const multer = require('multer');
const photoUpload = (req, res, next) =>{
// setup a new instance of the AvatarStorage engine
const storage = ImageStorage({
square: false,
responsive: true,
greyscale: true,
quality: 90
});
const limits = {
files: 1, // allow only 1 file per request
fileSize: process.env.IMAGE_SIZE, // 1 MB (max file size)
};
const fileFilter = function(req, file, cb) {
// supported image file mimetypes
const allowedMimes = ['image/jpeg', 'image/pjpeg', 'image/png', 'image/gif'];
if (_.includes(allowedMimes, file.mimetype)) {
// allow supported image files
cb(null, true);
} else {
// throw error for invalid files
cb(new Error('Invalid file type. Only jpg, png and gif image files are allowed.'));
}
};
// setup multer
const upload = multer({
storage,
limits,
fileFilter
});
next();
return upload();
};
module.exports = photoUpload()
;
Я только что вызвал промежуточное программное обеспечение на сервер. js файл
const photoUpload = require ('./middleware/storage');
app.use(photoUpload());
Когда я пытался сохранить файл, вот ошибка, которую я получил
path.js:28
throw new TypeError('Path must be a string. Received ' + inspect(path));
^
TypeError: Path must be a string. Received undefined
at assertPath (path.js:28:11)
at Object.resolve (path.js:1168:7)
at Object.<anonymous> (/home/nerd/Backend Projects/ewayv-api/utils/imageStorage.js:13:26)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
at Module.require (module.js:597:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/home/nerd/Backend Projects/ewayv-api/middleware/storage.js:1:84)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
Пожалуйста, помогите Спасибо