利用 FormData 实现文件上传、监控网路速度和上传进度(前端原生,后端 koa)

news2024/11/15 5:03:00

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

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

相关文章

12.2旋转,SPLAY树的各种操作(SPLAY与AVL是两种BST)

Splay树和AVL树是两种不同的自平衡二叉搜索树实现。 1. 平衡条件&#xff1a;AVL树通过维护每个节点的平衡因子&#xff08;左子树高度减去右子树高度&#xff09;来保持平衡&#xff0c;要求每个节点的平衡因子的绝对值不超过1。Splay树则通过经过每次操作后将最近访问的节点…

Mybatis 操作续集(连着上文一起看)

"查"操作(企业开发中尽量不使用*,需要哪些字段就写哪些字段,都需要就全写上) Mybatis 会自动地根据数据库的字段名和Java对象的属性名进行映射,如果名称一样就进行赋值 但是那些名称不一样的,我们想要拿到,该怎么拿呢? 一开始数据库字段名和Java对象属性名如下图…

mfc 设置excel 单元格的列宽

CString strTL, strBR;strTL.Format(L"%s%d", GetExcelColName(cd.nCol), cd.nRow);strBR strTL;CRange rangeMerge range.get_Range(_variant_t(strTL), _variant_t(strBR));rangeMerge.put_ColumnWidth(_variant_t((long)(20))); 宽度设置函数为 &#xff1a; pu…

软件测试要学习的基础知识——黑盒测试

黑盒测试概述 黑盒测试也叫功能测试&#xff0c;通过测试来检测每个功能是否都能正常使用。在测试中&#xff0c;把程序看作是一个不能打开的黑盒子&#xff0c;在完全不考虑程序内部结构和内部特性的情况下&#xff0c;对程序接口进行测试&#xff0c;只检查程序功能是否按照…

Milvus 再上新!支持 Upsert、Kafka Connector、集成 Airbyte,助力高效数据流处理

Milvus 已支持 Upsert、 Kafka Connector、Airbyte&#xff01; 在上周的文章中《登陆 Azure、发布新版本……Zilliz 昨夜今晨发生了什么&#xff1f;》&#xff0c;我们已经透露过 Milvus&#xff08;Zilliz Cloud&#xff09;为提高数据流处理效率&#xff0c; 先后支持了 Up…

为告警设备设置服务端属性,在tb中标记存在告警的设备

有位读者想要实现标记系统中存在告警的设备,于是我给他做了三个方案。各有优缺点。 第一个方案时,告警是在规则链里手动创建的,通过告警数,+1,-1来标记设备告警属性。 第二种是当设备通过设备配置创建,清空告警。这种情况只适用于一次遥测创建,清空一个告警。不支持单次…

【Vue】使用 Vue CLI 脚手架创建 Vue 项目(使用GUI创建)

前言 在开始使用Vue进行开发之前&#xff0c;我们需要先创建一个Vue项目。Vue CLI&#xff08;Command Line Interface&#xff09;是一个官方提供的脚手架工具&#xff0c;可以帮助我们快速创建Vue项目。Vue CLI也提供了一个可视化的GUI界面来创建和管理Vue项目。 步骤 打开终…

【离散差分】LeetCode2953:统计完全子字符串

作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本题其它解法 【滑动窗口】LeetCode2953:统计完全子字符串 涉及知识点 分块循环 离散差分 题目 给你一个字符串 word 和一个整数 k 。 如果 word 的一个子字符串 s 满足以下条件&#xff0c;我们称它是 完全…

爬虫程序为什么一次写不好?需要一直修改BUG?

从我学习编程以来&#xff0c;尤其是在学习数据抓取采集这方面工作&#xff0c;经常遇到改不完的代码&#xff0c;我毕竟从事了8年的编程工作&#xff0c;算不上大佬&#xff0c;但是也不至于那么差。那么哪些因素导致爬虫代码一直需要修改出现BUG&#xff1f;下面来谈谈我的感…

网络协议与 IP 编址

网络协议与 IP 编址 之前大概了解过了网络的一些基础概念&#xff0c;见文章&#xff1a; 网络基础概念。 之前简单了解OSI模型分层&#xff1a; TCP/IP模型OSI模型TCP/IP对等模型应用层应用层表示层应用层会话层主机到主机层传输层传输层因特网层网络层网络层网络接入层数据链…

ERPNext SQL 注入漏洞复现

0x01 产品简介 ERPNext 是一套开源的企业资源计划系统。 0x02 漏洞概述 ERPNext 系统frappe.model.db_query.get_list 文件 filters 参数存在 SQL 注入漏洞,攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权…

【深度学习】回归模型相关重要知识点总结

回归分析为许多机器学习算法提供了坚实的基础。在这篇文章中&#xff0c;我们将总结 10 个重要的回归问题和5个重要的回归问题的评价指标。 一、线性回归的假设是什么 线性回归有四个假设&#xff1a; 线性&#xff1a;自变量&#xff08;x&#xff09;和因变量&#xff08;y&…

[STM32-1.点灯大师上线】

学习了江协科技的前4课&#xff0c;除了打开套件的第一秒是开心的&#xff0c;后面的时间都是在骂娘。因为51的基础已经几乎忘干净&#xff0c;c语言已经还给谭浩强&#xff0c;模电数电还有点底子&#xff0c;硬着头皮上吧。 本篇主要是讲述学习点灯的过程和疑惑解释。 1.工…

【杂】解决关于mean(0)理解错误引发的程序bug

一、环境和解释器要一起配置好 invalid syntax 发生你在终端激活了一个环境&#xff0c;但 VSCode 依然使用之前的解释器的情况。 解释器设置影响了 VSCode 中运行 Python 脚本、调试、代码补全等功能的行为。VSCode 会根据你选择的解释器来执行这些操作。 二、关于mean&#x…

在OSPF中使用基本ACL过滤路由信息示例

1、ACL的基本原理。 ACL由一系列规则组成&#xff0c;通过将报文与ACL规则进行匹配&#xff0c;设备可以过滤出特定的报文。设备支持软件ACL和硬件ACL两种实现方式。 2、ACL的组成。 ACL名称&#xff1a;通过名称来标识ACL&#xff0c;就像用域名代替IP地址一样&#xff0c;更…

2023-2024-1-高级语言程序设计-第2次月考函数题

6-1-1 调用函数求分段函数 编写函数fun计算下列分段函数的值&#xff1a; 。 函数接口定义&#xff1a; float fun(float x); 其中 x 是用户传入的参数。 函数须返回分段函数的计算结果。 裁判测试程序样例&#xff1a; #include <stdio.h> #include <math.h> …

06 数仓平台MaxWell

Maxwell简介 Maxwell是由Zendesk公司开源&#xff0c;用 Java 编写的MySQL变更数据抓取软件&#xff0c;能实时监控 MySQL数据库的CRUD操作将变更数据以 json 格式发送给 Kafka等平台。 Maxwell输出数据格式 Maxwell 原理 Maxwell工作原理是实时读取MySQL数据库的二进制日志…

Windows 10安装FFmpeg详细教程

Windows 10安装FFmpeg详细教程 0. 背景 在搭建之前的项目环境时&#xff0c;需要安装ffmpeg&#xff0c;在此记录下过程 1. 官网下载 点击进入官网&#xff1a;ffmpeg&#xff0c;官网地址&#xff1a;https://ffmpeg.org/download.html 如图所示&#xff0c;点击Windows图标…

【Windows】永久屏蔽系统更新

永久关闭电脑更新服务 操作思路&#xff1a; 第一步 winR 输入 services.msc 回车 进入服务管理窗口第二步 进入窗口后 找到 w 开头的文件夹 并找到Windows Update 双击打开 Windows Update 将启动类型&#xff08;E&#xff09; 改为禁用 上方的 “常规” “登录” “恢…

MATLAB学习QPSK之QPSK_MOD_DEMOD_SALIMup分析

学习的背景说明 因为在学习5G物理层&#xff0c;一直很忙&#xff0c;没有时间。最近稍有一点空闲&#xff0c;所以&#xff0c;学习一下算法。 QPSK的算法&#xff0c;虽然说我没有完全学透&#xff0c;大致还是懂的。只能一直没时间用MATLAB来研究一下。 然后看到这个实例&…