当前位置: 首页 > news >正文

大文件上传如何做断点续传?(分别使用vue、React、java)

大文件上传断点续传的实现逻辑

大文件上传断点续传的核心在于将文件分成多个小片段(分片),逐个上传这些片段到服务器,并记录已成功上传的部分。如果上传中断,可以基于之前保存的状态重新恢复未完成部分的上传过程。

实现逻辑的关键点
  1. 前端处理
    使用浏览器提供的 File APIBlob.slice() 方法来切割文件成若干个小块。每一块可以通过 HTTP 请求发送至服务器。

  2. 唯一标识符
    为了区分不同用户的上传请求以及同一文件的不同分片,通常会为每次上传生成唯一的标识符(UUID 或 MD5 值)。该标识符用于标记当前上传的任务状态。

  3. 进度跟踪与存储
    客户端通过本地缓存机制(如 IndexedDB、LocalStorage)或 Cookies 来记录已完成的分片编号;而服务端则维护一份全局状态表,用来验证哪些分片已经收到并持久化下来。

  4. 错误重试机制
    如果某个分片失败,则允许客户端自动尝试再次提交直到成功为止。这一步骤可通过设置定时器配合 Promise 链式调用来达成目标效果。

  5. 合并操作
    当所有分片都到达服务器之后,由后台程序负责按照顺序拼接回原始数据流形式最后写入磁盘成为完整的文档副本。


Vue3 中的具体实现示例

以下是基于 Vue3 的简单版代码框架:

<template><div><input type="file" @change="handleFileChange"/><button @click="uploadChunks">开始上传</button></div>
</template><script setup>
import { ref } from 'vue';const file = ref(null);
const chunkSize = 1 * 1024 * 1024; // 每次上传大小设为1M字节function handleFileChange(event) {file.value = event.target.files[0];
}async function uploadChunks() {const chunks = [];let currentChunk = 0;while (currentChunk * chunkSize < file.value.size) {const start = currentChunk * chunkSize;const end = Math.min(file.value.size, start + chunkSize);chunks.push({blob: file.value.slice(start, end),index: currentChunk,});currentChunk++;}for (let i = 0; i < chunks.length; i++) {try {await fetch('/api/upload', {method: 'POST',headers: { 'Content-Type': 'application/octet-stream' },body: chunks[i].blob,});console.log(`${i}块上传完毕`);} catch (error) {console.error('上传失败:', error.message);}}
}
</script>

React 中的具体实现示例

下面是一个利用 Hooks 构建的大致流程展示:

import React, { useState } from 'react';function App() {const [selectedFile, setSelectedFile] = useState(null);const handleChange = (event) => {setSelectedFile(event.target.files[0]);};const handleSubmit = async () => {if (!selectedFile) return alert('请选择要上传的文件');const CHUNK_SIZE = 1 * 1024 * 1024; // 设置每个chunk为1MBconst totalChunks = Math.ceil(selectedFile.size / CHUNK_SIZE);for (let i = 0; i < totalChunks; ++i) {const start = i * CHUNK_SIZE;const end = ((i + 1) * CHUNK_SIZE > selectedFile.size ? selectedFile.size : (i + 1) * CHUNK_SIZE);const formData = new FormData();formData.append('file', selectedFile.slice(start, end));formData.append('index', i.toString());try {const response = await fetch("/upload", {method: "POST",body: formData,});if (!response.ok) throw Error(response.statusText);} catch (err) {console.warn(err);}}};return (<><input type="file" onChange={handleChange}/><button onClick={handleSubmit}>Upload File</button></>);
}export default App;

Java Spring Boot 后端接收示例

Spring Boot 提供了强大的 RESTful 支持能力,在这里我们定义了一个简单的接口用以接受来自前端传递过来的数据包:

@RestController
@RequestMapping("/upload")
public class UploadController {private static final String UPLOAD_DIR = "/tmp/uploads/";@PostMapping(consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)public ResponseEntity<String> handleFilePart(@RequestBody byte[] bytes,@RequestParam Integer index,HttpServletRequest request) throws IOException {UUID taskId = extractTaskIdFromRequest(request); Path targetLocation = Paths.get(UPLOAD_DIR).resolve(taskId.toString()).resolve(index + ".part");Files.write(targetLocation, bytes);return ResponseEntity.status(HttpStatus.CREATED).body("Uploaded part:" + index);}private UUID extractTaskIdFromRequest(HttpServletRequest req){/* 解析header或者其他参数获取task id */return null;}@GetMapping("/{taskId}")public void mergeParts(@PathVariable String taskId)throws Exception{List<Path> parts=Files.walk(Paths.get(UPLOAD_DIR)).filter(p->p.toAbsolutePath().toString().contains(taskId+"\\")).sorted().collect(Collectors.toList());RandomAccessFile raf=new RandomAccessFile(new File(UPLOAD_DIR+File.separator+taskId+".final"), "rw");for(Path p:parts){FileInputStream fis=new FileInputStream(p.toString());byte [] buffer=new byte[(int)p.toFile().length()];fis.read(buffer);raf.write(buffer);fis.close();            }raf.close();deleteTempFolderIfNecessary(parts.stream().map(Path::toFile).toArray(File[]::new));      }private void deleteTempFolderIfNecessary(File...filesToDelete){/*清理临时目录*/}
}

相关文章:

  • Scp命令使用
  • PPP 拨号失败:ATD*99***1# ... failed
  • AOP的代理模式
  • 计算机系统结构1-3章节 期末背诵内容
  • 从逻辑视角学习信息论:概念框架与实践指南
  • 软考 组合设计模式
  • 数据可视化利器 - Grafana 与 Prometheus 联手打造监控仪表盘
  • 【闲聊篇】java好丰富!
  • grafana/loki-stack 设置日志保存时间及自动清理
  • 56 在standby待机打通uart调试的方法
  • 基于 Vue3 与 exceljs 实现自定义导出 Excel 模板
  • NV054NV057美光固态闪存NV059NV062
  • 经典密码学和现代密码学的结构及其主要区别(1)维吉尼亚密码—附py代码
  • 49 python Matplotlib之Pandas 数据可视化
  • 动态规划-740.删除并获取节点-力扣(LeetCode)
  • Trae+12306 MCP,10分钟搭建行程可视化助手
  • MDK的编译过程及文件类型全解
  • 同一无线网络下的设备IP地址是否相同?
  • FFTW图像处理入门
  • Jenkins 2.426.2配置“构建历史的显示名称,加上包名等信息“
  • 信息网络设计/上海百度推广优化排名
  • 自己做网站怎么加定位/直通车怎么开效果最佳
  • 无锡手机网站制作费用/互联网去哪里学
  • 互联网网站开发有哪些职位/百度推广后台登陆首页
  • 武汉网站建设设计哪家好/chatgpt网址
  • 做美女图片网站犯法吗/微信视频号小店