document.write("");

springboot 大文件切片上传

1. 前端(vue element ui & 原生)

初始变量声明:

 

currentFile: {}, // 当前上传的文件
bigFileSliceCount: 20, // 大文件切片后的子文件数量(也可使用其它限定方式,如按照文件大小,每10MB切一片,此处采用的是固定切片的子文件数量的方式倒推切片大小)

  

 

接口:切片上传图片&合并切片文件

 <el-button @click="partUploadClick()" type="info"> 上传文件</el-button>
  <input v-show="false" id="uploadPartfile" class="upload-css" type='file' @click.stop=''
                  @change='handleFileChange($event, currentFile); currentFile = {}' single />

  

    // 文件上传处理
    partUploadClick() {
      var clickEvent = document.createEvent('MouseEvent'); // 1.创建一个鼠标事件类型
      clickEvent.initMouseEvent('click', false, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); // 2.初始化一个click事件
      document.getElementById('uploadPartfile').dispatchEvent(clickEvent); // 3.派发(触发)
    },

  

handleFileChange(event, item) {
      let inputDom = event.target // input 本身,从这里获取 files<FileList>
      let files = inputDom.files // input 中的文件,是 FileList 对象,一个类似数组的文件组,但不是数组,可遍历
      var GUID = this.guid();
      var file = files[0], //文件对象
        name = file.name,    //文件名
        size = file.size;    //总大小
      // console.log(size)
      if (size <= 524288000) { // 文件大小小于等于500MB,直接整文件上传
        this.handleFiles(event, item)
      } else { // 大于500MB 切片上传
        var shardSize = size / this.bigFileSliceCount,  // 根据切片数量,确定每切片的大小是多少
          shardCount = Math.ceil(size / shardSize); //总片数
        item.download = true
        for (var i = 0; i < shardCount; ++i) {
          //计算每一片的起始与结束位置
          var start = i * shardSize,
            end = Math.min(size, start + shardSize);
          var partFile = file.slice(start, end);
          this.partUpload(GUID, partFile, name, shardCount, i, item.fullPath, item, size);
        }
      }
    },

  

partUpload: function (GUID, partFile, name, chunks, chunk, filePath, item, size) {
      //构造一个表单,FormData是HTML5新增的
      const _this = this
      var form = new FormData();
      form.append("guid", GUID);
      form.append("file", partFile); //slice方法用于切出文件的一部分
      form.append("fileName", name);
      form.append("chunks", chunks); //总片数
      form.append("chunk", chunk);    //当前是第几片
      form.append("filePath", filePath);    //文件保存路径
      uploadFileByPart(form).then((res) => {
        // console.log(res)
        let data = res.data
        _this.status++;
        if (data.code == 200) {
          _this.complete = this.status * (100 / this.bigFileSliceCount)
          let finishSize = this.status != this.bigFileSliceCount ? (size / this.bigFileSliceCount) * this.status : size
          if (finishSize > 1048576) {
            _this.finishSize = parseFloat(finishSize / 1048576).toFixed(1) + 'M';
          } else if (finishSize > 1024) {
            _this.finishSize = parseInt(finishSize / 1024) + 'K';
          } else {
            _this.finishSize = finishSize + 'B';
          }
          // console.log(this.status + " / " + chunks)
        }
        if (this.status == chunks) {
          _this.mergeFile(GUID, name, filePath, item);
        }
      }).catch((err) => {
        console.log("请求出错", err);
      });
    },

  

// 切片上传文件
export const uploadFileByPart = (fileFormData) => {
    return http({
        url: `/xxxxx/files/part`,
        method: 'post',
        data: fileFormData,
        async: true,
        processData: false,
        contentType: false
    })
}

  

mergeFile: function (GUID, name, filePath, item) {
      var formMerge = new FormData();
      formMerge.append("guid", GUID);
      formMerge.append("fileName", name);
      formMerge.append("filePath", filePath);
      mergeFileByPart(formMerge).then((res) => {
        item.download = false
        let data = res.data
        if (data.code == 200) {
          this.$message({
            message: '上传成功',
            type: 'success'
          });
          this.getFalcoPath();
        }
      }).catch((err) => {
        item.download = false
        console.log("请求出错", err);
      });
    },

  

// 切片合并文件
export const mergeFileByPart = (fileFormData) => {
    return http({
        url: `/xxxxx/files/merge`,
        method: 'post',
        processData: false,
        contentType: false,
        data: fileFormData
    })
}

  

    guid: function (prefix) {
      var counter = 0;
      var guid = (+new Date()).toString(32),
        i = 0;
      for (; i < this.bigFileSliceCount; i++) {
        guid += Math.floor(Math.random() * 65535).toString(32);
      }
      return (prefix || 'wu_') + guid + (counter++).toString(32);
    }

  

    // 整文件上传时的处理
    handleFiles(event, item) {
      let inputDom = event.target // input 本身,从这里获取 files<FileList>
      let files = inputDom.files // input 中的文件,是 FileList 对象,一个类似数组的文件组,但不是数组,可遍历
      // console.log(files)
      let fileFormData = new window.FormData()
      fileFormData.append('file', files[0])
      item.download = true
      // console.log("上传文件路径" + item.fullPath)
      const _this = this
      uploadFileByPath(fileFormData, item.fullPath, this.gress).then((res) => {
        // console.log(res)
        item.download = false
        inputDom.value = ''
        if (res.data.code == 200) {
          _this.$message({
            message: '上传成功',
            type: 'success'
          });
          this.getFalcoPath();
        } else if (res.data.code == 2001) {
          this.$message.error('上传失败');
        }
      }).catch((err) => {
        item.download = false
        console.log("请求出错", err);
      });
    },

  

// 单个文件完整上传文件
export const uploadFileByPath = (fileFormData, filePath, uploadProgress) => {
    return http({
        url: `/xxxxx/files/upload`,
        method: 'post',
        onUploadProgress: function (progressEvent) {
            uploadProgress(progressEvent)
        },
        data: fileFormData,
        params: { 'filePath': filePath }
    })
}

  

    // 上传与下载文件的回调
    gress(progress) {
      const self = this
      this.complete = ((progress.loaded / progress.total) * 100).toFixed(0)

      if (progress.loaded > 1048576) {
        self.finishSize = parseFloat(progress.loaded / 1048576).toFixed(1) + 'M';
      } else if (progress.loaded > 1024) {
        self.finishSize = parseInt(progress.loaded / 1024) + 'K';
      } else {
        self.finishSize = progress.loaded + 'B';
      }
      // console.log("已下载:" + self.finishSize + ' 比例  ' + this.complete)
    },

  

2. 后台

接口:

@ResponseBody
@PostMapping("/xxxxx/files/upload")
public ApiResult upload(@RequestParam(value = "file", required = false) MultipartFile multipartFile, @RequestParam(required = false) String filePath) {
        File file=new File(filePath + File.separator + multipartFile.getOriginalFilename());
        try {
                FileUtil.copy(multipartFile.getBytes(), file);
        } catch (IOException e) {
                return ApiResult.fail(2001,"上传失败");
        }
        return ApiResult.ok("上传成功");
}
@PostMapping(
"/xxxxx/files/part") @ResponseBody public ApiResult bigFile(HttpServletRequest request, HttpServletResponse response, String guid, Integer chunk, MultipartFile file, Integer chunks, String filePath, String fileName) { try { boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (isMultipart) { if (chunk == null) chunk = 0; // 临时目录用来存放所有分片文件 String tempFileDir = applicationProperties.getNasPath() + File.separator + "临时文件夹" + File.separator + guid; File parentFileDir = new File(tempFileDir); if (!parentFileDir.exists()) { parentFileDir.mkdirs(); } // 分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台 log.info(SecurityUtils.getCurrentLogin() + " 上传:" + filePath + File.separator + fileName + "; 切片文件名:" + guid + "_" + chunk + ".part"); File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part"); FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile); } } catch (Exception e) { log.error(SecurityUtils.getCurrentLogin() + " 上传:" + filePath + File.separator + fileName + "; 切片文件名:" + guid + "_" + chunk + ".part" + " error: " + e.getMessage()); e.printStackTrace(); return ApiResult.fail(2009,e.getMessage()); } return ApiResult.ok(200,"上次成功"); }
@RequestMapping(
"/xxxxx/files/merge") @ResponseBody public ApiResult mergeFile(String guid, String fileName, String filePath) { try { log.info(SecurityUtils.getCurrentLogin() + " 合并切片文件:" + filePath + File.separator + fileName + " start"); String sname = fileName.substring(fileName.lastIndexOf(".")); //时间格式化格式 Date currentTime = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS"); //获取当前时间并作为时间戳 String timeStamp = simpleDateFormat.format(currentTime); //拼接新的文件名 String newName = timeStamp + sname; simpleDateFormat = new SimpleDateFormat("yyyyMM"); String path = applicationProperties.getNasPath() + File.separator + "临时文件夹" + File.separator ; String tmp = simpleDateFormat.format(currentTime); File parentFileDir = new File(path + guid); if (parentFileDir.isDirectory()) { File destTempFile = new File(filePath, fileName); if (!destTempFile.exists()) { //先得到文件的上级目录,并创建上级目录,在创建文件 destTempFile.getParentFile().mkdir(); try { destTempFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } for (int i = 0; i < parentFileDir.listFiles().length; i++) { File partFile = new File(parentFileDir, guid + "_" + i + ".part"); FileOutputStream destTempfos = new FileOutputStream(destTempFile, true); //遍历"所有分片文件"到"最终文件"中 FileUtils.copyFile(partFile, destTempfos); destTempfos.close(); } // 删除临时目录中的分片文件 FileUtils.deleteDirectory(parentFileDir); log.info(SecurityUtils.getCurrentLogin() + " 合并切片文件:" + filePath + File.separator + fileName + " end "); return ApiResult.ok(200,"合并成功"); }else{ log.info(SecurityUtils.getCurrentLogin() + " 合并切片文件:" + filePath + File.separator + fileName + " end error: 没找到目录"); return ApiResult.fail(2007,"没找到目录"); } } catch (Exception e) { log.info(SecurityUtils.getCurrentLogin() + " 合并切片文件:" + filePath + File.separator + fileName + " end error: " + e.getMessage()); e.printStackTrace(); return ApiResult.fail(2008,e.getMessage()); } }

 

 

 

工具类:

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;

import java.io.*;
import java.nio.file.*;

@Slf4j
public class FileUtil {

    public static void copy(byte[] in, File out) throws IOException {
        Assert.notNull(in, "No input byte array specified");
        Assert.notNull(out, "No output File specified");
        copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath()));
    }

    public static int copy(InputStream in, OutputStream out) throws IOException {
        Assert.notNull(in, "No InputStream specified");
        Assert.notNull(out, "No OutputStream specified");

        try {
            return StreamUtils.copy(in, out);
        } finally {
            try {
                in.close();
            } catch (IOException ex) {
            }
            try {
                out.close();
            } catch (IOException ex) {
            }
        }
    }

}

 

相关引用Class:

import org.apache.commons.io.FileUtils;

  

posted @ 2023-05-12 09:22  人间春风意  阅读(334)  评论(0编辑  收藏  举报