Vue3+Node.js实现文件上传并发控制与安全防线 进阶篇

上一篇我们实现了最基础的 FormData 上传,体会了前后端的基础联调。但如果在面试中被问到:"如果用户上传了一个 10GB 的文件怎么办?"或者"用户同时选了 100 张图片,浏览器卡死怎么办?"这就需要用到进度监听、双端校验与并发控制。

1 核心概念

文件校验

需要遵循"前端防误操作,后端防恶意攻击"的原则,分为三个层级:

  • 第一层:前端 JS 校验 通过 input 标签属性和 JavaScript 读取 file.size 与 file.type。

  • 第二层:后端请求头校验 使用 Node.js 的 multer 中间件,读取 HTTP 请求头中的 Content-Type 和限制文件流大小。能拦截超大文件,防止服务器内存溢出。

  • 第三层:文件二进制校验 读取文件底层二进制数据的前 8 个字节,与标准文件类型的十六进制码进行比对。这是最高级别的安全校验。无论用户怎么改后缀、改请求头,文件底层的二进制编码是无法伪造的。

并发控制

如果不做控制,直接通过 Promise.all 一次性发送 100 个文件,会导致两个严重问题:

  • 浏览器 TCP 连接限制, Chrome 浏览器规定,对同一个域名最多只能同时维持 6 个网络连接。多出的 94

    个请求会被强制挂起(Pending)。排队时间过长会导致后面的请求直接超时(Timeout)断开。

  • 前端内存占用过高100 个大文件的 FormData 实例会瞬间占满内存,触发浏览器垃圾回收(GC),直接导致页面卡死。

2 前端实现:进度条与取消请求

我们需要使用 Axios/XHR 而不是 fetch, 因为 fetch 具有局限性。fetch API 设计上不支持监听上传进度(只能通过 response.body.getReader() 监听下载进度)。要实现上传进度条,底层必须依赖 XMLHttpRequest 的 upload.onprogress 事件,这也是我们选用 Axios 的原因。

在前端,我们给 Axios 配置 onUploadProgress 来获取进度。同时,利用现代 JS 的 AbortController 来实现"取消上传"功能

ts 复制代码
<script setup lang="ts">
import axios from 'axios'
import { ref } from 'vue'

const selectedFile = ref<File | null>(null)
const message = ref('')
const progress = ref(0)
// 定义中断控制器
let abortController: AbortController | null = null

const uploadFile = async () => {
  if (!selectedFile.value) return

  // 1. 前端基础校验:大小限制 10MB
  if (selectedFile.value.size > 10 * 1024 * 1024) {
    message.value = '文件不能超过 10MB'
    return
  }

  const formData = new FormData()
  formData.append('file', selectedFile.value)
  abortController = new AbortController()

  try {
    message.value = '正在上传...'
    const res = await axios.post('http://localhost:3000/upload', formData, {
      signal: abortController.signal, // 绑定取消信号
      // 监听上传进度
      onUploadProgress: (progressEvent) => {
        if (progressEvent.total) {
          progress.value = Math.round((progressEvent.loaded * 100) / progressEvent.total)
        }
      }
    })
    message.value = `上传成功: ${res.data.filename}`
  } catch (error: any) {
    // 捕获取消操作的特殊错误
    if (axios.isCancel(error)) {
      message.value = '上传已取消'
    } else {
      message.value = error.response?.data?.msg || '上传失败'
    }
  }
}

// 取消上传的触发函数
const cancelUpload = () => {
  if (abortController) abortController.abort()
}
</script>

在 Vue 3 中,不要把 File 对象直接放到 reactive 里,这会导致性能问题。使用 ref 并通过 .value 操作即可。

3 后端实现:多文件与拦截器(实现并发和安全防线)

后端我们继续使用 Multer,但这次升级为 upload.array() 以支持多文件,并加上了 limits 和 fileFilter。这一步体现了后端对内存和存储的保护。

js 复制代码
const multer = require("multer");

// 配置安全防线:文件过滤器
const fileFilter = (req, file, cb) => {
  const allowedTypes = ["image/jpeg", "image/png", "application/pdf"];
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true); // 放行
  } else {
    cb(new Error("仅支持 JPG/PNG/PDF 文件"), false); // 拒绝
  }
};

const upload = multer({ 
  storage: storage,
  fileFilter: fileFilter,
  limits: { 
    fileSize: 10 * 1024 * 1024, // 限制单文件最大 10MB
    files: 5 // 限制单次请求最多 5 个文件
  } 
});

// 使用 upload.array 接收多文件,字段名为 "files"
app.post("/upload", upload.array("files", 5), (req, res) => {
  res.json({
    msg: "上传成功",
    files: req.files.map(f => f.filename) // 返回所有文件的名字
  });
});
相关推荐
wqwqweee2 小时前
Flutter for OpenHarmony 看书管理记录App实战:个人中心实现
开发语言·javascript·python·flutter·harmonyos
智慧化智能化数字化方案2 小时前
安全生产——解读数字政府网络安全运营建设指南【附全文阅读】
网络·安全
pas1362 小时前
36-mini-vue nextTick
前端·javascript·vue.js
VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue教务管理系统(源码+数据库+文档)
vue.js·spring boot·课程设计
梅梅绵绵冰2 小时前
springboot初步1
java·前端·spring boot
星辰_mya2 小时前
nginx之待续-没写完
前端
Irene19913 小时前
Vue3中 <slot >不支持 ref 属性的替代方案
vue.js·ref
独行soc3 小时前
2026年渗透测试面试题总结-10(题目+回答)
android·网络·python·安全·web安全·渗透测试·安全狮
GISer_Jing3 小时前
大语言模型Agent入门指南
前端·数据库·人工智能