前端大文件分片上传解决方案

前言

之前出过一篇【大文件分片下载】,想到要有始有终,但是最近确实有点忙,所以简单写下分片上传,后面有时间再捣鼓完整点

节约时间 直接上代码

前端

上传页面

<template>
  <div id="app">
      <input type="file" @change="handleFile"/>
      <button @click="upload">上传</button>
  </div>
</template>
<script>
import {handleFile,uploadByBlock} from '@/utils/upload.js'
export default {
  name: 'App',
  data(){
    return {
      chunks: []
    }
  },
  methods:{
    async handleFile(e){
      this.chunks = await handleFile(e)
    },
    async upload(){
      await uploadByBlock(this.chunks)
    }
  }
}
</script>

创建worker.js ,实现多线程

// 上传
self.onmessage = function (e) {
    let json = e.data;
    const formData = new FormData();
    formData.append('file', json.file);
    formData.append('fileName', json.fileName);
    formData.append('chunkFileName', json.chunkFileName);
    formData.append('fileSize', json.fileSize);
    formData.append('chunkNum', json.chunkNum);
    
    fetch("/api/upload/block", {
        method: 'POST',
        body: formData
    })
    .then(data => {
        self.postMessage({response: "success" });
    })
    .catch(error => {
        self.postMessage({ error: "error" });
    });
};

创建upload.js

// 每片文件大小,1024*1024*200=200M
const chunkSize = 1024 * 1024 * 200;

// 处理文件
export async function handleFile(e) {
    let file = e.target.files[0];
    const chunks = [],
        fileSize = file.size,
        chunkNum = Math.ceil(fileSize / chunkSize)
    for (let i = 0; i < fileSize; i += chunkSize) {
        chunks.push({
            fileSize,
            chunkNum,
            fileName:file.name,
            chunkFileName:i,
            blob: file.slice(i, i + chunkSize),
        })
    }
    return chunks
}

// 分片上传
export async function uploadByBlock(chunks) {
    const worker = new Worker('worker.js')
    for (let i = 0; i < chunks.length; i++) {
        // 利用worker 多线程处理上传请求
        worker.postMessage(
            {
                'file': chunks[i].blob, // 分片文件
                'fileName': chunks[i].fileName, // 文件名
                'chunkFileName': chunks[i].chunkFileName,  // 分片文件名
                'fileSize': chunks[i].fileSize, // 文件大小
                'chunkNum': chunks[i].chunkNum // 分片数量
            }
        );
    }

    // worker 回传消息
    worker.onmessage = e => {
        const { response, error } = e.data;
        if (error) {
            console.error('Chunk upload failed:', error);
        } else {
            console.log(`uploaded successfully:`, response);
        }
    };

    worker.onerror = e => {
        console.error('Worker error:', e.message);
    };
}

后端

controller.java

package com.leezijin.controller;

import com.leezijin.util.DownloadUtil;
import com.leezijin.util.UploadUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;

@Controller
@RequestMapping("upload")
public class UploadController {

    /**
     *
     * @param file 分片文件
     * @param fileName 完整文件名
     * @param chunkFileName 分片文件名
     * @param fileSize 完整文件大小
     * @param chunkNum 总的分片数量
     * @param request
     * @param response
     * @throws IOException
     */
    @PostMapping("/block")
    public void upload(
            @RequestParam("file") MultipartFile file,
            @RequestParam("fileName") String fileName,
            @RequestParam("chunkFileName") String chunkFileName,
            @RequestParam("fileSize") long fileSize,
            @RequestParam("chunkNum") int chunkNum,
            HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        UploadUtil.uploadByBlock(file,fileName,chunkFileName,fileSize,chunkNum);
    }
}

upload工具类

package com.leezijin.util;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class UploadUtil {

    /**存放上传的分片数据**/
    private static Map<String, List> files = new HashMap<>();
    private static final String BASE_URL = "D:/temp/";

    /**
     * 上传文件
     * @param file
     * @param fileName
     * @param chunkFileName
     * @param fileSize
     * @param chunkNum
     */
    public static void uploadByBlock(MultipartFile file,String fileName,String chunkFileName,long fileSize,int chunkNum) throws IOException {
        String filePath = saveFile(file,fileName,chunkFileName);
        List<String> list = files.get(fileName);
        if (list==null){
            list=new ArrayList<>();
        }
        list.add(filePath);
        files.put(fileName,list);
        // 最后一个分片文件
        if(list.size()==chunkNum){
            // 合并文件
            System.out.println("开始合并:"+new Date());
            // 排序
            Collections.sort(list);
            mergeFiles(list, BASE_URL+fileName+"/"+fileName);
            System.out.println("合并完成:"+new Date());
            // 删除数据
            files.remove(fileName);
        }
    }

    public static String saveFile(MultipartFile file, String fileName, String chunkFileName) throws IOException {
        String tem = BASE_URL+fileName;
        File te = new File(tem);
        if(!te.exists()){
            te.mkdirs();
        }
        String filePath = tem+"/"+chunkFileName;

        try {
            byte[] bytes = file.getBytes();
            Path path = Paths.get(filePath);
            Files.write(path, bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return filePath;
    }


    /**
     * 合并分片文件
     * @param fileParts
     * @param mergedFile
     * @throws IOException
     */
    public static void mergeFiles(List<String> fileParts, String mergedFile) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(mergedFile)) {
            for (String filePart : fileParts) {
                File file = new File(filePart);
                try (FileInputStream fis = new FileInputStream(file)) {
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = fis.read(buffer)) != -1) {
                        fos.write(buffer, 0, bytesRead);
                    }
                }
                // 删除已经读取的分片文件(可选)
                file.delete();
            }
        }
    }
}

结尾

时间太紧,图也就不上了,自己拿到代码试吧

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
前端vue大文件分片上传是一种将大文件切分成多个块进行上传的技术,通过将文件分成若干小块,逐个上传,可以减少重新上传的开销,并提高上传的效率。这种技术可以应对网络不稳定、传输中断等问题,当网络传输中断时,只需要重新传输剩余的分片,而不需要重新上传整个文件,从而节省了传输时间和成本。为了实现断点续传功能,需要与后台接口配合,后台接口负责接收并保存已上传文件块,以便在后续上传时跳过已上传的部分。对于前端vue大文件分片上传,需要后台接口的支持,后台接口需要接收分片并正确组合,这样在上传中断时只需要找到中断的分片,而不用重新上传全部内容,进一步节约成本。具体的实现方案可以根据具体需求和后台接口的支持来进行选择和开发。123 #### 引用[.reference_title] - *1* *2* [Vue实现大文件分片上传,包括断点续传以及上传进度条](https://blog.csdn.net/yjxkq99/article/details/128942133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *3* [前端vue实现大文件分片上传](https://blog.csdn.net/tt18473481961/article/details/128819751)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值