node和express_如何在Node和Express中添加高级照片上传

news/2024/7/7 13:06:48

node和express

介绍 (Introduction)

At one time or another when building our Node application we have been faced with uploading a photo (usually from a form) to be used as a profile photo for a user in our app. In addition, we usually have to store the photo in the local filesystem (during development) or even in the cloud for easy access. Since this is a very common task, there are lots of tools available which we can leverage to handle the individual parts of the process.

在构建Node应用程序时,我们有时不得不上传一张照片(通常是来自表单)以用作我们应用程序中用户的个人资料照片。 此外,为了便于访问,我们通常必须将照片存储在本地文件系统中(在开发过程中),甚至存储在云中。 由于这是一项非常常见的任务,因此我们可以利用许多工具来处理流程的各个部分。

In this tutorial, we will see how to upload a photo and manipulate it (resize, crop, greyscale, etc) before writing it to storage. We will limit ourselves to storing files in the local filesystem for simplicity.

在本教程中,我们将了解如何在将照片写入存储之前上传照片并进行操作(调整大小,裁剪,灰度等)。 为了简单起见,我们将限制自己存储在本地文件系统中。

先决条件 (Prerequisites)

We will be using the following packages to build our application:

我们将使用以下软件包来构建我们的应用程序:

  • express: A very popular Node server.

    express :非常受欢迎的Node服务器。

  • lodash: A very popular JavaScript library with lots of utility functions for working with arrays, strings, objects and functional programming.

    lodash :一个非常流行JavaScript库,带有许多实用程序函数,用于处理数组,字符串,对象和函数式编程。

  • multer: A package for extracting files from multipart/form-data requests.

    multer :从multipart/form-data请求中提取文件的软件包。

  • jimp: An image manipulation package.

    jimp :图像处理包。

  • dotenv: A package for adding .env variables to process.env.

    dotenv :将.env变量添加到process.env软件包。

  • mkdirp: A package for creating nested directory structure.

    mkdirp :用于创建嵌套目录结构的软件包。

  • concat-stream: A package for creating a writable stream that concatenates all the data from a stream and calls a callback with the result.

    concat-stream :用于创建可写流的包,该可写流将流中的所有数据连接起来并调用结果回调。

  • streamifier: A package to convert a Buffer/String into a readable stream.

    streamifier :一个将Buffer / String转换为可读流的包。

项目目标 (Project Goals)

We want to take over the uploaded file stream from Multer and then manipulate the stream buffer (image) however we wish using Jimp, before writing the image to storage (local filesystem). This will require us to create a custom storage engine to use with Multer — which we will be doing in this tutorial.

我们希望从Multer接管上传的文件流,然后操纵流缓冲区( 图像 ),但是我们希望在将图像写入存储( 本地文件系统 )之前使用Jimp 。 这将需要我们创建一个自定义存储引擎以与Multer一起使用-我们将在本教程中进行此操作。

Here is the end result of what we will be building in this tutorial:

这是我们将在本教程中构建的最终结果:

第1步-入门 (Step 1 — Getting Started)

We will begin by creating a new Express app using the Express generator. If you don’t have the Express generator already you will need to install it first by running the following command on your command line terminal:

我们将从使用Express生成器创建一个新的Express应用开始。 如果您还没有Express生成器,则需要首先在命令行终端上运行以下命令来安装它:

  • npm install express-generator -g

    npm install express-generator -g

Once you have the Express generator, you can now run the following commands to create a new Express app and install the dependencies for Express. We will be using ejs as our view engine:

一旦有了Express生成器,您现在就可以运行以下命令来创建新的Express应用并安装Express的依赖项。 我们将使用ejs作为我们的视图引擎:

  • express --view=ejs photo-uploader-app

    表达--view = ejs photo-uploader-app

  • cd photo-uploader-app

    cd photo-uploader-app

  • npm install

    npm安装

Next, we will install the remaining dependencies we need for our project:

接下来,我们将安装项目所需的其余依赖关系:

  • npm install --save lodash multer jimp dotenv concat-stream streamifier mkdirp

    npm install --save lodash multer jimp dotenv concat-stream streamifier mkdirp

第2步-配置基础 (Step 2 — Configuring the Basics)

Before we continue, our app will need some form configuration. We will create a .env file on our project root directory and add some environment variables. The .env file should look like the following snippet.

在继续之前,我们的应用程序将需要一些表单配置。 我们将在项目根目录中创建一个.env文件,并添加一些环境变量。 .env文件应类似于以下片段。

AVATAR_FIELD=avatar
AVATAR_BASE_URL=/uploads/avatars
AVATAR_STORAGE=uploads/avatars

Next, we will load our environment variables into process.env using dotenv so that we can access them in our app. To do this, we will add the following line to the app.js file. Ensure you add this line at the point where you are loading the dependencies. It must come before all route imports and before creating the Express app instance.

接下来,我们将使用dotenv将环境变量加载到process.env中,以便我们可以在应用程序中访问它们。 为此,我们将以下行添加到app.js文件中。 确保在加载依赖项的位置添加此行。 它必须在所有路由导入之前和创建Express应用程序实例之前出现。

app.js
app.js
var dotenv = require('dotenv').config();

Now we can access our environment variables using process.env. For example: process.env.AVATAR_STORAGE should contain the value uploads/avatars. We will go ahead to edit our index route file routes/index.js to add some local variables we will be needing in our view. We will add two local variables:

现在,我们可以使用process.env访问我们的环境变量。 例如: process.env.AVATAR_STORAGE应该包含值uploads/avatars 。 我们将继续编辑索引路由文件routes/index.js以添加一些我们认为需要的局部变量。 我们将添加两个局部变量:

  • title: The title of our index page: Upload Avatar

    title :索引页的标题: Upload Avatar

  • avatar_field: The name of the input field for our avatar photo. We will be getting this from process.env.AVATAR_FIELD

    avatar_field :我们的头像照片的输入字段的名称。 我们将从process.env.AVATAR_FIELD获取此信息

Modify the GET / route as follows:

修改GET /路由,如下所示:

routes/index.js
路线/index.js
router.get('/', function(req, res, next) {
res.render('index', { title: 'Upload Avatar', avatar_field: process.env.AVATAR_FIELD });
});

第3步-准备视图 (Step 3 — Preparing the View)

Let’s begin by creating the basic markup for our photo upload form by modifying the views/index.ejs file. For the sake of simplicity we will add the styles directly on our view just to give it a slightly nice look. See the following code for the markup of our page.

首先,通过修改views/index.ejs文件为照片上传表单创建基本标记。 为了简单起见,我们将直接在视图中添加样式,以使其外观稍微好看。 请参阅以下代码以获取我们的页面标记。

views/index.ejs
views / index.ejs
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= title %></title>
<style type="text/css">
* {
font: 600 16px system-ui, sans-serif;
}
form {
width: 320px;
margin: 50px auto;
text-align: center;
}
form > legend {
font-size: 36px;
color: #3c5b6d;
padding: 150px 0 20px;
}
form > input[type=file], form > input[type=file]:before {
display: block;
width: 240px;
height: 50px;
margin: 0 auto;
line-height: 50px;
text-align: center;
cursor: pointer;
}
form > input[type=file] {
position: relative;
}
form > input[type=file]:before {
content: 'Choose a Photo';
position: absolute;
top: -2px;
left: -2px;
color: #3c5b6d;
font-size: 18px;
background: #fff;
border-radius: 3px;
border: 2px solid #3c5b6d;
}
form > button[type=submit] {
border-radius: 3px;
font-size: 18px;
display: block;
border: none;
color: #fff;
cursor: pointer;
background: #2a76cd;
width: 240px;
margin: 20px auto;
padding: 15px 20px;
}
</style>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<legend>Upload Avatar</legend>
<input type="file" name="<%= avatar_field %>">
<button type="submit" class="btn btn-primary">Upload</button>
</form>
</body>
</html>

Notice how we have used our local variables on our view to set the title and the name of the avatar input field. You will notice that we are using enctype="multipart/form-data" on our form since we will be uploading a file. You will also see that we have set the form to make a POST request to the /upload route (we will implement later) on submission.

注意,我们如何在视图上使用局部变量来设置头像输入字段的标题和名称。 您会注意到我们在enctype="multipart/form-data"上使用enctype="multipart/form-data" ,因为我们将上传文件。 您还将看到,我们已经设置了表单,以便在提交时向/upload路由发出POST请求(稍后将实现)。

Now let’s start the app for the first time using npm start.

现在,让我们使用npm start第一次启动应用npm start

  • npm start

    npm开始

If you have been following correctly everything should run without errors. Just visit localhost:3000 on your browser. The page should look like the following screenshot:

如果您已正确遵循所有内容,则所有内容均应正常运行。 只需在浏览器上访问localhost:3000 。 该页面应类似于以下屏幕截图:

步骤4 —创建Multer存储引擎 (Step 4 — Creating the Multer Storage Engine)

So far, trying to upload a photo through our form will result in an error because we’ve not created the handler for the upload request. We are going to implement the /upload route to actually handle the upload and we will be using the Multer package for that. If you are not already familiar with the Multer package you can check the Multer package on Github.

到目前为止,尝试通过我们的表单上传照片将导致错误,因为我们尚未为上传请求创建处理程序。 我们将实现/upload路由以实际处理上传,为此我们将使用Multer包。 如果您还不熟悉Multer软件包,可以在Github上检查Multer软件包 。

We will have to create a custom storage engine to use with Multer. Let’s create a new folder in our project root named helpers and create a new file AvatarStorage.js inside it for our custom storage engine. The file should contain the following blueprint code snippet:

我们将必须创建一个自定义存储引擎以用于Multer。 让我们在项目根目录中创建一个名为helpers的新文件夹,并在其中为自定义存储引擎创建一个新文件AvatarStorage.js 。 该文件应包含以下蓝图代码片段:

helpers/AvatarStorage.js
助手/AvatarStorage.js
// Load dependencies
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var Jimp = require('jimp');
var crypto = require('crypto');
var mkdirp = require('mkdirp');
var concat = require('concat-stream');
var streamifier = require('streamifier');

// Configure UPLOAD_PATH
// process.env.AVATAR_STORAGE contains uploads/avatars
var UPLOAD_PATH = path.resolve(__dirname, '..', process.env.AVATAR_STORAGE);

// create a multer storage engine
var AvatarStorage = function(options) {

// this serves as a constructor
function AvatarStorage(opts) {}

// this generates a random cryptographic filename
AvatarStorage.prototype._generateRandomFilename = function() {}

// this creates a Writable stream for a filepath
AvatarStorage.prototype._createOutputStream = function(filepath, cb) {}

// this processes the Jimp image buffer
AvatarStorage.prototype._processImage = function(image, cb) {}

// multer requires this for handling the uploaded file
AvatarStorage.prototype._handleFile = function(req, file, cb) {}

// multer requires this for destroying file
AvatarStorage.prototype._removeFile = function(req, file, cb) {}

// create a new instance with the passed options and return it
return new AvatarStorage(options);

};

// export the storage engine
module.exports = AvatarStorage;

Let’s begin to add the implementations for the listed functions in our storage engine. We will begin with the constructor function.

让我们开始在存储引擎中添加列出功能的实现。 我们将从构造函数开始。

// this serves as a constructor
function AvatarStorage(opts) {

var baseUrl = process.env.AVATAR_BASE_URL;

var allowedStorageSystems = ['local'];
var allowedOutputFormats = ['jpg', 'png'];

// fallback for the options
var defaultOptions = {
storage: 'local',
output: 'png',
greyscale: false,
quality: 70,
square: true,
threshold: 500,
responsive: false,
};

// extend default options with passed options
var 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);
}

}

Here, we defined our constructor function to accept a couple of options. We also added some default (fallback) values for these options in case they are not provided or they are invalid. You can tweak this to contain more options depending on what you want, but for this tutorial we will stick with the following options for our storage engine.

在这里,我们定义了构造函数以接受几个选项。 我们还为这些选项添加了一些默认(备用)值,以防它们未提供或无效。 您可以根据需要进行调整,以包含更多选项,但是在本教程中,我们将为存储引擎坚持以下选项。

  • storage: The storage filesystem. Only allowed value is 'local' for local filesystem. Defaults to 'local'. You can implement other storage filesystems (like Amazon S3) if you wish.

    storage :存储文件系统。 对于本地文件系统,仅允许的值为'local' 。 默认为'local' 。 您可以根据需要实现其他存储文件系统(例如Amazon S3 )。

  • output: The image output format. Can be 'jpg' or 'png'. Defaults to 'png'.

    output :图像输出格式。 可以是'jpg''png' 。 默认为'png'

  • greyscale: If set to true, the output image will be greyscale. Defaults to false.

    灰度 :如果设置为true ,则输出图像将为灰度。 默认为false

  • quality: A number between 0: 100 that determines the quality of the output image. Defaults to 70.

    quality :0:100之间的一个数字,用于确定输出图像的质量。 默认为70

  • square: If set to true, the image will be cropped to a square. Defaults to false.

    square :如果设置为true ,则图像将被裁剪为正方形。 默认为false

  • threshold: A number that restricts the smallest dimension (in px) of the output image. The default value is 500. If the smallest dimension of the image exceeds this number, the image is resized so that the smallest dimension is equal to the threshold.

    threshold :一个数字,用于限制输出图像的最小尺寸(以px )。 默认值为500 。 如果图像的最小尺寸超过此数字,则将调整图像大小,以使最小尺寸等于阈值。

  • responsive: If set to true, three output images of different sizes (lg, md and sm) will be created and stored in their respective folders. Defaults to false.

    响应 :如果设置为true ,将创建三个不同大小( lgmdsm )的输出图像并将其存储在各自的文件夹中。 默认为false

Let’s implement the methods for creating the random filenames and the output stream for writing to the files:

让我们实现用于创建随机文件名的方法以及用于写入文件的输出流:

// this generates a random cryptographic filename
AvatarStorage.prototype._generateRandomFilename = function() {
// create pseudo random bytes
var bytes = crypto.pseudoRandomBytes(32);

// create the md5 hash of the random bytes
var 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
AvatarStorage.prototype._createOutputStream = function(filepath, cb) {

// create a reference for this to use in local functions
var that = this;

// create a writable stream from the filepath
var 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;
};

Here, we use crypto to create a random md5 hash to use as filename and appended the output from the options as the file extension. We also defined our helper method to create writable stream from the given filepath and then return the stream. Notice that a callback function is required, since we are using it on the stream event handlers.

在这里,我们使用加密算法创建一个随机的md5哈希值作为文件名,并将选项的输出附加为文件扩展名。 我们还定义了辅助方法,以根据给定的文件路径创建可写流,然后返回该流。 注意,因为我们在流事件处理程序上使用了回调函数,所以它是必需的。

Next we will implement the _processImage() method that does the actual image processing. Here is the implementation:

接下来,我们将实现_processImage()方法来执行实际的图像处理。 这是实现:

// this processes the Jimp image buffer
AvatarStorage.prototype._processImage = function(image, cb) {

// create a reference for this to use in local functions
var that = this;

var batch = [];

// the responsive sizes
var sizes = ['lg', 'md', 'sm'];

var filename = this._generateRandomFilename();

var mime = Jimp.MIME_PNG;

// create a clone of the Jimp image
var clone = image.clone();

// fetch the Jimp image dimensions
var width = clone.bitmap.width;
var height = clone.bitmap.height;
var square = Math.min(width, height);
var 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) {

var outputStream;

var image = null;
var 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: 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);
}
});
});

};

A lot is going on in this method but here is a summary of what it is doing:

此方法正在进行很多工作,但是这里是其工作摘要:

  • Generates a random filename, resolves the Jimp output image mime type and gets the image dimensions.

    生成随机文件名,解析Jimp输出图像的mime类型并获取图像尺寸。
  • Resize the image if required, based on the threshold requirements to ensure that the smallest dimension does not exceed the threshold.

    如果需要,请根据阈值要求调整图像大小,以确保最小尺寸不超过阈值。
  • Crop the image to a square if enabled in the options.

    如果在选项中启用了图像,则将图像裁剪为正方形。
  • Convert the image to greyscale if enabled in the options.

    如果在选项中启用,则将图像转换为灰度。
  • Set the image output quality from the options.

    从选项设置图像输出质量。
  • If responsive is enabled, the image is cloned and scaled for each of the responsive sizes (lg, md and sm) and then an output stream is created using the _createOutputStream() method for each image file of the respective sizes. The filename for each size takes the format [random_filename_hash]_[size].[output_extension]. Then the image clone and the stream are put in a batch for processing.

    如果启用了响应式,则针对每个响应式大小( lgmdsm )克隆和缩放图像,然后使用_createOutputStream()方法为各个大小的每个图像文件创建输出流。 每种大小的文件名采用[random_filename_hash]_[size].[output_extension]格式。 然后将图像克隆和流放入一批中进行处理。

  • If responsive is disabled, then only the current image and an output stream for it is put in a batch for processing.

    如果禁用了响应,则仅将当前图像及其输出流放入批处理中。
  • Finally, each item in the batch is processed by converting the Jimp image buffer into a readable stream using streamifier and then piping the readable stream to the output stream.

    最后,通过使用流化器将Jimp图像缓冲区转换为可读流,然后将可读流传递到输出流,来处理批处理中的每个项目。

Now we will implement the remaining methods and we will be done with our storage engine.

现在,我们将实现其余方法,并完成存储引擎的工作。

// multer requires this for handling the uploaded file
AvatarStorage.prototype._handleFile = function(req, file, cb) {

// create a reference for this to use in local functions
var 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
var 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
AvatarStorage.prototype._removeFile = function(req, file, cb) {

var matches, pathsplit;
var filename = file.filename;
var _path = path.join(this.uploadPath, filename);
var 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);
});

};

Our storage engine is now ready for use with Multer.

我们的存储引擎现在可以与Multer一起使用了。

第5步-实现POST /upload路线 (Step 5 — Implementing the POST /upload Route)

Before we define the route, we will need to setup Multer for use in our route. Let’s go ahead to edit the routes/index.js file to add the following:

在定义路线之前,我们将需要设置用于该路线的Multer。 让我们继续编辑routes/index.js文件以添加以下内容:

routes/index.js
路线/index.js
var express = require('express');
var router = express.Router();

/**
 * CODE ADDITION
 * 
 * The following code is added to import additional dependencies
 * and setup Multer for use with the /upload route.
 */

// import multer and the AvatarStorage engine
var _ = require('lodash');
var path = require('path');
var multer = require('multer');
var AvatarStorage = require('../helpers/AvatarStorage');

// setup a new instance of the AvatarStorage engine 
var storage = AvatarStorage({
square: true,
responsive: true,
greyscale: true,
quality: 90
});

var limits = {
files: 1, // allow only 1 file per request
fileSize: 1024 * 1024, // 1 MB (max file size)
};

var fileFilter = function(req, file, cb) {
// supported image file mimetypes
var 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
var upload = multer({
storage: storage,
limits: limits,
fileFilter: fileFilter
});

/* CODE ADDITION ENDS HERE */

Here, we are enabling square cropping, responsive images and setting the threshold for our storage engine. We also add limits to our Multer configuration to ensure that the maximum file size is 1 MB and to ensure that non-image files are not uploaded.

在这里,我们将启用方形裁剪,响应图像并设置存储引擎的阈值。 我们还对Multer配置增加了限制,以确保最大文件大小为1 MB并确保未上传非图像文件。

Now let’s add the POST /upload route as follows:

现在让我们添加POST /upload路由,如下所示:

/* routes/index.js */

/**
 * CODE ADDITION
 * 
 * The following code is added to configure the POST /upload route
 * to upload files using the already defined Multer configuration
 */

router.post('/upload', upload.single(process.env.AVATAR_FIELD), function(req, res, next) {

var files;
var file = req.file.filename;
var 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) {
var port = req.app.get('port');
var base = req.protocol + '://' + req.hostname + (port ? ':' + port : '');
var url = path.join(req.file.baseUrl, file).replace(/[\\\/]+/g, '/').replace(/^[\/]+/g, '');

return (req.file.storage == 'local' ? base : '') + '/' + url;
});

res.json({
images: files
});

});

/* CODE ADDITION ENDS HERE */

Notice how we passed the Multer upload middleware before our route handler. The single() method allows us to upload only one file that will be stored in req.file. It takes as first parameter, the name of the file input field which we access from process.env.AVATAR_FIELD.

注意我们如何在路由处理程序之前传递Multer上传中间件。 single()方法仅允许我们上传一个文件,该文件将存储在req.file 。 它以第一个参数为参数,它是我们从process.env.AVATAR_FIELD访问的文件输入字段的名称。

Now let’s start the app again using npm start.

现在,让我们使用npm start再次启动应用npm start

  • npm start

    npm开始

visit localhost:3000 on your browser and try to upload a photo. Here is a sample screenshot I got from testing the upload route on Postman using our current configuration options:

在浏览器上访问localhost:3000并尝试上传照片。 这是我使用当前配置选项在Postman上测试上传路由时获得的示例屏幕截图:

You can tweak the configuration options of the storage engine in our Multer setup to get different results.

您可以在我们的Multer设置中调整存储引擎的配置选项,以获得不同的结果。

结论 (Conclusion)

In this tutorial, we have been able to create a custom storage engine for use with Multer which manipulates uploaded images using Jimp and then writes them to storage. For a complete code sample of this tutorial, checkout the advanced-multer-node-sourcecode repository on Github.

在本教程中,我们已经能够创建与使用自定义存储引擎Multer其操纵上传图片使用服从跳 ,然后将它们写入到存储。 有关本教程的完整代码示例,请在Github上检出advanced-multer-node-sourcecode存储库。

翻译自: https://www.digitalocean.com/community/tutorials/how-to-add-advanced-photo-uploads-in-node-and-express

node和express


http://www.niftyadmin.cn/n/3649000.html

相关文章

Android ListView 数据更新的总结

1、android中ListView滚动刷新 在做ListView加载数据时如果数据量大的话会造成加载时间过长而卡屏&#xff0c;所以为了解决这个问题&#xff0c;查看了SDK&#xff0c; 在OnScrollListener中有两个方法 只要重写这两个方法就可以实现滚动加载&#xff0c;例如&#xff1a; pub…

Android数据存储方式一:sharedprefrence存储,外部存储,内部存储

Android数据存储方式 SharedPrefrence存储 说明 SP存储专门用来存储一些单一的小数据 存储数据的类型: boolean, float, int, long, String 数据保存的路径: /data/data/packageName/shared_prefs/yyy.xml 可以设置数据只能是当前应用读取, 而别的应用不可以 应用卸载时…

Android中保存Activity的状态

1.onSaveInstanceState和onRestoreInstanceState基本作用onSaveInstanceState是用来保存UI状态的&#xff0c;你可以使用它保存你所想保存的东西&#xff0c;在Activity杀死之前&#xff0c;它一般在onStop或者onPause之前触发&#xff0c;onRestoreInstanceState则是在onResum…

如何在React App Server上渲染CSS

Server-side rendering can be challenging due to ambiguity. Open-source tools like Next.js (React) and Nuxt.js(Vue) help streamline the process of rendering your app views to a server. 由于含糊不清&#xff0c;服务器端渲染可能具有挑战性。 诸如Next.js (React…

Android存储方式二:sqlite数据库

说明 应用运行需要保存一系列有一定结构的数据, 比如说公司员工信息 文件类型: .db 数据保存的路径: /data/data/projectPackage/databases/xxx.db 默认情况下其它应用不能访问, 当前应用可以通过ContentProvider提供其它应用操作 应用卸载时会删除此数据 sqlite数据库 …

事件机制分发

相关API 1. MotionEvent : 触屏事件 int ACTION_DOWN0 : 代表down int ACTION_MOVE2 ; 代表move int ACTION_UP1 : 代表up getAction() : 得到事件类型值 getX() : 得到事件发生的x轴坐标(相对于当前视图) getRawX() :得到事件发生的x轴坐标(相对于屏幕左顶点) getY() …

Android TabActivity的生命周期

在TabActivity中&#xff0c;只在第一次进入时走了onCreate()、onStart()、onResume()三个阶段&#xff0c;然后在退出该页面时走了onPause()、onStop()和onDestroy()两个阶段。其他时间无论其中的子Activity如何切换&#xff0c;都不会再进入TabActivity的生命周期。 而子Acti…

three.js使用svg_如何使用SVG和anime.js构建弹性范围输入

three.js使用svg介绍 (Introduction) In HTML5, many new types of input attributes were introduced for the form element, such as color, date, range, and many more. Although functionally these new types of input works, they often do not meet the aesthetic need…