前端文件上传实践与后端处理——文件分块上传

news2024/11/16 21:59:12

文件上传是现代Web应用程序中常见的功能之一。在这篇博客中,我们将探讨一个简单但完整的前端文件上传实践,同时提供一个后端示例,演示如何处理上传的文件。我们将使用JavaScript作为前端语言,并结合Node.js作为后端环境。让我们开始吧!

前端文件上传

我们首先关注前端部分。我们将使用HTML、CSS和JavaScript来创建一个简单的文件上传表单,然后通过AJAX将文件传递给后端服务器。这里我们假设你已经具备了基本的前端开发知识。

1. HTML结构

首先,我们创建一个HTML结构,包含一个文件上传输入框、上传按钮和用于显示上传进度和信息的元素:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>文件上传示例</title>
</head>
<body>
  <input type="file" id="videoUploader" accept="video/mp4, video/ogg">
  <button id="uploadBtn">上传</button>
  <p id="uploadInfo"></p>
  <progress id="uploadProgress" value="0" max="100"></progress>
</body>
</html>

2. CSS样式(可选)

你可以根据需要添加一些CSS样式来美化页面。

3. JavaScript逻辑

接下来,我们将使用JavaScript来实现文件上传逻辑。我们从一个配置文件开始,然后实现上传函数和相关的辅助函数:

// config.js
// 导出一些常量,包括上传信息、允许上传的文件类型、块大小和上传接口的URL
// 这些常量将在前端和后端代码中使用
const BASE_URL = 'http://localhost:8000/';

export const UPLOAD_INFO = {
  'NO_FILE': '请先选择文件',
  'INVALID_TYPE': '不支持该类型文件上传',
  'UPLOAD_FAILED': '上传失败',
  'UPLOAD_SUCCESS': '上传成功'
}

export const ALLOWED_TYPE = {
  'video/mp4': 'mp4',
  'video/ogg': 'ogg'
}

export const CHUNK_SIZE = 64 * 1024;

export const API = {
  UPLOAD_VIDEO: BASE_URL + 'upload_video'
}

// index.js
// 在闭包内部,定义一个立即执行的匿名函数,接受document作为参数
((doc) => {
  // 获取页面上的一些元素
  const oProgress = doc.querySelector('#uploadProgress'); // 上传进度条
  const oUploader = doc.querySelector('#videoUploader'); // 文件上传输入框
  const oInfo = doc.querySelector('#uploadInfo'); // 用于显示上传信息
  const oBtn = doc.querySelector('#uploadBtn'); // 上传按钮

  let uploadedSize = 0; // 已上传的文件大小,用于控制分块上传

  // 初始化函数
  const init = () => {
    bindEvent(); // 绑定事件
  }

  // 绑定事件函数
  function bindEvent() {
    oBtn.addEventListener('click', uploadVideo, false); // 点击上传按钮触发上传函数
  }

  // 异步上传函数
  async function uploadVideo() {
    // 获取文件对象
    const { files: [ file ] } = oUploader;
    
    if (!file) {
      // 检查是否选择了文件,如果没有则显示提示信息
      oInfo.innerText = UPLOAD_INFO['NO_FILE'];
      return;
    }

    if (!ALLOWED_TYPE[file.type]) {
      // 检查文件类型是否被允许,如果不允许则显示提示信息
      oInfo.innerText = UPLOAD_INFO['INVALID_TYPE'];
      return;
    }

    // 获取文件的一些基本信息
    const { name, type, size } = file;
    const fileName = new Date().getTime() + '_' + name;
    let uploadedResult = null;
    oProgress.max = size;
    oInfo.innerText = '';

    while (uploadedSize < size) {
      // 使用slice方法将文件切割成小块,实现分块上传
      const fileChunk = file.slice(uploadedSize, uploadedSize + CHUNK_SIZE);
      // 创建FormData对象,用于传递文件块和相关信息
      const formData = createFormData({
        name, 
        type,
        size,
        fileName,
        uploadedSize,
        file: fileChunk
      });

      try {
        // 使用axios库发送POST请求,将文件块上传到后端
        uploadedResult = await axios.post(API.UPLOAD_VIDEO, formData);
      } catch (e) {
        // 捕获异常,如果上传失败,则显示提示信息
        oInfo.innerText = `${ UPLOAD_INFO['UPLOAD_FAILED'] }(${ e.message })`;
        return; 
      }

      // 更新已上传的文件大小和进度条
      uploadedSize += fileChunk.size;
      oProgress.value = uploadedSize;
    }

    // 上传成功后,显示上传成功的提示信息,并将文件输入框置空
    oInfo.innerText = UPLOAD_INFO['UPLOAD_SUCCESS'];
    oUploader.value = null;
    // 根据后端返回的视频URL,创建一个视频元素并展示在页面上
    createVideo(uploadedResult.data.video_url);
  }

  // 创建FormData函数,将文件块和相关信息添加到FormData对象中
  function createFormData ({
    name, 
    type,
    size,
    fileName,
    uploadedSize,
    file
  }) {
    const fd = new FormData();

    fd.append('name', name);
    fd.append('type', type);
    fd.append('size', size);
    fd.append('fileName', fileName);
    fd.append('uploadedSize', uploadedSize);
    fd.append('file', file);

    return fd;
  }

  // 创建视频元素函数,用于在页面上展示上传成功的视频
  function createVideo(src) {
    const oVideo = document.createElement('video');
    oVideo.controls = true;
    oVideo.width = '500';
    oVideo.src = src;
    document.body.appendChild(oVideo);
  }

  init(); // 初始化,执行绑定事件函数
})(document);
  1. 引入依赖项:

    • config.js:导出一些常量,包括上传信息、允许上传的文件类型、块大小和上传接口的URL。
    • axios:一个用于发起HTTP请求的库,用于将文件块上传到后端。
  2. 创建一个立即执行的匿名函数:

    • 接受document作为参数,并使用doc来代表document对象。
    • 这种方式可以避免全局变量污染,确保代码运行在独立的作用域中。
  3. 获取页面上的元素:

    • oProgress:代表上传进度条的<progress>元素。
    • oUploader:代表文件上传输入框的<input type="file">元素。
    • oInfo:用于显示上传信息的<p>元素。
    • oBtn:代表上传按钮的<button>元素。
  4. 初始化函数:

    • 调用bindEvent()函数,用于绑定点击上传按钮时的事件处理函数。
  5. 绑定事件函数:

    • 使用addEventListener()方法,为上传按钮添加一个点击事件监听器。
    • 当点击上传按钮时,将触发uploadVideo()函数进行文件上传。
  6. 异步上传函数uploadVideo()

    • 获取文件对象file,这是通过文件上传输入框oUploader获取的。
    • 检查是否选择了文件,如果没有则显示提示信息,并返回。
    • 检查文件类型是否被允许上传,如果不允许则显示提示信息,并返回。
    • 获取文件的一些基本信息,如文件名、文件类型和文件大小等。
    • 使用while循环,对文件进行分块上传:
      • 使用file.slice()方法将文件切割成小块(块大小由常量CHUNK_SIZE定义)。
      • 创建FormData对象,将文件块和相关信息添加到其中,用于传递给后端。
      • 使用axios.post()方法,将文件块上传到后端指定的URL(由常量API.UPLOAD_VIDEO定义)。
      • 如果上传失败,捕获异常并显示提示信息,并返回。
      • 更新已上传的文件大小和进度条的值。
    • 上传完成后,显示上传成功的提示信息,并将文件输入框的值置空。
    • 根据后端返回的视频URL,调用createVideo()函数在页面上创建视频元素并展示上传成功的视频。
  7. 创建FormData函数createFormData()

    • 将文件块和相关信息添加到FormData对象中,用于传递给后端。
  8. 创建视频元素函数createVideo()

    • 用于在页面上创建视频元素,并设置视频URL为上传成功后后端返回的视频URL。
  9. 初始化函数init()

    • 执行bindEvent()函数,将上传按钮和事件处理函数绑定在一起。
  10. 最后,通过调用init()函数来启动整个前端代码。

 

我们在前端代码中引入了axios库,这是一个用于发起HTTP请求的常用工具。在实际项目中,你需要确保在HTML文件中引入axios库,或者使用其他类似功能的库。

以上代码中,我们首先定义了一些常量,如允许上传的文件类型、块大小等。然后我们通过事件监听捕获“上传”按钮的点击事件,执行上传函数。在上传函数中,我们通过分块上传的方式,将文件切割为多个小块,并逐个发送给后端进行处理。上传过程中,我们还会实时更新上传进度条和显示上传信息。

后端文件处理

现在让我们关注后端处理部分。我们将使用Node.js和Express来搭建一个简单的后端服务器,接收前端传递的文件块,并将它们合并成完整的文件。

1. 安装依赖

首先,在项目目录下创建一个package.json文件,然后运行以下命令安装依赖:

以上代码使用Express框架搭建了一个简单的服务器,并设置了文件上传的路由。我们可以通过/upload_video接口来上传文件块,后端根据上传的信息,将文件块保存在服务器端,直到所有块都接收完成后,合并成完整的文件。

npm init -y
npm install express body-parser express-fileupload

2. 后端代码

接下来,我们创建一个server.js文件,编写后端代码:

const express = require('express');
const bodyParser = require('body-parser');
const uploader = require('express-fileupload');
const { extname, resolve } = require('path');
const { existsSync, appendFileSync, writeFileSync } = require('fs');

const app = express();
const PORT = 8000;

// 设置中间件,解析请求的JSON数据和文件上传
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(uploader());

// 设置静态资源目录,用于存放上传的临时文件块
app.use('/', express.static('upload_temp'));

// 允许跨域访问
app.all('*', (req, res, next) => {
  res.header('Access-Control-Allow-origin', '*');
  res.header('Access-Control-Allow-Methods', 'POST,GET');
  next();
});

// 处理文件上传的POST请求,对应的URL为/upload_video
app.post('/upload_video', (req, res) => {
  // 从请求体中获取传递的文件信息和文件块
  const { name, type, size, fileName, uploadedSize } = req.body;
  const { file } = req.files;

  if (!file) {
    // 检查是否传递了文件块,如果没有则返回错误信息
    res.send({
      code: 1001,
      msg: 'No file uploaded'
    });
    return;
  }

  if (!ALLOWED_TYPE[type]) {
    // 检查文件类型是否被允许,如果不允许则返回错误信息
    res.send({
      code: 1002,
      msg: 'The type is not allowed for uploading.'
    });
    return;
  }

  const filename = fileName + extname(name);
  const filePath = resolve(__dirname, './upload_temp/' + filename);

  if (uploadedSize !== '0') {
    if (!existsSync(filePath)) {
      // 检查文件是否存在,如果不存在则返回错误信息
      res.send({
        code: 1003,
        msg: 'No file exists'
      });
      return;
    }

    // 文件已存在,则将当前文件块追加到已有的文件中
    appendFileSync(filePath, file.data);

    // 返回成功信息和视频URL
    res.send({
      code: 0,
      msg: 'Appended',
      video_url: 'http://localhost:8000/' + filename
    });

    return;
  }
  
  // 第一个文件块,创建一个新文件并写入当前块的数据
  writeFileSync(filePath, file.data);

  // 返回成功信息
  res.send({
    code: 0,
    msg: 'File is created'
  });
});

// 启动服务,监听指定端口
app.listen(PORT, () => {
  console.log('Server is running on ' + PORT);
});
  1. 引入依赖项:

    • express:Express框架,用于构建Web服务器。
    • body-parser:解析请求体中的JSON数据。
    • express-fileupload:处理文件上传请求。
    • path:Node.js内置模块,用于处理文件路径。
    • fs:Node.js内置模块,用于文件系统操作。
  2. 创建Express应用:

    const app = express();
    const PORT = 8000;
    

  3. 使用中间件:

    • bodyParser中间件用于解析请求体中的JSON数据和表单数据。
    • uploader中间件用于处理文件上传请求。
    • 设置静态资源目录'/upload_temp',用于存放上传的临时文件块。
  4. 设置跨域访问: 使用app.all()方法设置允许跨域请求的响应头。

  5. 处理文件上传请求:

    • 使用app.post('/upload_video', ...)定义处理文件上传的POST请求,对应的URL是/upload_video
    • 从请求体中获取传递的文件信息和文件块,包括文件名、文件类型、文件大小、文件块的上传起始位置等。
    • 检查文件块是否存在,如果不存在则返回错误信息。
    • 检查文件类型是否被允许,如果不允许则返回错误信息。
    • 根据文件名和文件类型生成新的文件名,并计算文件的存储路径。
    • 如果不是第一个文件块,将当前文件块追加到已有的文件中。
    • 如果是第一个文件块,创建一个新文件并写入当前块的数据。
    • 返回成功信息,并包含上传成功后的视频URL。
  6. 启动服务: 使用app.listen()方法启动服务器,监听指定的端口(在此例中为8000)。

 

运行项目

现在,我们已经完成了前端和后端的代码编写。接下来,我们需要在本地运行项目,以测试文件上传的功能。

1. 创建上传临时目录

在项目根目录下创建一个名为upload_temp的文件夹,用于保存上传的临时文件块。

2. 启动服务器

运行以下命令启动后端服务器:

node server.js

3. 启动前端

将前端代码(HTML、CSS和JavaScript)放置在一个文件夹中,然后在该文件夹下运行一个HTTP服务器(使用npm和yarn也没问题, npm install——npm run dev/yarn——yarn dev),比如使用http-server

npx http-server

现在,通过浏览器访问前端页面(通常是http://localhost:8080)。选择一个支持的视频文件(.mp4或.ogv格式),点击上传按钮,你将看到上传进度条显示上传进度。上传完成后,页面将显示上传成功的信息,并在页面上展示上传的视频文件。 

结束语

在本篇博客中,我们学习了如何实现一个简单的前端文件上传功能,并结合Node.js和Express搭建了一个后端服务器来处理文件上传。在实际项目中,你可以根据需求对文件上传的功能进行扩展和优化。希望这篇博客能够帮助你理解文件上传的基本原理和实践方法。谢谢阅读!

 

 

如果你对前端文件上传还有疑问,或者对其他技术主题感兴趣,欢迎加入讨论群或者关注!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/807524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jekins权限管理,给不同用户分配不同项目的权限【一些小细节真要注意】

首先进入jekins的系统管理界面&#xff1a; 一、插件安装 如果没有安装权限管理的插件&#xff0c;需要安装一个插件&#xff0c;插件的名字叫Role-based Authorization Strategy&#xff0c;进入插件管理中&#xff0c;选中这个插件&#xff0c;点击下面的安装就好了&#x…

java-day01(基本常识,环境搭建,注释,HelloWorld)

一&#xff1a;基础常识 软件&#xff1a;按照特定顺序的计算机数据与指令的集合。可分为系统软件&#xff08;如操作系统&#xff09;和应用软件&#xff08;如QQ&#xff09; 人机交互方式&#xff1a;图形化界面&#xff08;GUI&#xff09;与命令行&#xff08;CLI&#…

阿里大佬都内卷的 SpringBoot 从入门到实战笔记,太硬核了!

前言 这份 SpringBoot 实战文档&#xff0c;结合典型业务场景&#xff0c;全面介绍基于 Spring Boot 的核心开发技术&#xff0c;整合开源组件&#xff0c;一步一步地搭建微服务框架&#xff0c;实现微服务治理&#xff0c;详解 60 多个示例、2 个综合项目案例&#xff0c;提高…

低代码管理系统源码:只需简单编写配置文件即可构建企业级应用程序

一套可视化建模&#xff0c;描述式编程的企业应用程序开发平台。只需简单的点击鼠标&#xff0c;几乎任何人都可以创建功能强大的企业应用程序&#xff0c;实现业务流程自动化。企业创建的应用程序可以部署在移动&#xff0c;平板电脑和Web上&#xff0c;创建的应用程序可以很简…

使用Pytest生成HTML测试报告

背景 最近开发有关业务场景的功能时&#xff0c;涉及的API接口比较多&#xff0c;需要自己模拟多个业务场景的自动化测试&#xff08;暂时不涉及性能测试&#xff09;&#xff0c;并且在每次测试完后能够生成一份测试报告。 考虑到日常使用Python自带的UnitTest&#xff0c;所…

TPlink云路由器界面端口映射设置方法?快解析内网穿透能实现吗?

有很多网友在问&#xff1a;TPlink路由器端口映射怎么设置&#xff1f;因为不懂端口映射的原理&#xff0c;所以无从下手&#xff0c;下面小编就给大家分享TPlink云路由器界面端口映射设置方法&#xff0c;帮助大家快速入门TP路由器端口映射设置方法。 1.登录路由器管理界面&a…

Visual Studio 2022 程序员必须知道高效调试手段与技巧(下)终章

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 一些调试的实例&#x1f4ad; 实例一&#x1f4fa; 调试演示 &#x1f4ad; 实…

24考研数据结构-第一章 绪论

数据结构 引用文章第一章&#xff1a;绪论1.0 数据结构在学什么1.1 数据结构的基本概念1.2 数据结构的三要素1.3 算法的基本概念1.4 算法的时间复杂度1.4.1 渐近时间复杂度1.4.2 常对幂指阶1.4.3 时间复杂度的计算1.4.4 最好与最坏时间复杂度 1.5 算法的空间复杂度1.5.1 空间复…

freeswitch的mod_xml_curl模块

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 随着fs服务的增多&#xff0c;每一台fs都需要在后台单独配置&#xff0c;耗时耗力&#xff0c;心力憔悴。 如果有一个集中管理配置的配置中心&#xff0c;统一管理所有fs的配置&#xff0c;并可以实现动态的修改配置就…

STUN工作原理

目录 一. 前言 二. STUN报文格式 STUN Header RFC3489 RFC5389 STUN Message Body RFC3489 RFC5389 三. WebRTC对STUN协议的支持 四. STUN工作流程 1. 使用STUN获取NAT映射后的地址 五. 参考资料 一. 前言 现实网络环境中绝大多数主机都是处于 NAT 之后&#xff0c…

华为数通HCIP-VPN技术-mpls vpn

VPN(虚拟专线网络 作用&#xff1a;实现广域互联&#xff08;不同地域局域网之间跨越公网进行互通&#xff09;&#xff1b; MPLS VPN 设备角色&#xff1a; PE&#xff08;运营商边界设备&#xff09;&#xff1a; CE&#xff1a;VPN用户企业的边界设备 P:运营商 VPN实例…

平均列顺序对列排斥能的影响

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由5张二值化的图片组成&#xff0c;让A有6个1&#xff0c;B有4个1&#xff0c;并且让这10个1的位置没有重合。比较迭代次数的顺序。 其中有9组数据 差值结构 A-B 迭代次数 构造平均列 …

CentOS系统启动过程

CentOS系统启动流程图 &#xff08;1&#xff09; 加载 BIOS 计算机电源加电质检&#xff0c;首先加载基本输入输出系统&#xff08;Basic Input Output System&#xff0c;BIOS&#xff09;&#xff0c;BIOS 中包含硬件 CPU、内存、硬盘等相关信息&#xff0c;包含设备启动顺序…

JMM概述

JMM指的是Java内存模型&#xff0c;它是一个抽象的概念&#xff0c;并不真实存在&#xff0c;定义了对数据访问的规则&#xff0c;每个JVM都必须执行这样的规则&#xff0c;这样使得并发程序运行在不同的JVM上时&#xff0c;运行的结果才是准确、安全的。JMM抽象出主内存和本地…

JSP 中的隐式对象预定义变量详解

JSP隐式对象是JSP容器为每个页面提供的Java对象&#xff0c;开发者可以直接使用它们而不用显式声明。JSP隐式对象也被称为预定义变量。 JSP所支持的九大隐式对象&#xff1a; request对象 request对象是javax.servlet.http.HttpServletRequest 类的示例。每当客户端请求一个J…

1.Ansible

文章目录 Ansible概念作用特性总结 部署AnsibleAnsible模块commandshellcronusergroupcopyfilehostnamepingyumserice/systemdscriptmountarchiveunarchivereplacesetup inventory主机清单主机变量组变量组嵌套 Ansible 概念 Ansible是一个基于Python开发的配置管理和应用部署…

TCP网络通信编程之字符流

【案例1】 【题目描述】 【 注意事项】 (3条消息) 节点流和处理流 字符处理流BufferedReader、BufferedWriter&#xff0c;字节处理流-BufferedInputStream和BufferedOutputStream (代码均正确且可运行_Studying~的博客-CSDN博客 1。这里需要使用字符处理流&#xff0c;来将…

FPGA设计时序分析三、恢复/去除时间

目录 一、背景说明 二、工程设计 2.1 工程代码 2.2 综合结果 一、背景说明 ​恢复时间recovery和去除时间removal和setup、holdup类型&#xff0c;不同点是数据信号为控制信号&#xff0c;如复位&#xff0c;清零&#xff0c;使能信号&#xff0c;更多的是异步的复位信号&a…

Merge the squares! 2023牛客暑期多校训练营4-H

登录—专业IT笔试面试备考平台_牛客网 题目大意&#xff1a;有n*n个边长为1的小正方形摆放在边长为n的大正方形中&#xff0c;每次可以选择不超过50个正方形&#xff0c;将其合并为一个更大的正方形&#xff0c;求一种可行的操作使所有小正方形都被合并成一个n*n的大正方形 1…

硬件-8-操作系统的历史

操作系统的最强入门科普&#xff08;Unix/Linux篇&#xff09; 操作系统的发展史&#xff08;DOS/Windows篇&#xff09; Mac操作系统进化史 手机操作系统的沉浮往事&#xff08;上&#xff09; 手机操作系统的沉浮往事&#xff08;下&#xff09; 1 操作系统种类 我们天天都…