【前端学习记录】记一次分片上传逻辑的调试过程

前言

在项目开发的过程中,经常会遇到上传和下载,对于上传来说,如果是小文件的话,接口响应会比较快,但是对于大文件,则需要对其分片以减少请求体的大小和上传时间。

小文件上传

以Vue框架使用<el-upload>为例,直接上代码

<template>
  <div>
    <el-upload
      class="upload-demo"
      action="your_upload_api_url"
      :on-success="handleSuccess"
      :before-upload="beforeUpload"
    >
      <el-button size="small" type="primary">点击上传</el-button>
      <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
    </el-upload>
  </div>
</template>

<script>
export default {
  methods: {
    handleSuccess(response, file) {
      // 处理上传成功的逻辑
      console.log(response, file);
    },
    beforeUpload(file) {
      // 在上传之前的操作,例如限制文件类型、大小等
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
      if (!isJPG) {
        this.$message.error('只能上传jpg/png文件');
      }
      const isLt500K = file.size / 1024 < 500;
      if (!isLt500K) {
        this.$message.error('文件大小不能超过500KB');
      }
      return isJPG && isLt500K;
    },
  },
};
</script>

<style scoped>
/* 样式可以根据自己的需求进行调整 */
.upload-demo {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 180px;
}
</style>

在上述代码中:

<el-upload> 组件用于处理文件上传,通过 action 属性指定文件上传的接口。
:on-success 属性绑定一个方法,在文件上传成功后触发。
:before-upload 属性绑定一个方法,在文件上传之前触发,可以在该方法中进行一些操作,如限制文件类型和大小。
元素用于触发文件选择。
请注意替换 your_upload_api_url 为实际的文件上传接口。

分片上传

文件过大时就需要进行文件分片上传,文件分片上传是一种将大文件拆分成小块(分片)并分别上传的策略,这样可以更有效地处理大文件上传,避免一次性上传整个文件可能遇到的网络问题和服务器限制。FormData 对象和一些前端框架/库(如 axios)通常与文件分片上传一起使用。

下面是一个简单的实现示例,使用 FormData 和 axios 进行文件分片上传:

<template>
  <div>
    <input type="file" ref="fileInput" @change="handleFileChange" />
    <button @click="startUpload">开始上传</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      selectedFile: null,
      chunkSize: 1024 * 1024, // 每个分片的大小,这里设置为1MB
    };
  },
  methods: {
    handleFileChange(event) {
      this.selectedFile = event.target.files[0];
    },
    async startUpload() {
      if (!this.selectedFile) {
        alert('请选择文件');
        return;
      }

      // 计算总分片数量
      const totalChunks = Math.ceil(this.selectedFile.size / this.chunkSize);

      // 循环上传分片
      for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
        const start = chunkIndex * this.chunkSize;
        const end = Math.min(start + this.chunkSize, this.selectedFile.size);
        const chunk = this.selectedFile.slice(start, end);

        const formData = new FormData();
        formData.append('file', chunk);
        formData.append('chunkIndex', chunkIndex);
        formData.append('totalChunks', totalChunks);

        try {
          await axios.post('your_chunk_upload_api_url', formData);
          console.log(`分片 ${chunkIndex + 1} / ${totalChunks} 上传成功`);
        } catch (error) {
          console.error(`分片 ${chunkIndex + 1} / ${totalChunks} 上传失败`, error);
          // 处理上传失败的逻辑,可以选择中止上传或重试
          return;
        }
      }

      console.log('文件上传完成');
    },
  },
};
</script>

上面是一个分片上传的示例,在实际操作时遇到了一些问题

分片上传遇到的问题

问题1:请求体过大,如何处理?

项目中遇到的文件最大约1个GB,此时直接上传,会报请求体过大的报错,经过调试后发现文件最大传输为50MB。由于项目是依赖于平台,属于平台的子项目,因此在前后端联调时,前端通过nginx转发到对应的接口上。在nginx配置里,有50M大小的限制,修改后生效。后台同事在排查后台代码及配置也发现了请求不能过大的限制条件,即ingress中设置了请求的大小,最终两者同时修改后生效

nginx中的配置修改如下:
在这里插入图片描述

问题2:前端如何获取上传进度条?

在使用 axios 进行文件上传时,你可以通过配置 onUploadProgress 属性来监听上传进度。onUploadProgress 允许你在上传过程中获取上传进度,并执行相应的操作。

axios.post('your_upload_api_url', formData, {
    onUploadProgress: progressEvent => {
      const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
      console.log(`上传进度: ${percentCompleted}%`);
      // 在这里可以更新进度条或执行其他操作
    },
  })
  .then(response => {
    // 处理上传成功的逻辑
    console.log(response.data);
  })
  .catch(error => {
    // 处理上传失败的逻辑
    console.error('上传失败', error);
  });

需要注意的是,这里的progressEvent.total并不一定和文件的大小相等,处理百分比时,尽量使用total而不是file.size。

问题3:串行上传时文件上传没有问题,但是并行上传时,后台取到的文件片组装后错误?

经过定位,发现是在分片时,最后一片会比较小,如果串行上传时,前端一片片按着顺序依次上传,想优化上传速度,使用并行上传时,最后一片通常都会比前面的分片小,导致最后一片先上传,然后发生组装错误。经过测试,可以采取以下思路:1、前端最后一片在前面的分片都上传完毕后,上传最后一片,可使用for循环配合Promise.all处理;2、后端在拿到所有的分片后再开始组装,而不是边上传边组装。

问题4:进度条上传达到100%后,没有立刻返回结果

这是因为后台接收到文件后,可能还有处理的时间,但是文件已经传到了后台,如果后台没有其他逻辑处理,可以直接返回结果,告知用户上传已完成
最后,放上去部分代码

async handleUpload() {
      const file = this.formData.file;
      // 初始化分片的大小,可以自定义
      const chunkSize = 200 * 1024 * 1024;

      // 计算分片的数量, 传参时会用到
      const chunkCount = Math.ceil(file.size / chunkSize);

      // 用于保存每个分片的信息
      const chunks = [];

      if (file.size > chunkSize) {
        // 分割文件为多个分片
        for(let i = 0; i < chunkCount; i++) {
          const start = i * chunkSize;
          const end = Math.min(file.size, (i + 1) * chunkSize);
          const chunk = file.raw.slice(start, end);
          chunks.push(chunk);
        }
      } else {
        chunks.push(file.raw);
      }

      try {
        this.uploadLoading = true;
        // 创建一个数组来存储每个分片上传的 Promise
        const uploadPromises = [];
        for(let i = 0; i < chunks.length; i++) {
          this.loadedSizeArr[i] = 0;
          this.totalSizeArr[i] = 0;
        }
        const fileFlag = getRandomName();
        let percentage = 1;
        if (chunks.length > 1) {
          percentage = (chunks.length-1) / chunks.length;
        }
        const headers = {
          fileFlag,
          fileName: file.name,
          'Content-Type': 'multipart/form-data',
        }
        // 遍历并上传每个分片
        for(let i = 0; i < Math.max(chunks.length - 1, 1); i++) {
          const formData = new FormData();
          formData.append('file', chunks[i]);
          formData.append('chunkNumber', String(i+1));
          formData.append('totalChunks', String(chunkCount));

          // 创建分片上传的 Promise
          const uploadPromise = util.post('your_upload_api_url', formData, {
            headers,
            onUploadProgress: (progressEvent) => {
              if (progressEvent.lengthComputable) {
                this.loadedSizeArr[i] = progressEvent.loaded;
                this.totalSizeArr[i] = progressEvent.total;
                const loadedSizeTotal = this.loadedSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
                const totalSizeTotal = this.totalSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
                this.percent = Math.round(loadedSizeTotal / totalSizeTotal * percentage * 100);
              }
            },
          })
          // 将 Promise 存储到数组中
          uploadPromises.push(uploadPromise);
        }
        // 使用 Promise.all 来等待所有分片上传完成
        Promise.all(uploadPromises)
          .then(async () => {
            if (chunkCount > 1) {
              const formData = new FormData();
              formData.append('file', chunks[chunks.length-1]);
              formData.append('chunkNumber', String(chunkCount));
              formData.append('totalChunks', String(chunkCount));
              await util.post('your_upload_api_url', formData, {
                headers,
                onUploadProgress: (progressEvent) => {
                  if (progressEvent.lengthComputable) {
                    this.loadedSizeArr[chunks.length-1] = progressEvent.loaded;
                    this.totalSizeArr[chunks.length-1] = progressEvent.total;
                    const loadedSizeTotal = this.loadedSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
                    const totalSizeTotal = this.totalSizeArr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
                    this.percent = Math.round((percentage + loadedSizeTotal / totalSizeTotal / chunkCount) *100);
                  }
                },
              })
            }

            // 所有分片上传完成后执行的逻辑
            this.percent = 100;
            this.uploadLoading = false;
            this.$notify({
              type: 'success',
              title: '成功',
              message:  '上传成功!',
            })
          })
      } catch (e) {
        this.$notify({
          type: 'error',
          title: '失败',
          message: '上传失败!',
        });
      }
    },

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序map视野发生改变时切换定位点

<!--地图--> <view><map id"myMap" style"width: 100%; height: 300px;" latitude"{{latitude}}" longitude"{{longitude}}"scale"{{scale}}" markers"{{markers}}" controls"{{controls}}&q…

VUE篇之日历组件

1.简单日历组件展示 思路&#xff1a;根据当前月的第一天是星期几&#xff0c;来显示日期 <template><div class"wrap"><el-button click"preMonth">上个月</el-button><el-tag>当前年份{{ curYear }}</el-tag><e…

解决msvcr120.dll文件丢失问题

项目场景&#xff1a; 在VMware虚拟机中安装win7家庭版系统&#xff0c;安装MySQL数据库&#xff0c;部署项目文件。 问题描述 安装MySQL数据库过程中提示“msvcr120.dll文件丢失”。 原因分析&#xff1a; 提示丢失msvcr120.dll文件&#xff0c;我们首先要到C:\Windows\Sys…

Windows server 2016 如何禁止系统自动更新

1.打开“运行”&#xff0c;输入cmd&#xff0c;点击“确定”。 2.输入sconfig&#xff0c;然后按回车键。 3.输入5&#xff0c;然后按回车键。 4.示例需要设置为手动更新&#xff0c;即输入M&#xff0c;然后按回车键。 5.出现提示信息&#xff0c;点击“确定”即可。

【owt-server】清理日志:owt、srs、ffmpeg

运行一段时间后,云主机的磁盘满了owt的日志和 srs的日志比较多。查看日志文件占用: du 通过命令du -h –max-depth=1 *,可以查看当前目录下各文件、文件夹 关闭owt-server dist# ./bin/stop-all.sh root@k8s-master-2K4G:~/p2p/zdsvr-20201229/dist# ./bin/stop-all.sh stopp…

JMeter定时器

JMeter定时器 一、同步定时器1、场景2、位置3、参数4、使用 二、常数吞吐量定时器1、场景2、作用3、位置4、参数 三、固定定时器1、场景2、位置3、用例 一、同步定时器 1、场景 1w人同时使用电商网站&#xff1a;相对并发&#xff0c;可用线程组实现1w人同时秒杀&#xff1a;绝…

猫头虎博主深度探索:Amazon Q——2023 re:Invent大会的AI革新之星

猫头虎博主深度探索&#xff1a;Amazon Q——2023 re:Invent大会的AI革新之星 授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科…

Xshell连接虚拟机、设置及使用技巧

【环境系列】Linux虚拟机(Centos、Ubuntu)、云服务器&#xff1a;https://www.cnblogs.com/uncleyong/p/17874484.html Xshell连接虚拟机 登录服务器 查看ip&#xff1a;ip addr 安装xmanager&#xff0c;网盘中有安装包&#xff1b;也可以官网下载Xshell&#xff1a;https://…

百度搜索展现服务重构:进步与优化

作者 | 瞭东 导读 本文将简单介绍搜索展现服务发展过程&#xff0c;以及当前其面临的三大挑战&#xff1a;研发难度高、架构能力欠缺、可复用性低&#xff0c;最后提出核心解决思路和具体落地方案&#xff0c;期望大家能有所收货和借鉴。 全文4736字&#xff0c;预计阅读时间12…

冲压模具市场调研:2023年该行业发展现状及前景分析

汽车冲压件模具是汽车车身生产的重要工艺装备&#xff0c;是汽车换型的主要制约因素。汽车冲压件模具具有尺寸大、型面复杂、精度要求高等特点&#xff0c;属于技术密集型产品。 汽车冲压模具能快速精密地把材料直接加工成零件或半成品并通过焊接、铆接、拼装等工艺装配成零部件…

勒索病毒最新变种.mallox勒索病毒来袭,如何恢复受感染的数据?

导言&#xff1a; 威胁着我们数据安全的勒索病毒如.mallox已经变得愈发狡猾和具有挑战性。本文91数据恢复将深入介绍.mallox勒索病毒的特征、恢复受害数据的方法&#xff0c;以及一些预防措施&#xff0c;助您更好地应对这一威胁。 如果受感染的数据确实有恢复的价值与必要性&…

Nacos配置Mysql数据库

目录 前言1. 配置2. 测试前言 关于Nacos的基本知识可看我之前的文章: Nacos基础版 从入门到精通云服务器 通过docker安装配置Nacos 图文操作以下Nacos的版本为1.1.3 1. 配置 对应的配置文件路径如下: 对应的application.properties为配置文件 需配置端口号 以及 mysql中的…

c语言:初识指针

目录 目录 目录 CPU与内存协同工作 访问内存中的“房间” 基础数据类型怎样居住“房间” 取地址运算符 & 声明指针类型的变量 定义 指针类型 空间大小 小结 使用指针 取值运算符 * 指针类型的大小 强制转换指针类型 CPU与内存协同工作 以整型加法为例&…

Vue3页面如何设置rem单位的依据“根font-size”的两种方式

最近在对项目做整体的自适应。我们可以通过设置meta的viewport属性设置屏幕的缩放&#xff0c;但有时候&#xff0c;屏幕缩放了但字体大小也需要做相应的调整才能达到更好的自适应效果。我们很容易想到使用媒体查询rem来实现字体的自适应。 rem单位&#xff1a;“rem” 是 “ro…

Linux学习教程(第十一章 Linux高级文件系统管理)二

第十一章 Linux高级文件系统管理&#xff08;二&#xff09; 九、Linux如何判断磁盘配额是否生效&#xff1f; 我们的磁盘配额已经生效&#xff0c;接下来测试一下是否会限制我们的用户。以 lamp1 用户为例&#xff0c; 因为 lamp1 用户除容量被限制外&#xff0c;也限制了文…

MyBatis 四大核心组件之 StatementHandler 源码解析

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

利用n_gram进行情感分析

一、思路 二、关键步骤实现 1、利用tf-idf进行特征提取 详见利用tf-idf对特征进行提取-CSDN博客 2、利用svm进行模型训练 详见​​​​​​​​​​​​​​利用svm进行情感分析-CSDN博客

大数据分析与应用实验任务十二

大数据分析与应用实验任务十二 实验目的&#xff1a; 通过实验掌握spark机器学习库本地向量、本地矩阵的创建方法&#xff1b; 熟悉spark机器学习库特征提取、转换、选择方法&#xff1b; 实验任务&#xff1a; 一、逐行理解并参考编写运行教材8.3.1、8.3.3节各个例程代码…

服务器数据恢复-raid5多块磁盘掉线导致上层卷无法挂载的数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器中有一组由24块FC硬盘组建的raid5磁盘阵列&#xff0c;linux操作系统ext3文件系统&#xff0c;服务器上层部署有oracle数据库。 服务器故障&检测&#xff1a; raid5阵列中有两块硬盘出现故障掉线&#xff0c;导致服务器上层卷无法…

中通快递查询,中通快递单号查询,并进行多次揽收分析

批量查询中通快递单号的物流信息&#xff0c;并将其中的多次揽收件分析筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 中通快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的伙伴记得先注册&…