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) // 返回所有文件的名字
  });
});
相关推荐
大模型玩家七七43 分钟前
基于语义切分 vs 基于结构切分的实际差异
java·开发语言·数据库·安全·batch
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
Hello.Reader8 小时前
Flink ZooKeeper HA 实战原理、必配项、Kerberos、安全与稳定性调优
安全·zookeeper·flink
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
智驱力人工智能9 小时前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端