国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【java】java實(shí)現(xiàn)大文件的分片上傳與下載(springboot+vue3)

這篇具有很好參考價(jià)值的文章主要介紹了【java】java實(shí)現(xiàn)大文件的分片上傳與下載(springboot+vue3)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

源碼:

  • https://gitee.com/gaode-8/big-file-upload

演示視頻

  • https://www.bilibili.com/video/BV1CA411f7np/?vd_source=1fe29350b37642fa583f709b9ae44b35

1.1 項(xiàng)目背景

對(duì)于超大文件上傳我們可能遇到以下問(wèn)題
? 大文件直接上傳,占用過(guò)多內(nèi)存,可能導(dǎo)致內(nèi)存溢出甚至系統(tǒng)崩潰
? 受網(wǎng)絡(luò)環(huán)境影響,可能導(dǎo)致傳輸中斷,只能重新傳輸
? 傳輸時(shí)間長(zhǎng),用戶無(wú)法知道傳輸進(jìn)度,用戶體驗(yàn)不佳

1.2 項(xiàng)目目標(biāo)

對(duì)于上述問(wèn)題,我們需要對(duì)文件做分片傳輸。分片傳輸就是把文件分割成許多較小的文件,然后分多次上傳,最后再完成合并。

受網(wǎng)絡(luò)環(huán)境影響,我們還要實(shí)現(xiàn)斷點(diǎn)續(xù)傳,以節(jié)省傳輸時(shí)間和資源。斷點(diǎn)續(xù)傳就是已經(jīng)上傳或者下載過(guò)的文件分片不再傳輸。

對(duì)于已經(jīng)上傳過(guò)的文件,可以不再上傳,實(shí)現(xiàn)秒傳。秒傳就是根據(jù)文件的唯一標(biāo)識(shí),確認(rèn)是否需要上傳。
實(shí)現(xiàn)多任務(wù)上傳或下載。多任務(wù)就是同時(shí)多個(gè)文件上傳或下載。

2.1 業(yè)務(wù)流程

用戶上傳文件的流程圖如圖1所示,用戶首先選擇要上傳的文件,上傳過(guò)程中可以選擇暫?;蚶^續(xù)上傳。
java分片下載,vue3,java,spring boot,spring,vue

用戶上傳文件的流程圖如圖2所示,用戶首先可以瀏覽可以下載的文件列表,然后點(diǎn)擊下載,下載過(guò)程中可以選擇暫?;蚶^續(xù)下載

java分片下載,vue3,java,spring boot,spring,vue

2.2 系統(tǒng)用例

系統(tǒng)用例圖如圖3所示,用戶可以上傳文件,在文件上傳過(guò)程中可以查看文件的上傳進(jìn)度和速度,也可以暫停或開(kāi)始上傳;用戶可以查看已經(jīng)上傳過(guò)的,也就是可以下載的文件列表;用戶可以下載文件,在下載過(guò)程中可以查看文件下載的速度和進(jìn)度,用戶可以暫?;蜷_(kāi)始下載。
java分片下載,vue3,java,spring boot,spring,vue

2.3 系統(tǒng)總體功能

系統(tǒng)總體功能圖如圖4所示,分為上傳和下載。上傳包括秒傳,分片上傳,斷點(diǎn)續(xù)傳,多任務(wù)。下載包括分片下載,斷點(diǎn)續(xù)傳,多任務(wù)。

java分片下載,vue3,java,spring boot,spring,vue

3.1 技術(shù)選型

后端:
? 語(yǔ)言:Java8
? 框架:SpringBoot2.6
? 開(kāi)發(fā)工具:Idea 2021
前端:
? 語(yǔ)言:Html5、css3、JavaScript
? 框架:Vue3
? 開(kāi)發(fā)工具:Vscode、Edge
數(shù)據(jù)庫(kù):
? mysql8

4.1 文件上傳模塊

文件上傳模塊的流程圖如圖6所示,順序圖如圖7所示
首先前端讀取文件生成文件的唯一標(biāo)識(shí)MD5,這里采用常用的MD5生成框架:spark-md5.js。對(duì)于大文件一次性讀取比較慢,而且容易造成瀏覽器崩潰,因此這里采用分片讀取的方式計(jì)算MD5。

然后向服務(wù)器發(fā)送請(qǐng)求,查看該文件時(shí)候已經(jīng)上傳,如果已經(jīng)上傳,就提示用戶已經(jīng)秒傳。
如果數(shù)據(jù)庫(kù)中沒(méi)有記錄該文件,就表示該文件沒(méi)有上傳或沒(méi)有上傳完成,那么服務(wù)器就查詢并返回記錄的chunk分片列表。

async 和 await配可以實(shí)現(xiàn)等待異步函數(shù)計(jì)算完成

//計(jì)算文件的md5值
function computeMd5(file, uploadFile) {
  return new Promise((resolve, reject) => {
    //分片讀取并計(jì)算md5

    const chunkTotal = 100; //分片數(shù)
    const chunkSize = Math.ceil(file.size / chunkTotal);
    const fileReader = new FileReader();
    const md5 = new SparkMD5();
    let index = 0;
    const loadFile = (uploadFile) => {
      uploadFile.parsePercentage.value = parseInt((index / file.size) * 100);
      const slice = file.slice(index, index + chunkSize);
      
      fileReader.readAsBinaryString(slice);
    };
    loadFile(uploadFile);
    fileReader.onload = (e) => {
      md5.appendBinary(e.target.result);
      if (index < file.size) {
        index += chunkSize;
        loadFile(uploadFile);
      } else {
        // md5.end() 就是文件md5碼
        resolve(md5.end());
      }
    };
  });
}
//檢查文件是否存在
function checkFile(md5) {
  return request({
    url: "/check",
    method: "get",
    params: {
      md5: md5,
    },
  });
}
//文件上傳之前,el-upload自動(dòng)觸發(fā)
async function beforeUpload(file) {
  console.log("2.上傳文件之前");


  var uploadFile = {};
  uploadFile.name = file.name;
  uploadFile.size = file.size;
  uploadFile.parsePercentage = ref(0);
  uploadFile.uploadPercentage = ref(0);
  uploadFile.uploadSpeed = "0 M/s";
  uploadFile.chunkList = null;
  uploadFile.file = file;
  uploadFile.uploadingStop = false;
  uploadFileList.value.push(uploadFile);

  var md5 = await computeMd5(file, uploadFile);//async 和 await配可以實(shí)現(xiàn)等待異步函數(shù)計(jì)算完成
  uploadFile.md5 = md5;

  var res = await checkFile(md5);  //上傳服務(wù)器檢查,以確認(rèn)是否秒傳
  var data = res.data.data;

  if (!data.isUploaded) {
    uploadFile.chunkList = data.chunkList;
    uploadFile.needUpload = true;
  } else {
    uploadFile.needUpload = false;
    uploadFile.uploadPercentage.value = 100;

    console.log("文件已秒傳");
    ElMessage({
      showClose: true,
      message: "文件已秒傳",
      type: "warning",
    });
  }


}

前端分片請(qǐng)求文件,如果分片編號(hào)被包含在分片列表內(nèi),就標(biāo)識(shí)該分片已經(jīng)上傳,跳過(guò);反之,表示還未上傳,那么前端通過(guò)file的slice方法分割文件,向服務(wù)端傳遞。同時(shí)在頁(yè)面上顯示上傳進(jìn)度和速度。

服務(wù)端,收到前端的分片文件后,通過(guò)Java的RandomAccess類(隨機(jī)讀寫類),從文件的指定位置,寫入指定字節(jié),并記錄chunk到數(shù)據(jù)庫(kù),如果是最后一個(gè)分片再記錄file到數(shù)據(jù)庫(kù)。
java分片下載,vue3,java,spring boot,spring,vue

圖6 文件上傳流程圖
java分片下載,vue3,java,spring boot,spring,vue

圖7 文件上傳順序圖

前端代碼

<template>
  <div class="main">
    <!-- 文件上傳按鈕 -->
    <el-upload
      action="#"
      :http-request="upload"
      :before-upload="beforeUpload"
      :show-file-list="false"
    >
      <el-button type="primary">選擇上傳文件</el-button>
    </el-upload>

    <el-divider content-position="left">上傳列表</el-divider>
    <!-- 正在上傳的文件列表 -->
    <div class="uploading" v-for="uploadFile in uploadFileList">
      <span class="fileName">{{ uploadFile.name }}</span>
      <span class="fileSize">{{ formatSize(uploadFile.size) }}</span>

      <div class="parse">
        <span>解析進(jìn)度: </span>
        <el-progress
          :text-inside="true"
          :stroke-width="16"
          :percentage="uploadFile.parsePercentage"
        >
        </el-progress>
      </div>
      <div class="progress">
        <span>上傳進(jìn)度:</span>

        <el-progress
          :text-inside="true"
          :stroke-width="16"
          :percentage="uploadFile.uploadPercentage"
        >
        </el-progress>
        <span
          v-if="
            (uploadFile.uploadPercentage > 0) &
            (uploadFile.uploadPercentage < 100)
          "
        >
          <span class="uploadSpeed">{{ uploadFile.uploadSpeed }}</span>

          <el-button circle link @click="changeUploadingStop(uploadFile)">
            <el-icon size="20" v-if="uploadFile.uploadingStop == false"
              ><VideoPause
            /></el-icon>
            <el-icon size="20" v-else><VideoPlay /></el-icon>
          </el-button>
        </span>
      </div>
    </div>
  </div>
</template>

<script setup>
import emitter from "../utils/eventBus.js";
import { ElMessage } from "element-plus";
import SparkMD5 from "spark-md5";
import { VideoPause, VideoPlay } from "@element-plus/icons-vue";
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
const { appContext } = getCurrentInstance();
const request = appContext.config.globalProperties.request;
var uploadFileList = ref([]);

//換算文件的大小單位
function formatSize(size) {
  //size的單位大小k

  var unit;
  var units = [" B", " K", " M", " G"];
  var pointLength = 2;
  while ((unit = units.shift()) && size > 1024) {
    size = size / 1024;
  }
  return (
    (unit === "B"
      ? size
      : size.toFixed(pointLength === undefined ? 2 : pointLength)) + unit
  );
}
//計(jì)算文件的md5值
function computeMd5(file, uploadFile) {
  return new Promise((resolve, reject) => {
    //分片讀取并計(jì)算md5

    const chunkTotal = 100; //分片數(shù)
    const chunkSize = Math.ceil(file.size / chunkTotal);
    const fileReader = new FileReader();
    const md5 = new SparkMD5();
    let index = 0;
    const loadFile = (uploadFile) => {
      uploadFile.parsePercentage.value = parseInt((index / file.size) * 100);
      const slice = file.slice(index, index + chunkSize);
      
      fileReader.readAsBinaryString(slice);
    };
    loadFile(uploadFile);
    fileReader.onload = (e) => {
      md5.appendBinary(e.target.result);
      if (index < file.size) {
        index += chunkSize;
        loadFile(uploadFile);
      } else {
        // md5.end() 就是文件md5碼
        resolve(md5.end());
      }
    };
  });
}
//檢查文件是否存在
function checkFile(md5) {
  return request({
    url: "/check",
    method: "get",
    params: {
      md5: md5,
    },
  });
}
//文件上傳之前,el-upload自動(dòng)觸發(fā)
async function beforeUpload(file) {
  console.log("2.上傳文件之前");


  var uploadFile = {};
  uploadFile.name = file.name;
  uploadFile.size = file.size;
  uploadFile.parsePercentage = ref(0);
  uploadFile.uploadPercentage = ref(0);
  uploadFile.uploadSpeed = "0 M/s";
  uploadFile.chunkList = null;
  uploadFile.file = file;
  uploadFile.uploadingStop = false;
  uploadFileList.value.push(uploadFile);

  var md5 = await computeMd5(file, uploadFile);//async 和 await配可以實(shí)現(xiàn)等待異步函數(shù)計(jì)算完成
  uploadFile.md5 = md5;

  var res = await checkFile(md5);  //上傳服務(wù)器檢查,以確認(rèn)是否秒傳
  var data = res.data.data;

  if (!data.isUploaded) {
    uploadFile.chunkList = data.chunkList;
    uploadFile.needUpload = true;
  } else {
    uploadFile.needUpload = false;
    uploadFile.uploadPercentage.value = 100;

    console.log("文件已秒傳");
    ElMessage({
      showClose: true,
      message: "文件已秒傳",
      type: "warning",
    });
  }


}
//點(diǎn)擊暫停或開(kāi)始上傳
function changeUploadingStop(uploadFile) {
 
  uploadFile.uploadingStop = !uploadFile.uploadingStop;
  if (!uploadFile.uploadingStop) {
    uploadChunk(uploadFile.file, 1, uploadFile);
  }
}
//上傳文件,替換el-upload的action
function upload(xhrData) {
  var uploadFile = null;
 

  for (var i = 0; i < uploadFileList.value.length; i++) {
  
    if (
      (xhrData.file.name == uploadFileList.value[i].name) &
      (xhrData.file.size == uploadFileList.value[i].size)
    ) {
      uploadFile = uploadFileList.value[i];

      break;
    }
  }


  if (uploadFile.needUpload) {
    console.log("3.上傳文件");

    // 分片上傳文件
    // 確定分片的大小
    uploadChunk(xhrData.file, 1, uploadFile);
  }
}
//上傳文件分片
function uploadChunk(file, index, uploadFile) {
  var chunkSize = 1024 * 1024 * 10; //10mb
  var chunkTotal = Math.ceil(file.size / chunkSize);
  if (index <= chunkTotal) {
    // 根據(jù)是否暫停,確定是否繼續(xù)上傳

    // console.log("4.上傳分片");

    var startTime = new Date().valueOf();


    var exit = uploadFile.chunkList.includes(index);
    // console.log("是否存在",exit);


    if (!exit) {
      //    console.log("3.3上傳文件",uploadingStop);
      if (!uploadFile.uploadingStop) {
        // 分片上傳,同時(shí)計(jì)算進(jìn)度條和上傳速度
        // 已經(jīng)上傳的不在上傳、
        // 上傳完成后提示,上傳成功
        // console.log("上傳分片1",index);
        var form = new FormData();
        var start = (index - 1) * chunkSize;
        let end =
          index * chunkSize >= file.size ? file.size : index * chunkSize;
        let chunk = file.slice(start, end);
        //  downloadBlob(chunk,file)
        //  console.log("chunk",chunk);

        form.append("chunk", chunk);
        form.append("index", index);
        form.append("chunkTotal", chunkTotal);
        form.append("chunkSize", chunkSize);
        form.append("md5", uploadFile.md5);
        form.append("fileSize", file.size);
        form.append("fileName", file.name);
        // console.log("上傳分片", index);

        request({
          url: "/upload/chunk",
          method: "post",
          data: form,
        }).then((res) => {
          var endTime = new Date().valueOf();
          var timeDif = (endTime - startTime) / 1000;
          // console.log("上傳文件大小",formatSize(chunkSize));
          // console.log("耗時(shí)",timeDif);
          // console.log("then",index);

          // uploadSpeed = (chunkSize/(1024*1024))  / timeDif +" M / s"

          uploadFile.uploadSpeed = (10 / timeDif).toFixed(1) + " M/s";
          // console.log(res.data.data);
          //  console.log("f2",uploadFile);
          uploadFile.chunkList.push(index);
          //  console.log("f3",uploadFile);

          uploadFile.uploadPercentage = parseInt(
            (uploadFile.chunkList.length / chunkTotal) * 100
          );
          // console.log("上傳進(jìn)度",uploadFile.uploadPercentage);

          if (index == chunkTotal) {
            emitter.emit("reloadFileList");
          }

          uploadChunk(file, index + 1, uploadFile);
        });
      }
    } else {
      uploadFile.uploadPercentage = parseInt(
        (uploadFile.chunkList.length / chunkTotal) * 100
      );

      uploadChunk(file, index + 1, uploadFile);
    }
    // }
  }
}
</script>

<style  scoped>
.main {
  margin-top: 40px;
  margin-bottom: 40px;
}
.uploading {
  padding-top: 27px;
}
.progress {
  /* width: 700px; */
  display: flex;
}
.uploading .parse {
  display: flex;
}
.parse .el-progress {
  /* font-size: 18px; */
  width: 590px;
}
.progress .el-progress {
  /* font-size: 18px; */
  width: 590px;
}
.uploading .fileName {
  font-size: 17px;
  margin-right: 40px;
  margin-left: 80px;

  /* width: 80px; */
}
.uploading .fileSize {
  font-size: 17px;

  /* width: 80px; */
}

.progress .uploadSpeed {
  font-size: 17px;
  margin-left: 5px;
  padding-left: 5px;
  padding-right: 10px;
}
</style>

后端代碼

package com.cugb.bigfileupload.controller;

import com.cugb.bigfileupload.bean.FilePO;
import com.cugb.bigfileupload.bean.Result;
import com.cugb.bigfileupload.servie.ChunkService;
import com.cugb.bigfileupload.servie.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@RestController
@CrossOrigin
public class FileController {
    Logger logger = LoggerFactory.getLogger(getClass());

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

    @Autowired
    private FileService fileService;
    @Autowired
    private ChunkService chunkService;
    @GetMapping("/check")
    public Result checkFile(@RequestParam("md5") String md5){
        logger.info("檢查MD5:"+md5);
        //首先檢查是否有完整的文件
        Boolean isUploaded = fileService.selectFileByMd5(md5);
        Map<String, Object> data = new HashMap<>();
        data.put("isUploaded",isUploaded);
        //如果有,就返回秒傳
        if(isUploaded){
            return new Result(201,"文件已經(jīng)秒傳",data);
        }

        //如果沒(méi)有,就查找分片信息,并返回給前端
        List<Integer> chunkList = chunkService.selectChunkListByMd5(md5);
        data.put("chunkList",chunkList);

        return  new Result(201,"",data);
    }

    @PostMapping("/upload/chunk")
    public Result uploadChunk(@RequestParam("chunk") MultipartFile chunk,
                              @RequestParam("md5") String md5,
                              @RequestParam("index") Integer index,
                              @RequestParam("chunkTotal")Integer chunkTotal,
                              @RequestParam("fileSize")Long fileSize,
                              @RequestParam("fileName")String fileName,
                              @RequestParam("chunkSize")Long chunkSize
    ){


        String[] splits = fileName.split("\\.");
        String type = splits[splits.length-1];
        String resultFileName = filePath+md5+"."+type;

        chunkService.saveChunk(chunk,md5,index,chunkSize,resultFileName);
        logger.info("上傳分片:"+index +" ,"+chunkTotal+","+fileName+","+resultFileName);
        if(Objects.equals(index, chunkTotal)){
            FilePO filePO = new FilePO(fileName, md5, fileSize);
            fileService.addFile(filePO);
            chunkService.deleteChunkByMd5(md5);
            return  new Result(200,"文件上傳成功",index);
        }else{
            return new Result(201,"分片上傳成功",index);
        }

    }

    @GetMapping("/fileList")
    public Result getFileList(){
        logger.info("查詢文件列表");
        List<FilePO> fileList = fileService.selectFileList();

        return  new Result(201,"文件列表查詢成功",fileList);
    }
}

4.2 文件下載模塊

文件下載的流程圖如圖8所示,順序圖如圖9所示
文件下載是首先,前端向后端發(fā)送分片下載的請(qǐng)求,請(qǐng)求的responseType設(shè)為blob(Binary large Object) ,然后后端通過(guò)RandomAccess類讀取指定字節(jié)的內(nèi)容,再寫入到響應(yīng)的文件流中。

瀏覽器前端的請(qǐng)求的分片數(shù)據(jù),會(huì)暫時(shí)保存在“C:\Users\用戶名\AppData\Local\Microsoft\Edge\User Data\Default\blob_storage\”中,(請(qǐng)確保c盤有足夠的空間),當(dāng)所有分片下載完成,會(huì)合并成一個(gè)大文件(很快),分片不是放在內(nèi)存中,所以不用擔(dān)心文件太大是不是不行。
,
刷新瀏覽器,也會(huì)刪除已經(jīng)下載好的分片

當(dāng)前端請(qǐng)求了所有的文件分片之后,再把所有的blob合并成一個(gè)blob

if (index == chunkTotal) {
            var resBlob = new Blob(file.blobList, {
              type: "application/octet-stream",
            });
            // console.log("resb", resBlob);

            let url = window.URL.createObjectURL(resBlob); // 將獲取的文件轉(zhuǎn)化為blob格式
            let a = document.createElement("a"); // 此處向下是打開(kāi)一個(gè)儲(chǔ)存位置
            a.style.display = "none";
            a.href = url;
            // 下面兩行是自己項(xiàng)目需要的處理,總之就是得到下載的文件名(加后綴)即可

            var fileName = file.name;

            a.setAttribute("download", fileName);
            document.body.appendChild(a);
            a.click(); //點(diǎn)擊下載
            document.body.removeChild(a); // 下載完成移除元素
            window.URL.revokeObjectURL(url); // 釋放掉blob對(duì)象
          }

java分片下載,vue3,java,spring boot,spring,vuejava分片下載,vue3,java,spring boot,spring,vue

圖9文件上傳順序圖

前端代碼

<template>
  <div class="main">
    <div class="fileList">
      <div class="title">
        文件列表
        <!-- <hr> -->
      </div>

      <el-table :data="fileList" border style="width: 360px">
        <el-table-column prop="name" label="文件名" width="150">
        </el-table-column>

        <el-table-column prop="size" label="文件大小" width="110">
          <template #default="scope">
            {{ formatSize(scope.row) }}
          </template>
        </el-table-column>
        <el-table-column prop="" label="操作" width="100">
          <template #default="scope">
            <el-button
              size="small"
              type="primary"
              @click="downloadFile(scope.row)"
              >下載</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </div>

    <div class="downloadList">
      <el-divider content-position="left">下載列表</el-divider>

      <div v-for="file in downloadingFileList">
        <div class="downloading">
          <span class="fileName">{{ file.name }}</span>
          <span class="fileSize">{{ formatSize(file) }}</span>
          <span class="downloadSpeed">{{ file.downloadSpeed }}</span>

          <div class="progress">
            <span>下載進(jìn)度:</span>

            <el-progress
              :text-inside="true"
              :stroke-width="16"
              :percentage="file.downloadPersentage"
            >
            </el-progress>

            <el-button circle link @click="changeDownloadStop(file)">
              <el-icon size="20" v-if="file.downloadingStop == false"
                ><VideoPause
              /></el-icon>
              <el-icon size="20" v-else><VideoPlay /></el-icon>
            </el-button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import axios from "axios";
import { ref, reactive, getCurrentInstance } from "vue";
import emitter from "../utils/eventBus.js";
import { VideoPause, VideoPlay } from "@element-plus/icons-vue";
const { appContext } = getCurrentInstance();
const request = appContext.config.globalProperties.request;
var fileList = reactive([]);
var downloadingFileList = ref([]);
//上傳文件之后,重新加載文件列表
emitter.on("reloadFileList", () => {
  load();
});
function load() {
  fileList.length = 0;
  request({
    url: "/fileList",
    method: "get",
  }).then((res) => {
    // console.log("res", res.data.data);
    fileList.push(...res.data.data);
  });
}
load();

//換算文件的大小單位
function formatSize(file) {
  //console.log("size",file.size);
  var size = file.size;
  var unit;
  var units = [" B", " K", " M", " G"];
  var pointLength = 2;
  while ((unit = units.shift()) && size > 1024) {
    size = size / 1024;
  }
  return (
    (unit === "B"
      ? size
      : size.toFixed(pointLength === undefined ? 2 : pointLength)) + unit
  );
}
//點(diǎn)擊暫停下載
function changeDownloadStop(file) {
  file.downloadingStop = !file.downloadingStop;
  if (!file.downloadingStop) {
    console.log("開(kāi)始。。");

    downloadChunk(1, file);
  }
}
//點(diǎn)擊下載文件
function downloadFile(file) {
  // console.log("下載", file);
  file.downloadingStop = false;
  file.downloadSpeed = "0 M/s";
  file.downloadPersentage = 0;
  file.blobList = [];
  file.chunkList = [];
  downloadingFileList.value.push(file);

  downloadChunk(1, file);
}
//點(diǎn)擊下載文件分片
function downloadChunk(index, file) {
  var chunkSize = 1024 * 1024 * 5;
  var chunkTotal = Math.ceil(file.size / chunkSize);

  if (index <= chunkTotal) {
    // console.log("下載進(jìn)度",index);
    var exit = file.chunkList.includes(index);
    console.log("存在", exit);

    if (!exit) {
      if (!file.downloadingStop) {
        var formData = new FormData();
        formData.append("fileName", file.name);
        formData.append("md5", file.md5);
        formData.append("chunkSize", chunkSize);
        formData.append("index", index);
        formData.append("chunkTotal", chunkTotal);
        if (index * chunkSize >= file.size) {
          chunkSize = file.size - (index - 1) * chunkSize;
          formData.set("chunkSize", chunkSize);
        }

        var startTime = new Date().valueOf();

        axios({
          url: "http://localhost:9001/download",
          method: "post",
          data: formData,
          responseType: "blob",
          timeout: 50000,
        }).then((res) => {
          file.chunkList.push(index);
          var endTime = new Date().valueOf();
          var timeDif = (endTime - startTime) / 1000;
          file.downloadSpeed = (5 / timeDif).toFixed(1) + " M/s";
          //todo
          file.downloadPersentage = parseInt((index / chunkTotal) * 100);
          // var chunk = res.data.data.chunk
          // const blob = new Blob([res.data]);
          const blob = res.data;

          file.blobList.push(blob);
          // console.log("res", blobList);
          if (index == chunkTotal) {
            var resBlob = new Blob(file.blobList, {
              type: "application/octet-stream",
            });
            // console.log("resb", resBlob);

            let url = window.URL.createObjectURL(resBlob); // 將獲取的文件轉(zhuǎn)化為blob格式
            let a = document.createElement("a"); // 此處向下是打開(kāi)一個(gè)儲(chǔ)存位置
            a.style.display = "none";
            a.href = url;
            // 下面兩行是自己項(xiàng)目需要的處理,總之就是得到下載的文件名(加后綴)即可

            var fileName = file.name;

            a.setAttribute("download", fileName);
            document.body.appendChild(a);
            a.click(); //點(diǎn)擊下載
            document.body.removeChild(a); // 下載完成移除元素
            window.URL.revokeObjectURL(url); // 釋放掉blob對(duì)象
          }

          downloadChunk(index + 1, file);
        });
      }
    } else {
      file.downloadPersentage = parseInt((index / chunkTotal) * 100);
      downloadChunk(index + 1, file);
    }
  }
}
</script>

<style  scoped>
.main {
  display: flex;
}
.fileList {
  width: 400px;
}
.downloadList {
  width: 450px;
}
.title {
  margin-top: 5px;
  margin-bottom: 5px;
}
.downloading {
  margin-top: 10px;
}
.downloading .fileName {
  margin-left: 76px;
  margin-right: 30px;
}
.downloading .fileSize {
  /* margin-left: 70px; */
  margin-right: 30px;
}
.downloading .progress {
  display: flex;
}
.progress .el-progress {
  /* font-size: 18px; */
  width: 310px;
}
</style>

后端代碼

package com.cugb.bigfileupload.controller;

import com.cugb.bigfileupload.servie.ChunkService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Objects;

@Controller
@CrossOrigin
public class DownLoadController {
    Logger logger = LoggerFactory.getLogger(getClass());

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

    @Autowired
    private ChunkService chunkService;
    @PostMapping("/download")
    public void download(@RequestParam("md5") String md5,
                           @RequestParam("fileName") String fileName,
                           @RequestParam("chunkSize") Integer chunkSize,
                         @RequestParam("chunkTotal") Integer chunkTotal,
                           @RequestParam("index")Integer index,
                         HttpServletResponse response) {
        String[] splits = fileName.split("\\.");
        String type = splits[splits.length - 1];
        String resultFileName = filePath + md5 + "." + type;

        File resultFile = new File(resultFileName);

        long offset = (long) chunkSize * (index - 1);
        if(Objects.equals(index, chunkTotal)){
            offset = resultFile.length() -chunkSize;
        }
        byte[] chunk = chunkService.getChunk(index, chunkSize, resultFileName,offset);


        logger.info("下載文件分片" + resultFileName + "," + index + "," + chunkSize + "," + chunk.length+","+offset);
//        response.addHeader("Access-Control-Allow-Origin","Content-Disposition");
        response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.addHeader("Content-Length", "" + (chunk.length));
        response.setHeader("filename", fileName);


        response.setContentType("application/octet-stream");
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            out.write(chunk);
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

}

4.3 數(shù)據(jù)庫(kù)設(shè)計(jì)

4.3.1 概念結(jié)構(gòu)設(shè)計(jì)
數(shù)據(jù)庫(kù)設(shè)計(jì)只有倆個(gè)表,一個(gè)file表來(lái)記錄已經(jīng)完整上傳的文件信息,一個(gè)chunk表用來(lái)記錄還未上傳完成的分片信息
java分片下載,vue3,java,spring boot,spring,vue

5.1 大文件上傳實(shí)現(xiàn)

上傳頁(yè)面如圖13所示,有一個(gè)“選擇上傳文件”的按鈕,下面是顯示正在上傳文件的列表
java分片下載,vue3,java,spring boot,spring,vue
圖13 上傳頁(yè)面首頁(yè)

我們選擇要上傳的文件,確認(rèn)上傳,首先會(huì)顯示解析進(jìn)度,當(dāng)解析完成后,就會(huì)開(kāi)始上傳,并顯示上傳進(jìn)度和速度;同時(shí),我們可以選擇多個(gè)文件一同上傳;在上傳的同時(shí)我們還可以暫停上傳。如圖14所示

java分片下載,vue3,java,spring boot,spring,vue

圖14 上傳文件中

當(dāng)文件上傳成功之后,就會(huì)彈窗提示文件上傳成功。如圖15所示

java分片下載,vue3,java,spring boot,spring,vue

圖15 文件上傳成功
java分片下載,vue3,java,spring boot,spring,vue

5.2 大文件下載實(shí)現(xiàn)

文件下載頁(yè)面如圖16所示,左邊是可以下載文件的列表,右邊是下載中的文件
java分片下載,vue3,java,spring boot,spring,vue

java分片下載,vue3,java,spring boot,spring,vue

當(dāng)所有的分片下載完成后,前端會(huì)將所有的分片合并成一個(gè)文件。如圖18所示文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-781977.html

到了這里,關(guān)于【java】java實(shí)現(xiàn)大文件的分片上傳與下載(springboot+vue3)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • React 實(shí)現(xiàn)文件分片上傳和下載

    文件分片上傳是一種將大文件分割成多個(gè)小片段進(jìn)行上傳的技術(shù)。它的原理是將大文件切割成固定大小的小塊,然后逐個(gè)上傳這些小塊,最后在服務(wù)器端將這些小塊合并成完整的文件。 文件分片上傳的機(jī)制可以提高上傳速度和穩(wěn)定性。由于大文件的上傳可能會(huì)受到網(wǎng)絡(luò)不穩(wěn)定

    2024年02月13日
    瀏覽(21)
  • springboot實(shí)現(xiàn)minio文件分片上傳

    在Spring Boot中實(shí)現(xiàn)MinIO的文件分片上傳涉及到幾個(gè)關(guān)鍵步驟。MinIO是一個(gè)高性能的分布式對(duì)象存儲(chǔ)服務(wù),它兼容Amazon S3的API。分片上傳主要用于上傳大文件,將大文件分成多個(gè)部分,分別上傳,最后再將這些部分合并成一個(gè)完整的文件。這樣做的好處是提高了上傳的可靠性,并

    2024年01月17日
    瀏覽(22)
  • .net6Api后臺(tái)+VUE3前端實(shí)現(xiàn)上傳和下載文件全過(guò)程

    .net6Api后臺(tái)+VUE3前端實(shí)現(xiàn)上傳和下載文件全過(guò)程

    首先本文參考的是,感謝博主: net6WebApi上傳下載文件_cduoa的博客-CSDN博客_webapi下載文件 在博主的基礎(chǔ)上,增加了新的功能,代碼中有注明,并且使用VUE3前端實(shí)現(xiàn)。 后端部分: 1.首先建立IFileService文件 2.建立FileService文件 3.增加FileController文件 4.Program文件中,進(jìn)行配置和跨域

    2023年04月09日
    瀏覽(32)
  • Springboot+WebUploader優(yōu)雅實(shí)現(xiàn)超大文件的分片上傳(一)

    Springboot+WebUploader優(yōu)雅實(shí)現(xiàn)超大文件的分片上傳(一)

    在軟件工程里,在處理“大”的時(shí)候一直是一個(gè)痛點(diǎn)和難點(diǎn),如并發(fā)大、數(shù)據(jù)量大、文件大,對(duì)硬件進(jìn)行升級(jí)可以解決一些問(wèn)題,但這并不最聰明的辦法,而對(duì)于老板來(lái)說(shuō),這也不是成本最小的辦法。作為開(kāi)發(fā)人員來(lái)說(shuō),在面對(duì)類似極端的問(wèn)題時(shí),只可智取,不可硬剛,最大

    2024年02月03日
    瀏覽(21)
  • springboot整合vue2-uploader文件分片上傳、秒傳、斷點(diǎn)續(xù)傳

    springboot整合vue2-uploader文件分片上傳、秒傳、斷點(diǎn)續(xù)傳

    vue-simple-uploader 是基于 simple-uploader.js 封裝的vue上傳插件。它的優(yōu)點(diǎn)包括且不限于以下幾種: 支持文件、多文件、文件夾上傳;支持拖拽文件、文件夾上傳 可暫停、繼續(xù)上傳 錯(cuò)誤處理 支持“秒傳”,通過(guò)文件判斷服務(wù)端是否已存在從而實(shí)現(xiàn)“秒傳” 分片上傳 支持進(jìn)度、預(yù)估

    2024年02月06日
    瀏覽(35)
  • Java實(shí)現(xiàn)文件分片上傳

    Java實(shí)現(xiàn)文件分片上傳

    Java實(shí)現(xiàn)文件分片上傳 為什么要使用分片上傳 在需要上傳文件時(shí),不可避免地會(huì)遇到上傳文件內(nèi)容過(guò)大,上傳時(shí)間太長(zhǎng)地問(wèn)題,采用文件分片上傳就可以解決這個(gè)問(wèn)題。 什么是分片上傳? 簡(jiǎn)單的說(shuō)就是本來(lái)是需要一次搬一個(gè)很大的東西,比如是一大桶水,一次搬起來(lái)比較費(fèi)

    2024年02月08日
    瀏覽(16)
  • 【SpringBoot整合系列】SpringBoot 實(shí)現(xiàn)大文件分片上傳、斷點(diǎn)續(xù)傳及秒傳

    【SpringBoot整合系列】SpringBoot 實(shí)現(xiàn)大文件分片上傳、斷點(diǎn)續(xù)傳及秒傳

    小文件(圖片、文檔、視頻)上傳可以直接使用很多ui框架封裝的上傳組件,或者自己寫一個(gè)input 上傳,利用FormData 對(duì)象提交文件數(shù)據(jù),后端使用spring提供的MultipartFile進(jìn)行文件的接收,然后寫入即可。 但是對(duì)于比較大的文件,比如上傳2G左右的文件(http上傳),就需要將文件

    2024年04月16日
    瀏覽(35)
  • MINIO服務(wù)器基于AWS S3 SDK 文件分片上傳及下載(C++實(shí)現(xiàn))

    MINIO服務(wù)器基于AWS S3 SDK 文件分片上傳及下載(C++實(shí)現(xiàn))

    安裝環(huán)境依賴: 獲取SDK源碼并安裝: 項(xiàng)目中CMakeLists.txt配置: SDK文檔資料 C++_SDK.pdf 實(shí)現(xiàn)下載的整體類代碼下載 具體內(nèi)容如下

    2024年04月10日
    瀏覽(50)
  • SpringBoot整合hdfs,實(shí)現(xiàn)文件上傳下載刪除與批量刪除,以及vue前端發(fā)送請(qǐng)求,實(shí)現(xiàn)前后端交互功能;

    SpringBoot整合hdfs,實(shí)現(xiàn)文件上傳下載刪除與批量刪除,以及vue前端發(fā)送請(qǐng)求,實(shí)現(xiàn)前后端交互功能;

    部分工具類代碼參考文章:https://blog.csdn.net/qq_27242695/article/details/119683823 前端實(shí)現(xiàn)效果 HDFSController HDFS FileInterface (文件接口) HDFS FileImplService (文件接口實(shí)現(xiàn)類) HDFSConfig(從yaml讀取文件) HDFSUTils 前端vue代碼:

    2024年02月16日
    瀏覽(109)
  • vue3 - 使用element-plus組件庫(kù)el-upload上傳超大mp4視頻分片上傳,Upload上傳大文件mp4視頻進(jìn)行切片分段上傳到后端服務(wù)器教程,vue3如何上傳很大的視頻(詳細(xì)示例代碼

    vue3 - 使用element-plus組件庫(kù)el-upload上傳超大mp4視頻分片上傳,Upload上傳大文件mp4視頻進(jìn)行切片分段上傳到后端服務(wù)器教程,vue3如何上傳很大的視頻(詳細(xì)示例代碼

    在vue3+elementPlus中,使用el-upload組件\\\"切片分段\\\"上傳mp4大視頻到服務(wù)器,支持任意大視頻、大文檔、大壓縮包等超大文件,通用方法將其拆分成多個(gè)小段進(jìn)行逐個(gè)逐條上傳到后端(支持?jǐn)帱c(diǎn)續(xù)傳、下載預(yù)覽)。 詳細(xì)大文件分片功能源碼,可只拿前端源碼或只拿springboot(Java)后

    2024年03月16日
    瀏覽(37)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包