【文件上传系列】No.0 利用 FormData 实现文件上传、监控网路速度和上传进度(原生前端,Koa 后端)

news2024/11/24 4:51:57

利用 FormData 实现文件上传

基础功能:上传文件

演示如下:

请添加图片描述

概括流程:

  • 前端:把文件数据获取并 appendFormData 对象中
  • 后端:通过 ctx.request.files 对象拿到二进制数据,获得 node 暂存的文件路径

前端

前端的工作就是把页面写好,ajaxFormData 组装好,发送给后端。

基础功能:组装 FormData 和 XHR

前端这边代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="file" name="file" id="file" />
    <button id="btn">点我上传</button>
  </body>
  <script>
    const btn = document.getElementById('btn');
    btn.onclick = function () {
      let file = document.querySelector('#file').files[0];
      console.log(file);
      // 组装好 formData
      // 文件传输是通过正文传输的,所以要用 post
      let formData = new FormData(); // 这里的 new formData() 会自动帮我设置 content-type
      formData.append('data', file);
      formData.append('name', '文件');
      formData.append('年龄', 20);

      // 组装好 xhr
      let xhr = new XMLHttpRequest();
      xhr.open('post', '/upload');
      xhr.onload = function () {
        console.log(xhr.responseText);
      };
      xhr.send(formData);
    };
  </script>
</html>

基础:xhr.upload 上传钩子函数

大概有如下几个钩子(比较常用的)

xhr.upload.onprogress = (event) => {
    console.log('上传过程');
}
xhr.upload.onload = () => {
    console.log('上传成功');
}
xhr.upload.onloadend = () => {
    console.log('上传完成');
}
xhr.upload.onabort = () => {
    console.log('取消上传');
}

onprogress 这个函数是在上传过程中不断循环被执行的,其中有事件因子 event,里面会有上传中的信息

如果想要监控速度和进度的话,可以在上传的过程中计算出来

如果想要取消上传,就把 xhr.abort() 即可。

document.getElementById('cancelBtn').addEventListener('click', function () {
    // 取消上传
    xhr.abort();
});

基础:利用钩子函数计算下载速度和进度

速度:思路就是求出一段时间的下载量(byte)和一段时间(s),然后做除法
s p e e d = d 单位数据包大小 b y t e d 单位时间 s b y t e / s speed = \frac{{\rm d}单位数据包大小 byte}{{\rm d }单位时间 s}{byte/s} speed=d单位时间sd单位数据包大小bytebyte/s

let oldDataSize;
let oldTime;
xhr.onload = function () {
    let responseText = xhr.responseText;
    console.log("上传成功", responseText);
};
xhr.upload.onloadstart = () => {
    console.log("上传开始!");
    oldLoaded = 0;
    oldTime = new Date().getTime();
};
xhr.upload.onprogress = (enent) => {
    // 计算单位时间文件加载大小
    let duringLoaded = event.loaded - oldLoaded;
    // 计算单位时间差
    let duringTime = (new Date().getTime() - oldTime) / 1000; // 时间戳,默认单位是毫秒
    
    // 记录旧的数据,下次循环的时候需要用的
    oldTime = new Date().getTime();
    oldLoaded = event.loaded;
    
    console.log("上传中:>>", event);
};

进度:已经上传的数据loaded 与总数据 total 的比值
p r o g r e s s = 已上传数据包大小 b y t e 总文件大小 b y t e ∗ 100 % progress= \frac{已上传数据包大小 byte}{总文件大小 byte} { * } {100}{\%} progress=总文件大小byte已上传数据包大小byte100%

完善:添加进度条以及速度标识

整体代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="file" name="file" id="file" />
    <div>进度: <progress value="0" max="100" id="progress"></progress></div>
    <div>速度: <span id="speed"></span> <span id="unit"></span></div>
    <button id="btn">上传</button>
    <button id="cancelBtn">取消上传</button>
  </body>
  <script>
    const btn = document.getElementById('btn');
    let xhr = new XMLHttpRequest();
    let oldDataSize;
    let oldTime;
    btn.onclick = function () {
      let file = document.querySelector('#file').files[0];
      console.log(file);
      // 组装好 formData
      // 文件传输是通过正文传输的,所以要用 post
      let formData = new FormData(); // 这里的 new formData() 会自动帮我设置 content-type
      formData.append('data', file);
      formData.append('name', '文件');
      formData.append('年龄', 20);

      // 组装好 xhr
      xhr.open('post', '/upload');
      xhr.onload = function () {
        console.log(xhr.responseText);
      };
      xhr.upload.onloadstart = (event) => {
        console.log('开始上传');
        oldLoaded = 0;
        oldTime = new Date();
      };
      // onprogress 钩子函数会不停地被调用
      xhr.upload.onprogress = (event) => {
        console.log('正在上传:>>', event);

        // 计算速度
        let duringLoaded = (event.loaded - oldLoaded) / 1024;
        let duringTime = (new Date() - oldTime) / 1000; // 时间戳,默认单位是毫秒

        // 记录旧的数据,下次循环的时候需要用的
        oldTime = new Date();
        oldLoaded = event.loaded;
        let speed = duringLoaded / duringTime; // 单位是 bt/s
        let unit = 'b/s';
        if (speed > 1024) {
          speed = speed / 1024;
          unit = 'kb/s';
        }
        if (speed > 1024) {
          speed = speed / 1024;
          unit = 'mb/s';
        }
        if (speed > 1024) {
          speed = speed / 1024;
          unit = 'gb/s';
        }
        if (speed > 1024) {
          speed = speed / 1024;
          unit = 'tb/s';
        }
        document.getElementById('speed').innerHTML = `${speed}`;
        document.getElementById('unit').innerHTML = `${unit}`;

        // 计算进度
        const { total, loaded } = event;
        let progress = ((loaded / total) * 100).toFixed(0);
        document.getElementById('progress').value = progress;
      };
      xhr.upload.onload = () => {
        console.log('上传成功');
      };
      xhr.upload.onloadend = () => {
        console.log('上传完成');
      };
      xhr.upload.onabort = () => {
        console.log('取消上传');
      };

      xhr.send(formData);
    };

    document.getElementById('cancelBtn').addEventListener('click', function () {
      // 取消上传
      xhr.abort();
    });
  </script>
</html>

后端

后端获取相应数据的方式如下:

router.post('/upload', ctx => {
    console.log(ctx.request.body);  // 接收文字
    console.log(ctx.request.files); // 接收文件信息
})

node 会帮我们把二进制文件存储到临时地址,我们可以通过 fs 模块拿到文件,然后写到自己想要的位置

在这里插入图片描述

基本功能:拿到二进制数据并转存文件

后端接收注意要在 KoaBody 这里允许上传文件,具体的知识点可以阅读一下这篇博文:理解 HTTP 中的 multipart/form-data

app.use(KoaBody({
    multipart: true
}))

在这里插入图片描述

后端代码如下:

const Koa = require('koa');
const View = require('koa-views');
const Router = require('koa-router');
const { koaBody } = require('koa-body');
const Static = require('koa-static');
const fs = require('fs');
const app = new Koa();
const router = new Router();
app.use(View(__dirname));
app.use(Static(__dirname));
app.use(koaBody({ multipart: true }));
// 异步函数
router.get('/', async (ctx, next) => {
  await ctx.render('index.html');
});

// 异步函数
router.post('/upload', async (ctx, next) => {
  console.log('ctx.request.files:>>', ctx.request.files);
  console.log('ctx.request.body:>>', ctx.request.body);
  const filePath = ctx.request.files.data.filepath;
  const readFile = fs.readFileSync(filePath);
  fs.writeFileSync('static/' + ctx.request.files.data.originalFilename, readFile);
  ctx.body = '请求成功';
});

app.use(router.routes());
app.listen(3000, () => {
  console.log('server start:>>', 'http://localhost:3000');
});

优化:文件夹的判断以及错误处理

可以检测文件夹是否存在,如果文件夹不存在的话自然会报错,完善后的代码如下

/**
 * 说明:
 * fs.exists() 已弃用,但 fs.existsSync() 不是。
 * fs.exists() 的 callback 参数接受与其他 Node.js 回调不一致的参数。 fs.existsSync() 不使用回调
 * 参考地址:https://nodejs.cn/api/fs/fs_existssync_path.html
 */
router.post('/upload', async (ctx, next) => {
  try {
    //   console.log('ctx.request.files:>>', ctx.request.files);
    //   console.log('ctx.request.body:>>', ctx.request.body);
    const data = ctx.request.files.data;
    const { filepath, originalFilename } = data;
    if (!fs.existsSync(`static`)) {
      fs.mkdirSync('static');
    }
    const readFile = fs.readFileSync(filepath);
    fs.writeFileSync(`static/${originalFilename}`, readFile);
    ctx.body = '请求成功';
  } catch (err) {
    console.log(err);
  }
});

断点续传

Q & A

Error: options.maxFileSize (209715200 bytes) exceeded, received 209731427 bytes of file data

在这里插入图片描述

这是后端有上传文件大小限制的问题,在 koa-body 配置中把文件改的大一些,默认是 200mb,点我查看源文档

在这里插入图片描述

/**
 * 设置上传文件大小最大限制,默认1000M
 * https://github.com/node-formidable/formidable
 */
app.use(
  koaBody({
    multipart: true,
    formidable: {
      maxFileSize: 1000 * 1024 * 1024, 
    },
  })
);

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

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

相关文章

智能优化算法应用:基于广义正态分布算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于广义正态分布算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于广义正态分布算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.广义正态分布算法4.实验参数设定5.算…

手把手将Visual Studio Code变成Python开发神器

Visual Studio Code 是一款功能强大、可扩展且轻量级的代码编辑器&#xff0c;经过多年的发展&#xff0c;已经成为 Python 社区的首选代码编辑器之一 下面我们将学习如何安装 Visual Studio Code 并将其设置为 Python 开发工具&#xff0c;以及如何使用 VS Code 提高编程工作…

数字人对话系统 Linly-Talker

&#x1f525;&#x1f525;&#x1f525;数字人对话系统 Linly-Talker&#x1f525;&#x1f525;&#x1f525; English 简体中文 欢迎大家star我的仓库 https://github.com/Kedreamix/Linly-Talker 2023.12 更新 &#x1f4c6; 用户可以上传任意图片进行对话 介绍 Lin…

读书笔记-《数据结构与算法》-摘要4[插入排序]

插入排序 核心&#xff1a;通过构建有序序列&#xff0c;对于未排序序列&#xff0c;在已排序序列中从后向前扫描(对于单向链表则只能从前往后遍历)&#xff0c;找到相应位置并插入。实现上通常使用in-place排序(需用到O(1)的额外空间) 从第一个元素开始&#xff0c;该元素可…

2023年广东工业大学腾讯杯新生程序设计竞赛

E.不知道叫什么名字 题意&#xff1a;找一段连续的区间&#xff0c;使得区间和为0且区间长度最大&#xff0c;输出区间长度。 思路&#xff1a;考虑前缀和&#xff0c;然后使用map去记录每个前缀和第一次出现的位置&#xff0c;然后对数组进行扫描即可。原理&#xff1a;若 s …

低代码——“平衡饮食”才是王道

文章目录 一、低代码的概念二、低代码的优点2.1. 高效率与快速开发2.2. 降低技术门槛2.3. 适用于快速迭代与原型开发 三、低代码的缺点3.1. 定制性不足3.2. 深度不足3.3. 可能导致技术债务 四、低代码开发的未来4.1. 深度定制化4.2. 智能化 五、低代码会替代传统编程吗&#xf…

甘草书店:#9 2023年11月23日 星期四 「麦田创业历程分享1——联合创始人的魔幻相遇」

既然甘草是一家创业主题的书店咖啡馆&#xff0c;那就从我&#xff0c;从麦田开始分享一下创业历程吧。 需要声明的是&#xff0c;我从不认为我有资格对别人的创业指指点点&#xff0c;每位创业者的性格、背景、基础、诉求各有不同&#xff0c;时代发展也日新月异&#xff0c;…

Javaweb | Servlet编程

目录: 1.认识Servlet2.编写Servlet3.Servlet的运行机制4.Servlet的生命周期4.1 Servlet生命周期图init()方法doGet()doPost()service()destroy()方法 5.解决“控制台”打印中文乱码问题6.Servlet 和 JSP内置对象 (常用对象)获得out对象获得request 和 response对象获得session对…

数据结构 图的广度优先搜索和深度优先搜索

一、广度优先搜索 广度优先搜索等价于树的层次遍历&#xff0c;将起点的每一层进行遍历 当这一层结点全部被遍历完时&#xff0c;再遍历下一层次&#xff0c;从图中可以根据距离遍历起点的长度进行层次选择 例&#xff1a; 以a结点作为开始结点 a的下一层次有b c e三个结点 所以…

无敌是多么的寂寞!一本书讲透Java多线程!吊打多线程从原理到实践!

摘要 互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流量洪峰&#xff0c;背后都离不开多线程技术的支持。在数字化转型的过程中&#xff0c;高并发、高性能是衡量系统性能的核心指标&#xff…

C语言--不使用库函数,把一个数字转为字符串【详细解释】

一.题目描述 输入一个数字&#xff0c;把他转为字符串 比如&#xff1a;输入数字&#xff1a;12345 输出&#xff1a;12345&#xff08;这里的12345是字符串12345&#xff09; 二.思路分析 比如给定一个数字12345&#xff0c;先把它转为字符54321&#xff08;“54321”&#…

洛谷P1044 [NOIP2003 普及组] 栈 递归方法

目录 核心&#xff1a; 问题转化&#xff1a; 状态转化&#xff1a;&#xff08;你得先读懂题&#xff0c;理解我们要干什么&#xff09; 对应不同情况下的状态转化&#xff1a;&#xff08;比如栈空就不能出栈&#xff0c;&#xff0c;&#xff09; AC代码&#xff1a; 题…

MySQL实战45讲-第1-2讲-一条SQL查询语句是如何执行的? 一条SQL更新语句是如何执行的

大体来说&#xff0c;MySQL可以分为Server层和存储引擎层两部分 Server层&#xff1a;Server层包括连接器、查询缓存、分析器、优化器、执行器等。以及所有的内置函数&#xff08;如日期、时间、数学和加密函数等&#xff09;&#xff0c;所有跨存储引擎的功能都在这一层实现&a…

C语言课程设计

内容与设计思想 1、系统功能与分析&#xff08;填写你所设计的菜单及流程图&#xff09;。 菜单&#xff1a; 日历打印 日历推算 日历间隔倒计时牌 退出程序 模块设计 根据功能需要&#xff1a; 源文件&#xff1a; #include<stdio.h> #include&…

基于深度学习的遥感图像变化差异可视化系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像变化差异可视化是遥感图像处理和分析的重要研究领域之一。随着遥感技术的快速发展和遥感数据的广泛应用&#xff0c;遥感图像的获取和处理变得越来越容易…

hive-3.1.2环境安装实验

1.修改hadoop相关参数 1-修改core-site.xml [bigdata@master hive]$ vim /opt/module/hadoop/etc/hadoop/core-site.xml <!-- 配置该bigdata(superUser)允许通过代理访问的主机节点 --><property><name>hadoop.proxyuser.bigdata.hosts</name><va…

FastAPI的请求体参数

FastAPI 请求体参数 所谓的请求体参数&#xff0c;就是不在url中的发送的数据&#xff0c;其实我们应该知道http协议是什么。 所谓的协议&#xff0c;就是一种格式的约定。举个生活中的简单的例子&#xff0c;你想买个早餐&#xff0c;你要问老板鸡蛋灌饼多少钱&#xff0c;老…

Python查看文件列表

os.listdir 是 Python 的一个内置函数&#xff0c;用于列出指定目录中的所有文件和子目录。它接受一个字符串参数&#xff0c;即要列出内容的目录的路径。 列出当前工作目录中的所有文件和子目录 files_and_dirs os.listdir() print(files_and_dirs) 列出指定目录中的所…

一则广告,一个故事,这就我选择学习计算机专业的两个原因

还记得当初自己为什么选择计算机&#xff1f; 现在回想起来&#xff0c;当初驱使自己选择学习计算机专业的原因&#xff0c;一共有两个&#xff1a; 一、一则长城电脑的广告。 上个世纪80年代&#xff0c;我还在读小学&#xff0c;当时在中央电视台上经常播放着的长城电脑的一则…

(NeRF学习)3D Gaussian Splatting Instant-NGP

学习参考&#xff1a; 3D Gaussian Splatting入门指南【五分钟学会渲染自己的NeRF模型&#xff0c;有手就行&#xff01;】 三维重建instant-ngp环境部署与colmap、ffmpeg的脚本参数使用 一、3D Gaussian Splatting &#xff08;一&#xff09;3D Gaussian Splatting环境配置…