大文件上传如何做断点续传?全端+后端结合开发

简介: 大文件上传如何做断点续传?全端+后端结合开发

断点续存是什么?

断点续传(Resumable File Upload)是一种文件上传的技术,它允许在上传过程中出现中断或失败的情况下,能够从中断的位置继续上传,而不需要重新上传整个文件。这在处理大文件或不稳定的网络连接时非常有用。

断点续传的实现通常涉及以下几个关键概念和步骤:

  1. 分片:将大文件分割成较小的文件块(通常是固定大小的块),每个块都有一个唯一的标识符。
  2. 上传请求:客户端发起上传请求,并将文件分片按顺序上传到服务器。
  3. 上传状态记录:服务器端需要记录上传的状态,包括已接收的分片、分片的顺序和完整文件的大小等信息。
  4. 中断处理:如果上传过程中发生中断(例如网络中断、用户主动中止等),客户端可以记录已上传的分片信息,以便在恢复上传时使用。
  5. 恢复上传:当上传中断后再次开始上传时,客户端可以发送恢复上传请求,并将已上传的分片信息发送给服务器。
  6. 服务器处理:服务器接收到恢复上传请求后,根据已上传的分片信息,判断哪些分片已经上传,然后继续接收剩余的分片。
  7. 合并文件:当所有分片都上传完成后,服务器将所有分片按顺序组合成完整的文件。

下面是一个简化的断点续传流程图:

客户端                                服务器
  |                                      |
  |------ 发起上传请求 ------------------>|
  |                                      |
  |------ 上传分片1 -------------------->|
  |                                      |
  |------ 上传分片2 -------------------->|
  |                                      |
  |                ...                   |
  |                                      |
  |------ 上传分片N -------------------->|
  |                                      |
  |------ 中断或失败 -------------------->|
  |                                      |
  |------ 发起恢复上传请求 --------------->|
  |                                      |
  |------ 发送已上传分片信息 ------------>|
  |                                      |
  |                ...                   |
  |                                      |
  |------ 上传剩余分片 ------------------>|
  |                                      |
  |------ 上传完成 ---------------------->|
  |                                      |
  |------ 合并分片为完整文件 ------------>|
  |                                      |
  |<----- 上传成功响应 --------------------|
  |                                      |

上述流程图描述了客户端和服务器之间的交互过程。客户端发起上传请求,并逐个上传分片,如果中断或失败,客户端可以恢复上传并将已上传的分片信息发送给服务器。服务器根据已上传的分片信息,继续接收剩余的分片。当所有分片上传完成后,服务器将它们合并成完整的文件,并向客户端发送上传成功的响应。

断点续传技术可以提高文件上传的可靠性和效率,特别是在处理大文件或不稳定的网络环境时。它可以减少重新上传的数据量,节省带宽和时间,并提供更好的用户体验

断点续传实现

1.前端对文件进行分块

2.前端使用多线程上传分片,上传前给服务器发送消息验证当前分片是否已经上传。

3.所有分片上传完毕后,发送合并分片请求,校验文件的完整性。 (上传的分片应该具备顺序标记)

4.前端给服务器传一个MD5值,服务器合并文件后,利用MD5值计算是否与源文件一致。如果不一致,说明文件需要重新上传。

分片文件清理问题:

   在数据库中有一张文件表记录minIo中存储的文件信息

   文件开始上传时会写入文件表,状态为上传中,上传完成会更新状态为上传完成

   当一个文件传了一半不再上传了,说明该文件没有上传完成,通过定时任务去查询文件表中的记录,如果文件距离上次上传结束超过24小时,则可以考虑清除MinIo中相关的分片数据

原理能是一大堆,代码如何去实现呢?

在这我使用的是前段vue3 + 后端的是基于tp5开发的fastadmin框架

前端部分:

<template>
  <div>
    <input type="file" @change="selectFile" />
    <button @click="upload">上传</button>
    <progress :value="progress" :max="100"></progress>
  </div>
</template>

script部分:

<template>
  <div>
    <input type="file" @change="selectFile" />
    <!-- <button @click="upload" :disabled="disabled">上传</button> -->
    <progress :value="progress" :max="100"></progress>
    <button @click="toggleUpload">{{ isUploading ? '停止上传' : '开始上传' }}</button>
  </div>
</template>
<script setup>
import axios from 'axios';
import qs from 'qs';
import { ref, watch ,onMounted } from 'vue';
import { MD5, enc } from 'crypto-js';
const disabled = ref(false);
const serializedData = qs.stringify();
const tableData = ref([]);
axios.post('/api/uploadss/index', serializedData)
  .then(response => {
    console.log(response.data);
    tableData.value = response.data;
  })
  .catch(error => {
    console.error(error);
  });
const encodeMD5 = (md5Hash) => {
  const part1 = md5Hash.substr(0, 6);
  const part2 = md5Hash.substr(6, 6);
  const part3 = md5Hash.substr(12, 6);
  const part4 = md5Hash.substr(18, 6);
  const part5 = md5Hash.substr(24, 8);
  return `${part1}-${part2}-${part3}-${part4}-${part5}`;
};
const file = ref(null);
const progress = ref(0);
const isUploading = ref(false);
let filename;
const selectFile = (event) => {
  file.value = event.target.files[0];
  filename = `.${file.value.name.split('.')[1]}`;
};
let chunkSize;
let totalChunks;
let currentChunk;
let chunkIdValue;
const uploadChunk = (start, end) => {
  const formData = new FormData();
  const reader = new FileReader();
  reader.onload = () => {
    const imageData = reader.result;
    if (currentChunk == 0) {
      chunkIdValue = MD5(imageData);
    }
    formData.append('file', new File([file.value.slice(start, end)], filename));
    formData.append('action', currentChunk === totalChunks ? 'merge' : 'clean');
    formData.append('chunkindex', currentChunk);
    formData.append('chunkid', encodeMD5(chunkIdValue.toString(enc.Hex)));
    formData.append('chunkcount', totalChunks);
    formData.append('filename', filename);
    axios.post('/api/uploadss/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      onUploadProgress: (progressEvent) => {
        const progressValue = Math.round((progressEvent.loaded / progressEvent.total) * 100);
        progress.value = progressValue;
      },
    })
      .then((res) => {
        currentChunk++;
        if (currentChunk <= totalChunks && isUploading.value) {
          const start = currentChunk * chunkSize;
          const end = Math.min(start + chunkSize, file.value.size);
          uploadChunk(start, end);
        } else {
          console.log(res);
          console.log('上传完成');
        }
      })
      .catch((error) => {
        console.error('上传失败:', error);
      });
  };
  reader.readAsDataURL(file.value.slice(start, end));
};
const upload = () => {
  if (!file.value) {
    return;
  }
  console.log(1111);
  chunkSize = 2 * 1024 * 1024; // 设置分片大小为8MB
  totalChunks = Math.ceil(file.value.size / chunkSize);
  currentChunk = 0;
  chunkIdValue;
  const start = 0;
  const end = Math.min(chunkSize, file.value.size);
  uploadChunk(start, end);
  return uploadChunk;
};
const toggleUpload = () => {
  isUploading.value = !isUploading.value;
  if (isUploading.value) {
    // 继续上传时,从存储中读取记录并恢复上传状态
    const uploadRecord = localStorage.getItem('uploadRecord');
    if (uploadRecord) {
      const { currentChunk, progressValue } = JSON.parse(uploadRecord);
      progress.value = progressValue;
      uploadChunk(currentChunk * chunkSize, file.value.size);
    }
  } else {
    // 停止上传时,保存当前的分片索引和进度值到本地存储中
    const uploadRecord = JSON.stringify({
      currentChunk: currentChunk - 1,
      progressValue: progress.value,
    });
    localStorage.setItem('uploadRecord', uploadRecord);
  }
};
watch(isUploading, (value) => {
  if (!value) {
    // 中断上传并保存记录
    console.log('停止上传');
    const uploadRecord = JSON.stringify({
      currentChunk: currentChunk - 1,
      progressValue: progress.value,
    });
    localStorage.setItem('uploadRecord', uploadRecord);
  } else {
    // 恢复上传
    console.log('继续上传');
    upload();
  }
});
onMounted(() => {
  const handleOnline = () => {
    console.log('网络已恢复,继续上传');
    // if (file.value && progress.value !== 100) {
    isUploading.value = true;
    upload();
    // }
  };
  const handleOffline = () => {
    isUploading.value = false;
    console.log('网络中断,停止上传');
  };
  window.addEventListener('online', handleOnline);
  window.addEventListener('offline', handleOffline);
  return () => {
    window.removeEventListener('online', handleOnline);
    window.removeEventListener('offline', handleOffline);
  };
});
</script>

这些是vue3的script部分其中要注意的是参数的定义,在这里我使用的实例formeData进行传值向后端的接口发起请求,我们这里有调用了一个reader.onload的一个函数用来进行这个异步加载来访问接口.其中他的action的值他其他的值不同与其他的值不同(本来是可用不同的值,fastadmin框架需要这种类型的值)因为上面有一个前段的Md5的36为字符的加密想要使用需要在你的终端上输入

npm install crypto-js //下载md5算法的依赖

这里我们前段的值就传递了大不多了,其中有formData.append('filename', filename);这个值一开始是需要后端返回过来的,但实际上并不是,它是由前端传递他的后缀名;这些值传递完成后就是后端的操作流程到这里前端部分就完成了

后端部分(前面说过了使用的是fastadmin框架完成的后端,也就是需要去改框架代码);

这里我就编写一些需要改的连接以及调用的接口;

public function upload()
    {
        Config::set('default_return_type', 'json');
        //必须设定cdnurl为空,否则cdnurl函数计算错误
        Config::set('upload.cdnurl', '');
        $chunkid = $this->request->post("chunkid");
        if ($chunkid) {
            if (!Config::get('upload.chunking')) {
                $this->error(__('Chunk file disabled'));
            }
            $action = $this->request->post("action");
            $chunkindex = $this->request->post("chunkindex/d");
            $chunkcount = $this->request->post("chunkcount/d");
            $filename = $this->request->post("filename");
            $method = $this->request->method(true);
            if ($action == 'merge') {
                $attachment = null;
                //合并分片文件
                try {
                    $upload = new Upload();
                    $attachment = $upload->merge($chunkid, $chunkcount, $filename);
                } catch (UploadException $e) {
                    // return 111;
                    $this->error($e->getMessage());
                }
                $this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
            } elseif ($method == 'clean') {
                //删除冗余的分片文件
                try {
                    $upload = new Upload();
                    $upload->clean($chunkid);
                } catch (UploadException $e) {
                    $this->error($e->getMessage());
                }
                $this->success();
            } else {
                //上传分片文件
                //默认普通上传文件
                $file = $this->request->file('file');
                try {
                    $upload = new Upload($file);
                    return $upload->chunk($chunkid, $chunkindex, $chunkcount);
                } catch (UploadException $e) {
                    $this->error($e->getMessage());
                }
            }
        }
        else {
            $attachment = null;
            //默认普通上传文件
            $file = $this->request->file('file');
            try {
                $upload = new Upload($file);
                $attachment = $upload->upload();
            } catch (UploadException $e) {
                $this->error($e->getMessage());
            }
            $this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
        }
    }

这里后端的接口(不要复制了,这里的代码在application/admin/controller/Ajax.php文件),因为使用的它自带的admin的上传(修改的幅度较大)根据情况也可以使用api的上传;首先我们要去打开多图片上传(application/extra/upload.php)  修改 'chunking'  => false,改为true;根据上面所需要的值进行传值;这些我们在前段已经定义过了主要去说一下filename的值,这里有个判断是前段定义的,当action的值为不为merge访问chunk方法(地址:application/common/library)这里面都是upload等方法,包括处理分片合并,以及向public/uploads移入都可以.

以上就是简单点断点续传的示例

相关文章
|
19天前
|
API 数据库 开发者
构建高效可靠的微服务架构:后端开发的新范式
【4月更文挑战第8天】 随着现代软件开发的复杂性日益增加,传统的单体应用架构面临着可扩展性、维护性和敏捷性的挑战。为了解决这些问题,微服务架构应运而生,并迅速成为后端开发领域的一股清流。本文将深入探讨微服务架构的设计原则、实施策略及其带来的优势与挑战,为后端开发者提供一种全新视角,以实现更加灵活、高效和稳定的系统构建。
23 0
|
1月前
|
负载均衡 测试技术 持续交付
高效后端开发实践:构建可扩展的微服务架构
在当今快速发展的互联网时代,后端开发扮演着至关重要的角色。本文将重点探讨如何构建可扩展的微服务架构,以及在后端开发中提高效率的一些实践方法。通过合理的架构设计和技术选型,我们可以更好地应对日益复杂的业务需求,实现高效可靠的后端系统。
|
1月前
|
监控 Kubernetes 持续交付
构建高效可扩展的微服务架构:后端开发实践指南
在数字化转型的浪潮中,企业对软件系统的要求日益提高,追求快速响应市场变化、持续交付价值成为核心竞争力。微服务架构以其灵活性、模块化和独立部署的特点,成为解决复杂系统问题的有效途径。本文将深入探讨如何构建一个高效且可扩展的微服务架构,涵盖关键设计原则、技术选型及实践案例,为后端开发者提供一条清晰的指导路线,帮助其在不断变化的技术环境中保持竞争力。
133 3
|
7天前
|
消息中间件 监控 持续交付
构建高效微服务架构:后端开发的进阶之路
【4月更文挑战第20天】 随着现代软件开发的复杂性日益增加,传统的单体应用已难以满足快速迭代和灵活部署的需求。微服务架构作为一种新兴的分布式系统设计方式,以其独立部署、易于扩展和维护的特点,成为解决这一问题的关键。本文将深入探讨微服务的核心概念、设计原则以及在后端开发实践中如何构建一个高效的微服务架构。我们将从服务划分、通信机制、数据一致性、服务发现与注册等方面入手,提供一系列实用的策略和建议,帮助开发者优化后端系统的性能和可维护性。
|
28天前
|
监控 Java 开发者
构建高效微服务架构:后端开发的新范式
在数字化转型的浪潮中,微服务架构以其灵活性、可扩展性和容错性成为企业技术战略的关键组成部分。本文深入探讨了微服务的核心概念,包括其设计原则、技术栈选择以及与容器化和编排技术的融合。通过实际案例分析,展示了如何利用微服务架构提升系统性能,实现快速迭代部署,并通过服务的解耦来提高整体系统的可靠性。
|
1月前
|
机器学习/深度学习 人工智能 搜索推荐
未来人工智能在后端开发中的应用前景
随着人工智能技术的不断发展,后端开发领域也迎来了新的机遇与挑战。本文探讨了人工智能在后端开发中的应用前景,分析了其对传统开发模式的影响和未来发展趋势。
|
1月前
|
监控 数据管理 API
构建高效微服务架构:后端开发的新趋势
在现代软件开发领域,随着业务需求的不断复杂化以及敏捷迭代的加速,传统的单体应用架构逐渐暴露出其局限性。微服务架构作为一种新的解决方案,以其高度模块化、独立部署和可扩展性,正成为后端开发领域的新趋势。本文将探讨微服务架构的核心概念,分析其优势与面临的挑战,并提供实施高效微服务的策略和最佳实践,帮助读者理解如何利用这一架构模式提升系统的可靠性、灵活性和可维护性。
137 5
|
1月前
|
运维 Cloud Native 云计算
未来趋势:云原生技术在后端开发中的应用
随着云计算技术的快速发展,云原生技术作为一种新兴的软件架构理念,在后端开发领域日益受到关注。本文将探讨云原生技术的基本概念、优势以及在后端开发中的应用,展望未来云原生技术对于软件开发的影响和发展趋势。
|
1月前
|
人工智能 运维 监控
构建高性能微服务架构:现代后端开发的挑战与策略构建高效自动化运维系统的关键策略
【2月更文挑战第30天】 随着企业应用的复杂性增加,传统的单体应用架构已经难以满足快速迭代和高可用性的需求。微服务架构作为解决方案,以其服务的细粒度、独立性和弹性而受到青睐。本文将深入探讨如何构建一个高性能的微服务系统,包括关键的设计原则、常用的技术栈选择以及性能优化的最佳实践。我们将分析微服务在处理分布式事务、数据一致性以及服务发现等方面的挑战,并提出相应的解决策略。通过实例分析和案例研究,我们的目标是为后端开发人员提供一套实用的指南,帮助他们构建出既能快速响应市场变化,又能保持高效率和稳定性的微服务系统。 【2月更文挑战第30天】随着信息技术的飞速发展,企业对于信息系统的稳定性和效率要求
|
3天前
|
持续交付 API 开发者
构建高效微服务架构:后端开发的新范式
【4月更文挑战第24天】 随着现代软件系统的复杂性日益增加,传统的单体应用已难以满足快速迭代与灵活扩展的需求。微服务架构作为一种新兴的软件开发模式,以其服务的细粒度、独立部署和弹性伸缩等优势,正在逐渐成为后端开发的重要趋势。本文将深入探讨微服务架构的设计原则、关键技术以及在实际业务中的应用实践,旨在为后端开发者提供构建和维护高效微服务架构的参考指南。