讲师中心 微信公众号
AI工具推荐 视频效率加速

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

梦宇君_9159

梦宇君_9159

发布时间:2025-11-15 15:59:02

|

956人浏览过

|

来源于php中文网

原创

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。

Markdown → 图片卡片
Markdown → 图片卡片

当用户想要将Markdown内容转换为可分享的图片卡片时使用。触发词包括:转图片、做卡片、生成海报、生成分享图、把这个转成图片、export as image

下载

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

// 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 的自定义逻辑之前执行。
  • 静态文件服务: 为了让浏览器能够

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

相关专题

更多
什么是中间件
什么是中间件

中间件是一种软件组件,充当不兼容组件之间的桥梁,提供额外服务,例如集成异构系统、提供常用服务、提高应用程序性能,以及简化应用程序开发。想了解更多中间件的相关内容,可以阅读本专题下面的文章。

589

2024.05.11

Golang 中间件开发与微服务架构
Golang 中间件开发与微服务架构

本专题系统讲解 Golang 在微服务架构中的中间件开发,包括日志处理、限流与熔断、认证与授权、服务监控、API 网关设计等常见中间件功能的实现。通过实战项目,帮助开发者理解如何使用 Go 编写高效、可扩展的中间件组件,并在微服务环境中进行灵活部署与管理。

584

2025.12.18

ThinkPHP中间件机制与请求拦截处理实践
ThinkPHP中间件机制与请求拦截处理实践

本专题围绕 ThinkPHP 中间件体系展开,深入讲解中间件的定义、注册与执行流程。内容包括全局中间件与路由中间件的区别、请求前后处理逻辑、自定义中间件开发以及权限验证与日志处理应用。通过实际案例,帮助开发者掌握中间件在项目中的核心作用与最佳实践。

398

2026.03.31

Node.js后端开发与Express框架实践
Node.js后端开发与Express框架实践

本专题针对初中级 Node.js 开发者,系统讲解如何使用 Express 框架搭建高性能后端服务。内容包括路由设计、中间件开发、数据库集成、API 安全与异常处理,以及 RESTful API 的设计与优化。通过实际项目演示,帮助开发者快速掌握 Node.js 后端开发流程。

741

2026.02.10

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

529

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1658

2024.03.01

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

3296

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

286

2025.12.06

PixTV AI视频生成与无限画布创作
PixTV AI视频生成与无限画布创作

PixTV专题整理AI视频与视觉内容创作相关功能使用教程,涵盖AI生图、视频生成、无限画布、多模型创作、素材管理、声音音乐及视频剪辑等功能,帮助用户快速掌握PixTV从创意到成片的完整制作方法。

0

2026.09.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
GDB 17.2 官方文档集合
GDB 17.2 官方文档集合

共0课时 | 0人学习

Bootstrap 入门安装配置
Bootstrap 入门安装配置

共0课时 | 0人学习

38+ PhpStorm 提示和技巧
38+ PhpStorm 提示和技巧

共1课时 | 214人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn