利用 FormData 实现文件上传、监控网路速度和上传进度

news2024/10/5 13:41:59

利用 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/1277216.html

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

相关文章

推荐3个完美替代 Navicat 的工具

现在企业&#xff0c;mysql数据库用的比较多&#xff0c;mysql数据库客户端的需求也就比较大&#xff0c;navicat就被大家所熟知。 这个工具&#xff0c;确实好用&#xff0c;功能也非常强大&#xff0c;但是&#xff0c;它的强大&#xff0c;是需要付费&#xff0c;或者用一些…

Ubuntu 2204 安装libimobiledevice

libimobiledevice是一个开源的软件&#xff0c;它可以直接使用系统原生协议和IOS设备进行通信&#xff0c;类似iMazing&#xff0c;iTunes&#xff0c;libimobiledevice不依赖IOS的私有库&#xff0c;并且连接IOS设备时用的都是原生协议&#xff0c;IOS无需越狱就能实现设备信息…

css实现简单的抽奖动画效果

使用css的animation和transform和transition可以实现简单的图片放大缩小&#xff0c;旋转&#xff0c;位移的效果&#xff0c;由此可以延伸的动画效果还是挺多的&#xff0c;比如图片慢慢放大&#xff0c;图片慢慢旋转并放大&#xff0c;图片慢慢变化位置等等&#xff0c; 抽奖…

TCP三次握手过程

什么是TCP tcp是一个面向连接的、可靠的、基于字节流的传输层通信协议 面向连接&#xff1a;TCP连接是一对一的&#xff0c;不能实现一对多或多对一&#xff0c;TCP在通信前要首先建立连接&#xff0c;连接成功后才能开始进行通信可靠的&#xff1a;TCP连接要保证通信过程的可靠…

HUAWEI Vision Pro?华为欲出手竞品对标Apple Vision Pro

大家好&#xff0c;我是极智视界&#xff0c;欢迎关注我的公众号&#xff0c;获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」&#xff0c;星球内有超多好玩的项目实战源码和资源下载&#xff0c;链接&#xff1a;https://t.zsxq.com/0aiNxERDq Apple Vision Pro 是…

【Python】永久切换pip下载源

目录 永久切换pip下载源切换方式pip国内镜像源参考 永久切换pip下载源 使用Python默认pip下载源容易导致部分库下载超时失败&#xff0c;将pip下载源切换回国内&#xff0c;避免超时访问导致失败 切换方式 通过修改配置文件完成切换&#xff1a; 进入到 C:\Users\用户名 路径…

Python批量Git Pull,对文件夹批量进行Pull操作

效果展示 说明 本来是想写的完善一些&#xff0c;但由于是自用&#xff0c;所以写出来后发现已经解决了自己的问题&#xff0c;所有 2和3功能没有写。 执行的话&#xff0c;需要 cmd 之后 直接 Python BatchGitPull.py 运行下面代码即可。 里面同时涉及到其他Pyhon知识点(写给…

无桌面版docker在Ubuntu系统上安装

目录 注意 系统要求 卸载旧版本 安装 使用apt存储库安装 1. 设置 Docker 的apt存储库。 2. 安装Docker软件包 3. 通过运行镜像来验证Docker Engine安装是否成功 hello-world。 从包中安装 1. 进入 https://download.docker.com/linux/ubuntu/dists/。 2. 在列表中选择…

2023最新软件测试八股文,你背得怎样了

今天给大家分享软件测试面试题基础篇&#xff0c;看看大家能答对几题 1、软件测试方法有哪些分类?各自有什么特点?设计测试用例的主要方法有哪些? 白盒: 测试人员利用程序内部的逻辑结构及相关信息&#xff0c;设计或选择型试用例:对程序所有的逻辑路径进行测试。 黑盒: …

用Flask构建Python的API接口

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 文章目录 Flask简介编码运行 Flask简介 如果你的代码是用python实现的&#xff0c;但是你想写一个类似SpringBoot那种的API接口&#xff0c;这时我们可以使用Flas…

算法基础四

括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例…

io基础入门

压缩的封装 参考&#xff1a;https://blog.csdn.net/qq_29897369/article/details/120407125?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-120407125-blog-120163063.235v38pc_relevant_sort_base3&spm1001.2101.3001.…

05-建造者模式-C语言实现

UML类图&#xff1a; 代码实现&#xff1a; #include <stdio.h> #include <stdlib.h>// 产品类 typedef struct {char* part1;char* part2;char* part3; } Product;// 抽象建造者类 typedef struct {void (*buildPart1)(void*, const char*);void (*buildPart2)(v…

【广州华锐视点】广东3D展厅开发服务找广州华锐视点,打造未来展览新体验!

随着科技的不断发展&#xff0c;人们对于信息获取和传播的方式也在不断创新。传统的实体展览馆在空间、时间、地域等方面存在诸多限制&#xff0c;而3D数字展厅则为我们提供了一个全新的解决方案。广州华锐互动致力于为客户提供专业的3D数字展厅定制开发服务&#xff0c;让您的…

4个Pycharm高效插件

大家好&#xff0c;Pycharm是Python最受欢迎的集成开发环境之一&#xff0c;它具有良好的代码助手、漂亮的主题和快捷方式&#xff0c;使编写代码变得简单快捷。话虽如此&#xff0c;开发者仍可以通过使用一些插件来提高在Pycharm中编写Python代码的效率和乐趣&#xff0c;在市…

PID控制

在PID控制中&#xff0c;输出通常是一个控制量&#xff0c;而不是直接的PWM占空比。输出的具体含义可以根据具体的系统和应用而变化。在这段代码中&#xff0c;PID控制器的输出是 output_calc。 而 CCR_duty 是控制施肥系统的PWM占空比&#xff0c;这是通过PID控制的输出和曲线…

华为云之快速部署FTP站点

华为云之快速部署FTP站点 一、本次实践介绍1.1 实践环境简介1.2 本次实践目的 二、vsftpd介绍2.1 vsftpd简介2.2 vsftpd特点 三、环境准备工作3.1 预置实验环境3.2 查看预置环境信息3.3 登录华为云3.4 查看弹性云服务器状态3.5 查看弹性公网IP地址3.6 ssh登录弹性云服务器3.6 查…

System.out.println隐藏字符串

昨天开发的时候遇到一个坑&#xff0c;这个坑几乎浪费了我一整天时间&#xff0c;我甚至现在都不知道其原因。 开发环境 macOS Ventura 13.4 IntelliJ IDEA 2023.1.2 现象 我用java的各种httpclient获取网络上的一个文本文件&#xff0c;获取的文本文件的内容使用System.ou…

Python 网络爬虫(一):HTML 基础知识

《Python入门核心技术》专栏总目录・点这里 文章目录 1. 什么是 HTML2. HTML 的特点3. HTML 的标签和属性4. HTML 的结构4.1 文档类型声明4.2 根元素4.3 头部部分4.4 主体部分4.5 表格标签4.6 区块4.7 嵌套和层次结构4.8 表单4.9 注释 5. HTML 交互事件 大家好&#xff0c;我是…

Postman Post请求上传文件

Postman Post请求上传文件 一、选择post请求方式&#xff0c;输入请求地址 二、填写Headers Key&#xff1a;Content-Type Value&#xff1a;multipart/form-data [{"key":"Content-Type","value":"multipart/form-data","de…