【唐叔学设计】一文带你了解文件上传设计方案

文件上传,可以说是一个绕不开的很基础的后端操作,一般都怎么实现呢?接下来,就让唐叔花几分钟给你做简要的介绍吧。

小文件上传

小文件上传太简单啦,就直接上代码吧,大家直接看实现哈。

1. 代码实现

前端

前端上传文件表单

<!-- resources/templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    <label>文件名:</label><input type="file" name="file"/><br/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>
后端

对于小文件上传,如果后端服务使用的是SpringBoot框架,基本就是配置+MultipartFile轻松一把梭。就如下述代码实现:

(1) 配置文件示例:

# application.properties
spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=2MB
file.upload.path=/uploads/

(2) 后端代码实现:

// FileUploadController.java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Controller
public class FileUploadController {

    @Value("${file.upload.path}")
    private String uploadDir;

    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
        try {
            // Check if the file is empty
            if (file.isEmpty()) {
                redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
                return "redirect:/";
            }

            // Get the file and save it to the directory
            String fileName = file.getOriginalFilename();
            Path targetLocation = Paths.get(uploadDir + fileName);
            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
            redirectAttributes.addFlashAttribute("message", "You successfully uploaded '" + fileName + "'");
        } catch (IOException e) {
            redirectAttributes.addFlashAttribute("message", "Could not upload the file: " + e.getMessage());
        }
        return "redirect:/";
    }
}

2. 文件上传存在的问题

如果项目中的文件基本都是MB级别,那么确实使用这种方式太适合不过了。

不过呢,如果是项目涉及到大文件操作呢?这个时候,还是使用这种方式可能就不够看了。就下面几个场景,使用小文件上传方式分分钟让你放弃上传文件的欲望。

  • 20GB文件,上传了99%,但是突然由于网络波动,导致请求连接断了,哦豁,白忙活了,人生没有重来,但是文件上传可以重来。
  • 5GB文件,假设上传速率是30Mbps,单线程情况下,也要接近20min才能上传完毕,这都够打一局王者荣耀了,好吧,其实劳逸结合也不错。

大文件上传

就上述两个问题,使用大文件分片上传,那解决起来,都是轻轻松松。

  • 20G文件,一次性肯定过大了,那么就切割分片。按50MB进行分片,约400片,最后1%由于网络波动上传失败了,那也就是最后4片失败了,但是前面396片都成功了,ok,那就重新上传最后4片文件就可以了,人生没有重来,但是跌倒了还是可以再出发的。
  • 5GB文件,单线程,可能跑不满带宽,那就上多线程呗,反正都分片了,并发上传又不是不可以,好吧,工作就要有工作的样子,天天想着玩玩玩,咋给老板买奔驰呢(这里只是为了方便理解说”跑不满带宽“,实际上影响上传速率的因素还有很多)。

1. 交互流程

好吧,铺垫了这么多,该上正菜了——大文件分片上传

下面是大文件分片上传交互时序图,实际上如果使用其他存储介质,这里应该还会有存储介质交互,但是基本实现是大差不差的。

大文件上传交互时序图

交互流程说明:

  1. 用户点击上传文件
  2. 接口1:生成任务id
    • 浏览器(前端)携带计算的分片情况(如需要进行100次分片),请求后端为此次用户上传的文件生成分片任务id。
    • 后端生成任务id,入库后,返回给前端
  3. 接口2:上传分片文件
    • 浏览器(前端)使用API切割文件,分片上传文件
    • 后端接收分片文件,并生成分片文件信息,入库后,响应给前端
  4. 接口3:合并文件
    • 浏览器(前端)异步请求合并文件
    • 后端校验文件完整性,如完整,则返回校验合并情况,并异步执行合并操作
  5. 接口4:查询文件合并进度
    • 浏览器(前端)循环查询文件合并进度
    • 后端进行合并操作中,如未完成,则返回未完成状态;反之,完成合并并删除数据库分片信息
  6. 响应用户上传成功

2. 使用场景答疑

  • 上述方案怎么解决”断点续传“问题呢?

    就此前提及的20G文件,上传99%,但是由于网络波动,导致上传最后1%失败了。使用分片上传方案,由于存在分片信息落库,可以获取到落库分片信息,在请求合并时,校验分片数是否和生成任务id时前端提供的分片数吻合,吻合则进行合并操作,反之,则报错提示缺少的分片。由前端补充缺失分片的上传。

  • 和小文件上传相比,如何提速呢?

    实际上,如果只是接口上看,分片上传方案会涉及四个接口,看着远比小文件上传复杂。
    但是当文件大小达到一定量级时,该方案的性能才能明确地在分片上传这一步体现出来,小文件上传只能是单线程处理,而分片方案是批量分片请求,相当于并发多线程处理,因而会有明显优势。

  • 重复上传校验。

    在分片上传时,携带分片编号,上传后会对分片信息(包括分片编号)落库,因而重复上传时,可以根据分片编号来避免重复上传。
    当然,如果考虑完整性,也可以通过计算文件哈希值来作为分片编号,进而避免重复上传。

  • 文件完整性校验。

    可以考虑使用文件MD5值来作为文件完整性校验,在分割文件前,计算MD5值,并发送服务端落库。
    合并完成后,可以对合并后的文件进行MD5值校验,再将MD5值和数据库存储的MD5值比较,相同即标识文件是完整的,未被损坏或篡改。

  • 文件分片大小推荐多少呢?

    出于对网络环境稳定性、服务器并发请求处理能力、内存限制等的综合考虑,分片大小不应该过小或过大,推荐是1MB到10MB之间。这个范围既能保证较好的上传成功率,又能维持相对较低的服务器负载。

  • 大文件都存放服务器吗?

    实际上,一般公司都会考虑进行文件存储和请求处理进行分离操作,将文件存储到专门的存储介质,如NAS或对象存储;服务器更专注于处理用户请求,只对文件进行临时中转处理,本身服务器不存储文件。PS:类似S3之类的对象存储,其客户端SDK其实也已经对分片上传进行了实现,因而如果使用S3等对象存储,可以直接考虑使用其对应的SDK的API进行请求处理即可。

3. 部分核心实现说明

1) 前端文件分片实现
文件分片的基本原理
  • 定义块大小:首先你需要确定每个分片(chunk)的大小,这取决于你的网络条件、服务器配置等因素。通常选择几兆字节作为分片大小。
  • 读取文件:使用JavaScript的FileReader对象来读取文件内容,并将其分割成多个小块。
  • 生成唯一的标识符:为每个文件生成一个唯一ID,用于跟踪所有分片,保证它们可以正确地重组回原文件。
  • 逐个上传分片:通过AJAX请求或者WebSocket等技术将这些分片逐一发送到服务器端。
  • 合并分片:在服务器端接收到所有的分片后,按照一定的规则将它们重新组合成完整的文件。
前端代码示例
async function uploadFile(file, chunkSize = 1024 * 1024) {
    const fileId = generateUniqueID(); // 生成唯一文件ID
    let offset = 0;

    while (offset < file.size) {
        const chunk = file.slice(offset, offset + chunkSize);
        await sendChunkToServer(chunk, fileId, offset / chunkSize);
        offset += chunkSize;
    }

    async function sendChunkToServer(chunk, fileId, index) {
        const formData = new FormData();
        formData.append('file', chunk, `${fileId}-${index}`);
        formData.append('fileId', fileId);
        formData.append('index', index);

        try {
            const response = await fetch('/upload-chunk', {
                method: 'POST',
                body: formData,
            });
            if (!response.ok) throw new Error('Network response was not ok');
        } catch (error) {
            console.error('There was a problem with the fetch operation:', error);
        }
    }
}
现成工具推荐

Web Uploaders

  • Resumable.js: 这是一个支持断点续传和并发上传的JavaScript库,非常适合用来做文件分片上传。它会自动处理文件切片、并发控制等问题。
  • Uppy: Uppy 是一个现代化的文件上传器,具有丰富的插件系统,可以轻松集成到项目中。它不仅支持分片上传,还提供了进度条显示等功能。
  • Tus.io: Tus 是一个开放协议,旨在简化大文件上传过程中的复杂性,包括但不限于分片上传、断点续传等功能。有多种语言的客户端和服务端实现可供选择。

Browser APIs

  • Blob/File API: 如前所述,你可以直接利用浏览器内置的 FileReaderBlob 对象来进行文件读取与分片操作。
  • XMLHttpRequest Level 2: 支持发送二进制数据(如ArrayBuffer),可用于构建自定义的上传机制。
其他:待完善

很想写完完整的,甚至提供demo的,但是时间有限啊。还想赶一波流量券推广呢(主要是有流量券没用完,再写几篇其他的蹭流量券hhh)。

好吧,确实只是有点懒,太晚了,想找个借口不写了,下次空了再完善啦。下次一定[手动狗头]。


当然,如果觉得唐叔写得还不错,欢迎点赞收藏哦。如果存在误导的,也欢迎在评论区指出哦。涉及唐叔更多资讯,欢迎关注唐叔的相关博客账号、微信公众号哦,全网唯一IP——【唐叔在学习】。拜拜拜拜,下次见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值