# 文件上传

URL: https://caijiao.org/express/chapter5/3-file-upload
Source: docs/express/chapter5/3-file-upload.md
Description: 本章节介绍如何在 Express 中处理文件上传，讲解如何使用 'multer' 中间件来接收并保存上传的文件。

文件上传是 Web 应用中常见的功能之一。Express 不自带处理文件上传的功能，但我们可以通过第三方中间件 `multer` 来轻松处理文件上传。`multer` 支持多种上传方式，包括单个文件上传、多个文件上传和文件大小限制等功能。

## 安装 `multer`

首先，我们需要安装 `multer`，它是 Express 的一个中间件，用于处理 `multipart/form-data` 格式的文件上传。

```bash
npm install multer
````

## 配置 `multer`

`multer` 中间件需要一个存储引擎，用于决定文件存储的位置和文件名。`multer` 提供了两种存储方式：

* **硬盘存储**：将上传的文件存储到指定的文件夹。
* **内存存储**：将文件存储在内存中（适用于小文件的上传）。

### 1. 硬盘存储

如果我们希望将上传的文件存储到磁盘中，可以使用硬盘存储。

#### 1.1 配置硬盘存储

```js
const multer = require('multer');
const path = require('path');

// 配置存储引擎，指定文件保存路径和文件名
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');  // 文件存储的文件夹
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); // 为文件生成唯一名称
    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
  }
});

// 创建 multer 实例，指定存储引擎
const upload = multer({ storage: storage });
```

#### 1.2 处理文件上传

```js
// 上传单个文件
app.post('/upload', upload.single('file'), (req, res) => {
  res.send(`文件上传成功！文件名：${req.file.filename}`);
});
```

`upload.single('file')` 用于上传单个文件，`'file'` 是表单中文件上传字段的名称。

#### 1.3 测试上传表单

创建一个 HTML 表单，允许用户选择文件并上传：

```html
<form action="/upload" method="POST" enctype="multipart/form-data">
  <label for="file">选择文件：</label>
  <input type="file" id="file" name="file" required />
  <br />
  <button type="submit">上传</button>
</form>
```

### 2. 内存存储

内存存储将上传的文件存储在内存中，而不是磁盘。适用于处理较小文件，且无需在服务器上长期存储的场景。

#### 2.1 配置内存存储

```js
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
```

#### 2.2 处理文件上传

```js
app.post('/upload', upload.single('file'), (req, res) => {
  console.log('文件内容：', req.file.buffer);  // req.file.buffer 存储文件的二进制内容
  res.send('文件上传成功！');
});
```

#### 2.3 测试上传表单

HTML 表单与之前相同，用户上传文件时，服务器会将文件存储在内存中。

### 3. 上传多个文件

如果我们想同时上传多个文件，可以使用 `upload.array()` 方法。

#### 3.1 配置上传多个文件

```js
app.post('/uploads', upload.array('files', 3), (req, res) => {
  // req.files 是上传的多个文件数组
  console.log(req.files);
  res.send(`成功上传了 ${req.files.length} 个文件`);
});
```

`upload.array('files', 3)` 用于上传最多 3 个文件，其中 `'files'` 是表单中文件字段的名称。

#### 3.2 测试上传多个文件的表单

```html
<form action="/uploads" method="POST" enctype="multipart/form-data">
  <label for="files">选择文件：</label>
  <input type="file" id="files" name="files" multiple required />
  <br />
  <button type="submit">上传</button>
</form>
```

### 4. 限制文件大小

我们可以设置文件的大小限制，防止上传过大的文件。

#### 4.1 设置文件大小限制

```js
const upload = multer({
  storage: storage,
  limits: { fileSize: 2 * 1024 * 1024 }  // 限制文件大小为 2MB
});
```

如果文件大小超过限制，`multer` 会自动返回一个错误，表示文件太大。

### 5. 文件类型验证

为了确保上传的文件是用户期望的类型（例如，只允许上传图片），我们可以设置文件类型验证。

#### 5.1 设置文件类型验证

```js
const upload = multer({
  storage: storage,
  fileFilter: (req, file, cb) => {
    const fileTypes = /jpeg|jpg|png|gif/;
    const extname = fileTypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = fileTypes.test(file.mimetype);

    if (extname && mimetype) {
      return cb(null, true);  // 文件通过验证
    } else {
      cb(new Error('只允许上传图片文件！'), false);  // 文件不通过验证
    }
  }
});
```

### 6. 错误处理

如果文件上传过程中出现错误，例如文件类型不匹配或文件过大，`multer` 会返回一个错误。我们可以捕获并处理这些错误。

#### 6.1 错误处理中间件

```js
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    return res.status(400).send(`上传错误：${err.message}`);
  } else if (err) {
    return res.status(500).send('服务器错误');
  }
  next();
});
```

## 总结

在 Express 中处理文件上传非常简单，主要通过 `multer` 中间件来实现。我们可以根据需求配置不同的存储方式、文件类型验证和文件大小限制等。文件上传处理的核心步骤包括：

* 配置 `multer` 存储引擎（硬盘存储或内存存储）。
* 使用 `upload.single()` 或 `upload.array()` 方法处理单个文件或多个文件上传。
* 可选择设置文件类型和大小限制。

通过这些配置，我们能够轻松地处理各种文件上传需求。
