首页 > web前端 > js教程 > 正文

Node.js 中使用 Multer 和 MongoDB 实现图片上传与管理

花韻仙語
发布: 2025-11-15 15:59:02
原创
905人浏览过

Node.js 中使用 Multer 和 MongoDB 实现图片上传与管理

本教程详细介绍了如何在 node.js express 应用中利用 multer 中间件处理图片上传,并将上传后的图片路径存储到 mongodb 数据库。文章将通过具体的代码示例,演示 multer 的配置、表单处理以及如何确保文件信息正确地保存至数据库,解决常见的 `req.file.mv` 错误,确保图片上传流程的完整性与数据持久化。

1. 前言

在构建基于 Node.js 的 Web 应用程序时,文件上传是一个常见且重要的功能,尤其是在博客或内容管理系统中,用户通常需要上传图片来丰富文章内容。本教程将聚焦于如何在 Node.js Express 环境下,结合 Multer 中间件处理图片上传,并将图片文件的路径有效地存储到 MongoDB 数据库中,以实现完整的图片管理功能。我们将涵盖前端表单设置、Multer 配置、后端路由处理以及数据模型定义等关键环节。

2. 前端表单准备

要实现文件上传,前端的 HTML 表单需要进行特殊设置。最关键的是将表单的 enctype 属性设置为 multipart/form-data,并使用 type="file" 的 input 元素来选择文件。

以下是一个典型的文件上传表单片段:

<!-- new.ejs 中包含的 _form_fields.ejs 片段 -->
<form action="/articles" method="POST" enctype="multipart/form-data">
    <div class="form-group">
      <label for="title">标题</label>
      <input required value="<%= article.title %>" type="text" name="title" id="title" class="form-control">
    </div>

    <div class="form-group">
      <label for="description">描述</label>
      <textarea name="description" id="description" class="form-control"><%= article.description %></textarea>
    </div>

    <div class="form-group">
      <label for="markdown">内容</label>
      <textarea required name="markdown" id="markdown" class="form-control"><%= article.markdown %></textarea>
    </div>

    <div class="form-group">
      <label for="image">图片</label>
      <input type="file" name="image" id="image" class="form-control">
    </div>

    <a href="/" class="btn btn-secondary">取消</a>
    <button type="submit" class="btn btn-primary">保存</button>
</form>
登录后复制

关键点:

  • enctype="multipart/form-data":这是浏览器发送文件数据到服务器所必需的编码类型。
  • <input type="file" name="image" id="image" class="form-control">:name="image" 是后端 Multer 中间件用来识别文件字段的关键。

3. Multer 配置与文件存储

Multer 是一个 Node.js 中间件,用于处理 multipart/form-data 类型的表单数据,主要用于文件上传。我们需要配置 Multer 来指定文件的存储位置和命名规则。

// routes/articles.js
const express = require('express');
const Article = require('./../models/article');
const router = express.Router();
const multer = require('multer');
const path = require('path'); // 用于处理文件路径

// 配置 Multer 的存储引擎
const storage = multer.diskStorage({
  // destination 决定文件存储的目录
  destination: (req, file, cb) => {
    // cb(error, destination)
    cb(null, 'public/uploads/'); // 将文件存储在项目根目录下的 public/uploads 文件夹
  },
  // filename 决定文件中存储的文件名
  filename: (req, file, cb) => {
    // cb(error, filename)
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); // 生成唯一后缀
    const extension = path.extname(file.originalname); // 获取原始文件的扩展名
    cb(null, uniqueSuffix + extension); // 组合生成唯一的文件名
  },
});

// 初始化 Multer 实例
const upload = multer({ storage: storage });
登录后复制

配置说明:

  • multer.diskStorage():指定将文件存储到磁盘上。
  • destination:一个函数,用于设置文件存储的目录。cb(null, 'public/uploads/') 表示文件将保存到 public/uploads/ 目录。
  • filename:一个函数,用于设置文件中存储的文件名。这里我们生成了一个基于时间戳和随机数的唯一文件名,并保留了原始文件的扩展名,以避免文件重名冲突。
  • upload = multer({ storage: storage }):创建 Multer 实例,并传入配置好的存储引擎。

4. 后端路由与图片处理(问题分析)

在将 Multer 集成到 Express 路由时,一个常见的错误是未能正确地将 Multer 中间件插入到请求处理链中。这会导致 req.file 对象为空,或者在尝试访问其属性时出现 TypeError。

Cutout老照片上色
Cutout老照片上色

Cutout.Pro推出的黑白图片上色

Cutout老照片上色 20
查看详情 Cutout老照片上色

以下是原始代码中存在问题的路由处理方式:

// routes/articles.js (原始代码片段)

// ... 其他代码 ...

const saveArticleAndRedirect = (path) => {
  return async (req, res, next) => {
    let article = req.article;
    article.title = req.body.title;
    article.description = req.body.description;
    article.markdown = req.body.markdown;

    try {
      if (req.file) { // 问题所在:此时 req.file 可能未被 Multer 正确填充
        // 错误的图片处理逻辑:Multer 已经将文件保存到磁盘,不提供 .mv 方法
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
        const extension = extname(req.file.originalname);
        const filename = uniqueSuffix + extension;
        const imagePath = pathJoin('public/uploads/', filename);
        req.file.mv(imagePath, (err) => { // TypeError: req.file.mv is not a function
          if (err) {
            console.error(err);
            throw new Error('Failed to upload the image file');
          }
        });
        article.image = imagePath;
      }

      article = await article.save();
      res.redirect(`/articles/${article.slug}`);
    } catch (e) {
      console.error(e);
      res.render(`articles/${path}`, { article: article, error: e.message });
    }
  };
};

router.post('/', async (req, res, next) => {
    if (!req.file) {
      console.log('No file received!'); // 经常会打印此信息
    } else {
      console.log(req.file);
    }
    req.article = new Article(); // 创建一个新的文章实例
    next();
  }, saveArticleAndRedirect('new'));
登录后复制

问题分析:

  1. TypeError: req.file.mv is not a function: Multer 中间件在处理文件上传后,会将文件元数据(如 filename, path, size 等)添加到 req.file 对象中。但是,req.file 对象本身并没有 mv 方法。mv 方法通常是 express-fileupload 等其他文件上传库提供的功能,而不是 Multer 的标准 API。Multer 在 diskStorage 配置中已经将文件保存到指定目录了,无需手动再次移动。
  2. No file received!: 这表明在 router.post 的第一个处理函数中,req.file 对象为空。根本原因是 Multer 中间件 (upload.single('image')) 没有在请求到达这个处理函数之前被正确执行。Express 的中间件是按顺序执行的,如果 Multer 中间件没有在解析文件之前运行,那么后续的路由处理函数就无法访问到 req.file。

5. 解决方案:正确集成 Multer 中间件

解决上述问题的关键在于将 upload.single('image') 作为中间件,正确地插入到路由处理函数之前。

// routes/articles.js (修正后的代码片段)

// ... Multer 配置 (如上所示) ...

// 修正后的路由处理
router.post('/', upload.single('image'), async (req, res, next) => {
    if (!req.file) {
      console.log('No file received!');
    } else {
      console.log('File received:', req.file); // 此时 req.file 会包含正确的文件信息
      /*
        req.file 示例:
        {
          fieldname: 'image',
          originalname: 'example.jpg',
          encoding: '7bit',
          mimetype: 'image/jpeg',
          destination: 'public/uploads/',
          filename: '1686845869591-191454535.jpg',
          path: 'public\uploads\1686845869591-191454535.jpg', // 在 Windows 上可能是反斜杠
          size: 128712
        }
      */
    }
    req.article = new Article(); // 创建一个新的文章实例
    next();
  }, saveArticleAndRedirect('new'));
登录后复制

解释: 通过将 upload.single('image') 作为 router.post 的第二个参数(在路径 '/' 之后,但在实际的异步处理函数之前),我们确保了 Multer 中间件会在处理请求体和文件之前运行。upload.single('image') 会识别表单中 name="image" 的文件字段,将文件保存到 public/uploads/ 目录,并将文件的元数据填充到 req.file 对象中,然后才将控制权传递给下一个中间件或路由处理函数。

6. 更新文章保存逻辑

Multer 正确处理文件后,我们可以在 saveArticleAndRedirect 函数中安全地访问 req.file 对象,并将其相关信息(如文件名或相对路径)存储到 MongoDB 中。

// routes/articles.js (修正后的 saveArticleAndRedirect 函数)

const saveArticleAndRedirect = (path) => {
  return async (req, res, next) => {
    let article = req.article; // req.article 在之前的中间件中被初始化
    article.title = req.body.title;
    article.description = req.body.description;
    article.markdown = req.body.markdown;

    try {
      if (req.file) {
        // Multer 已经将文件保存到 destination 目录
        // 我们需要将文件路径存储到数据库,以便后续访问
        // 建议存储相对路径,便于前端通过静态文件服务访问
        const imageRelativePath = '/uploads/' + req.file.filename; 
        article.image = imageRelativePath; // 将图片相对路径保存到文章模型
      }

      article = await article.save(); // 保存文章到数据库
      res.redirect(`/articles/${article.slug}`); // 重定向到新文章详情页
    } catch (e) {
      console.error('Error saving article:', e);
      // 如果保存失败,重新渲染表单并显示错误信息
      res.render(`articles/${path}`, { article: article, error: e.message });
    }
  };
};
登录后复制

关键点:

  • 移除 req.file.mv: 由于 Multer 已经将文件保存到磁盘,不再需要手动移动文件。
  • 存储相对路径: article.image = '/uploads/' + req.file.filename; 这一行将图片的相对路径存储到数据库。这样做的好处是,当你在前端需要显示图片时,可以直接使用这个路径(例如 <img src="/uploads/your-image.jpg">),前提是你的 Express 应用配置了静态文件服务。

7. MongoDB 存储模型

为了在 MongoDB 中存储图片路径,Article 模型的 Schema 需要包含一个用于存储图片路径的字段。

// models/article.js
const mongoose = require('mongoose')
const marked = require('marked')
const slugify = require('slugify')
const createDomPurify = require('dompurify')
const { JSDOM } = require('jsdom')
const dompurify = createDomPurify(new JSDOM().window)

const articleSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  description: {
    type: String
  },
  markdown: {
    type: String,
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  slug: {
    type: String,
    required: true,
    unique: true
  },
  sanitizedHtml: {
    type: String,
    required: true
  },
  image: {
    type: String // 新增字段,用于存储图片文件的相对路径
  }
})

// ... 其他 pre 钩子和模块导出 ...

module.exports = mongoose.model('Article', articleSchema)
登录后复制

8. 总结与最佳实践

  • 中间件顺序至关重要: 确保 Multer 中间件 (upload.single('fieldname') 或其他 Multer 方法) 始终在任何需要访问 req.file 或 req.files 的自定义逻辑之前执行。
  • 静态文件服务: 为了让浏览器能够

以上就是Node.js 中使用 Multer 和 MongoDB 实现图片上传与管理的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号