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;

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号