找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2499

积分

0

好友

359

主题
发表于 11 小时前 | 查看: 3| 回复: 0

在 Web 项目中,文件上传 是一个高频且核心的需求,例如:

  • 用户头像、内容配图上传
  • PDF / Word / 附件上传
  • 需要为 Web、App、小程序等多端提供统一的文件上传接口

如果每个项目都从零开始设计一套上传方案,不仅效率低下,还可能因为考虑不周而引入安全隐患。

本文将基于 Node.js 生态,使用 Express + Multer,手把手搭建一套前端友好、可长期复用、可一键部署的文件上传服务,并提供 Web(Fetch API)与 uni-app 的完整对接示例。

一、Multer 中间件:文件上传的专用处理器

Multer 是一个用于处理 multipart/form-data 格式数据的 Node.js 中间件。在基于 Express 框架开发时,它是处理文件上传场景的事实标准

它主要解决了三个问题:

  1. 接收并解析 来自浏览器或移动端应用上传的文件流。
  2. 控制存储,包括文件保存的目录和命名规则,防止文件覆盖。
  3. 前置校验,在上传阶段就能完成文件大小、类型等基础验证。

简单来说:

Multer = Express 生态中专为「文件上传」任务构建的基础设施层。

二、Multer 的核心使用方式

1. 配置存储引擎:决定文件存哪、叫什么

使用 diskStorage 方法定义文件的存储规则。

const storage = multer.diskStorage({
  destination: './uploads/',
  filename: function (req, file, cb) {
    cb(
      null,
      file.fieldname +
        '-' +
        Date.now() +
        path.extname(file.originalname)
    )
  }
})

参数说明

  • destination:文件保存的目录。建议统一使用 uploads 这样的文件夹,便于管理。
  • filename:自定义文件名。这里采用“字段名-时间戳.后缀”的格式,能有效避免因同名导致的文件覆盖。
  • path.extname:用于提取并保留原始文件的后缀名。

2. 创建上传中间件:集成校验规则

基于配置好的存储引擎,创建最终的上传处理中间件。

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 10000000 // 文件大小限制为 10MB
  },
  fileFilter: function (req, file, cb) {
    checkFileType(file, cb)
  }
}).single('file') // 'file' 需与前端上传字段名保持一致

这一层配置至关重要,它负责:

  • 文件大小控制:通过 limits 防止过大文件耗尽服务器资源。
  • 文件类型白名单:通过 fileFilter 只允许指定类型的文件上传,提升安全性。
  • 字段名匹配single('file') 表示处理单个文件,且字段名为 file

三、一个完整可用的 Multer 中间件代码

将上述配置整合,得到一个可直接用于生产环境的中间件模块。

const multer = require('multer')
const path = require('path')

// 设置存储引擎
const storage = multer.diskStorage({
  destination: './uploads/',
  filename: function (req, file, cb) {
    cb(
      null,
      file.fieldname +
        '-' +
        Date.now() +
        path.extname(file.originalname)
    )
  }
})

// 初始化 upload 中间件
const upload = multer({
  storage: storage,
  limits: { fileSize: 10000000 }, // 10MB
  fileFilter: function (req, file, cb) {
    checkFileType(file, cb)
  }
}).single('file')

// 文件类型校验函数
function checkFileType(file, cb) {
  const filetypes = /jpeg|jpg|png|gif|pdf|doc|docx/
  const extname = filetypes.test(
    path.extname(file.originalname).toLowerCase()
  )
  const mimetype = filetypes.test(file.mimetype)

  if (mimetype && extname) {
    return cb(null, true)
  } else {
    cb('Error: Images, PDFs, and Documents Only!')
  }
}

module.exports = upload

这个模块导出后,可以在任何路由中引入,用于处理文件上传请求。

四、构建文件上传与删除接口

有了上传中间件,接下来在 Express 路由中创建具体的接口。

1. 文件上传接口

const upload = require('../middleware/upload')
const codes = require('../config/codes')

// @desc    上传文件
// @route   POST /api/files/upload
// @access  Public
exports.uploadFile = (req, res, next) => {
  upload(req, res, (err) => {
    // 处理 Multer 中间件本身的错误(如文件过大、类型不符)
    if (err) {
      const error = new Error(err.message || err)
      error.code = codes.INVALID_PARAMS
      error.isOperational = true
      return next(error)
    }

    // 检查是否有文件被上传
    if (req.file === undefined) {
      const error = new Error('Please select a file to upload.')
      error.code = codes.MISSING_PARAMS
      error.isOperational = true
      return next(error)
    }

    // 构建返回给前端的数据
    const fileData = {
      filename: req.file.filename,
      path: req.file.path,
      size: req.file.size,
      mimetype: req.file.mimetype,
      url: `/uploads/${req.file.filename}`
    }

    res.cc(fileData, 'File uploaded successfully')
  })
}

2. 文件删除接口

一个完整的服务通常也需要删除能力。

const fs = require('fs').promises
const path = require('path')

// @desc    删除文件
// @route   DELETE /api/files/:filename
// @access  Public
exports.deleteFile = async (req, res, next) => {
  const filename = req.params.filename

  // 基础安全校验,防止路径遍历攻击
  if (!filename || filename.includes('..') || filename.includes(path.sep)) {
    const error = new Error('Invalid filename.')
    return next(error)
  }

  const filePath = path.join(__dirname, '..', 'uploads', filename)

  await fs.unlink(filePath)
  res.cc({ filename }, 'File deleted successfully')
}

五、前端(Web)如何对接上传接口?

在 Web 前端,使用经典的 fetch API 配合 FormData 对象即可轻松上传。

HTML 部分

<input type="file" id="fileInput" />
<button onclick="uploadFile()">上传文件</button>
<div id="result"></div>

JavaScript 部分

async function uploadFile() {
  const fileInput = document.getElementById('fileInput')

  if (fileInput.files.length === 0) {
    alert('请先选择文件')
    return
  }

  const file = fileInput.files[0]
  const formData = new FormData()
  formData.append('file', file) // 字段名 ‘file’ 需与后端配置一致

  const response = await fetch('/api/files/upload', {
    method: 'POST',
    body: formData // 无需手动设置 Content-Type,浏览器会自动处理
  })

  const result = await response.json()
  console.log(result)
}

六、uni-app 如何对接上传接口?

在 uni-app 等跨端框架中,可以使用其内置的上传 API。

uni.uploadFile({
  url: '/api/files/upload',
  filePath: tempFilePath, // 例如,从选择文件 API 获取的临时路径
  name: 'file', // 字段名 ‘file’ 需与后端配置一致
  success: (res) => {
    const result = JSON.parse(res.data)
    console.log(result)
  }
})

总结

通过 Express 框架集成 Multer 中间件,我们快速构建了一个功能完备的文件上传服务。它具备文件类型校验、大小限制、安全存储等生产级特性,并提供了清晰的 Web 端与移动端对接示例。

这套方案代码清晰、职责分离,你可以直接将其集成到你的 Node.js 项目中,作为稳定可靠的后端文件处理基础设施。如果想了解更多实战项目架构与开发技巧,欢迎访问 云栈社区 与其他开发者交流讨论。




上一篇:从Datadog迁移至Grafana云:AI时代下供应商锁定的48小时实战复盘
下一篇:C++ inline关键字详解:优化高频小函数性能与避免多重定义
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-16 18:15 , Processed in 0.276047 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表