美文网首页
springboot + vue 分片上传,分片下载

springboot + vue 分片上传,分片下载

作者: 三没产品 | 来源:发表于2022-05-24 11:08 被阅读0次

分片上传

前端代码

 <progress class="progress" :value="fileObj.progress" ></progress>
 <span class="desc">{{fileObj.progressText ? fileObj.progressText : '正在解析文件'}}</span>
 <input type="file" @change="handleFileInputChange"/>

 methods: {
    handleFileInputChange(e) {
          let _this = this;
          if (e.target.files) {
             let file = e.target.files[0];
             let sha1 = hex_sha1(file.name + '-' + file.lastModified);//用于前端判断该文件是否已上传
             let item = {
                  progressText: '等待上传', //上传提示
                  progress: 0, //进度
                  file: file, // 文件本身
                  retry: 0,// 重试次数
                  del: false, // 删除标志
                  sha1: sha1, // 前端判断标识
                  uuid: UUID.generate().replace(/-/g, ''), //作为该文件的唯一标识
                  idx: 0, //当前分片
                  chunkNum: Math.ceil(file.size / bufferLength), // 计算分片数量, 向上取整
                  finishIdx: 0//完成上传的分片数
              }
              _this.upload(item);
             e.target.value = '';
         }
    },
    /*上传*/
    async upload(item) {
        let _this = this;
        item.progressText = '正在上传(' + Math.round(item.finishIdx / item.chunkNum * 100) + '%)';
        _this.uploadSlice(item);
    },
    /*上传*/
    uploadSlice(item) {
        if (item.del) return;
        let _this = this;
        let file = item.file;
        // 每片分片大小
        let bufferLength = 1024 * 1024;
        //开始切割位置
        let start = item.idx * bufferLength;
        //全部上传完毕或重试次数用完则退出
        if (start >= file.size) return;
        //计算分割的位置
        let end = start + bufferLength;
        //如果分割点超出文件大小,回退分割点
        if (end > file.size) {
            end = file.size;
        }
        //切割文件
        let chunk = file.slice(start, end);
        let fileReader = new FileReader();
        //该方法用于将File对象转化为二进制文件
        fileReader.readAsBinaryString(chunk);
        fileReader.onload = function (e) {
            //e.target.result为读取到的分片的二进制
            //创建 formData 对象并添加数据
            let formData = new FormData();
            formData.set("uuid", item.uuid);
            formData.set('sha1', hex_sha1(e.target.result));//每个分片的sha1
            formData.set("file", chunk, file.name);
            formData.set("idx", item.idx);
            formData.set("totalIdx", item.chunkNum);
            formData.set("start", start);
            formData.set("end", end);
            formData.set("bufferLength", bufferLength);
            formData.set("totalSize", file.size);
            _this.$http.post(_this.src, formData).then((res) => {
                item.finishIdx += 1;
                let p = item.finishIdx / item.chunkNum;
                item.progress = p;
                if (p === 1) {
                    item.progressText = '上传完成(100%)';
                    if (res.code === 1 && res.data) {
                        _this.$emit('complete', res.data)
                    }
                } else {
                    item.progressText = '正在上传(' + Math.round(p * 100) + '%)';
                }
                if (item.idx === item.chunkNum) return;
                item.idx += 1;
                _this.uploadSlice(item);
            }).catch(err => {
                console.log(err);
                //失败后可以重试上传对应分片
           })
        }
    }
 }

后端代码

重点:只要使用了RandomAccessFile对文件进行处理(好像还有FileChannel可以实现)

//FileSliceVo 实体类
public class FileSliceVo implements Serializable {
    private static final long SerialVersionUID = 1L;
    private Integer idx;
    private Long start;
    private Long end;
    private Long bufferLength;
    private Integer totalSize;
    private String sha1;
    private Integer totalIdx;
    private long idxSize;
    private String uuid;
    private String path;
    private String filename;
}

public AjaxResult<?> upload(MultipartFile file, FileSliceVo sliceVo) {
   if (file == null || file.isEmpty()) {
       return AjaxResult.fail("请选择要上传的文件");
   }
   log.info("sha1:{} - totalSize:{} - bufferLength: {} - totalIdx: {} - idx:{} - idxSize: {} - start: {} - end: {}", sliceVo.getSha1(), sliceVo.getTotalSize(), sliceVo.getBufferLength(), sliceVo.getTotalIdx(), sliceVo.getIdx(), file.getSize(), sliceVo.getStart(), sliceVo.getEnd());
   //检测对应分片是否已上传
   List<FileSliceVo> sliceVos = RedisStorage.get(RedisKeys.FILE_KEY + sliceVo.getUuid(), () -> ListUtil.list(false), 3600L);
        if (CollectionUtils.isNotEmpty(sliceVos.stream().filter(f -> f.getSha1().equals(sliceVo.getSha1())).collect(Collectors.toList()))) {
            return AjaxResult.ok();
        }
   }
   // 用uuid的后两位作为文件夹的名称
   String format = sliceVo.getUuid().substring(sliceVo.getUuid().length() - 2);
   // 文件夹路径
   String baseUrl = appConfiguration.getFilePath() + File.separator + format;
   // 用前端传过来的uuid作为文件名称
   String filename = sliceVo.getUuid() + "." + FileUtils.getExtension(file.getOriginalFilename());
   // 文件夹路径 文件对象
   File serviceFile = new File(baseUrl);
   // 文件路劲 文件对象
   File tmpFile = new File(baseUrl, filename);
   // 检测文件夹是否已存在,不存在就创建
   if (!serviceFile.exists()) {
       serviceFile.mkdirs();
   }
   sliceVo.setPath(format + File.separator + filename);
   sliceVo.setFilename(file.getOriginalFilename());
   sliceVo.setIdxSize(file.getSize()); // 该分片的文件大小
   try (RandomAccessFile accessFile = new RandomAccessFile(tmpFile, "rw")) {
         accessFile.seek(sliceVo.getStart());
         accessFile.write(file.getBytes());
         sliceVos.add(sliceVo);
         JedisUtil.setObjectValue(RedisKeys.FILE_KEY + sliceVo.getUuid(), sliceVos, 3600L);
         long sum = sliceVos.stream().mapToLong(FileSliceVo::getIdxSize).sum();
         // 检测是否文件上传完成
         if (sliceVos.size() == sliceVo.getTotalIdx() && (sum + "").equals(sliceVo.getTotalSize().toString())) {
              //文件上传完成后,保存文件信息
             String sha1 = SecureUtil.sha1(tmpFile);
             ItFiles itFiles = filesService.selectBySha1(sha1);
             if (itFiles == null) {
                 itFiles = new ItFiles();
                 itFiles.setFilename(file.getOriginalFilename());
                 itFiles.setCreateAt(LocalDateTime.now());
                 itFiles.setPath(format + File.separator + filename);
                 itFiles.setFilesize(sliceVo.getTotalSize());
                 itFiles.setSha1(sha1);
                 filesService.save(itFiles);
             }
             //  删除对应的redis数据
            JedisUtil.del(RedisKeys.FILE_KEY + sliceVo.getUuid());
        }
   }

分片上传参考的是tus协议

视频文件分片下载(其他大文件亦可参考)

前端代码(vue)

/*
avSrc: 文件获取地址
crossorigin="anonymous"  允许跨域
autoplay 自动播放
*/
 <video :src="avSrc" controls="controls" width="100%" crossorigin="anonymous" autoplay></video>

后端代码

/**
range: video 会在请求头中自动添加Range字段,Range: bytes=1048576-
使用RandomAccessFile 读取对应的文件分片
*/
public void fileSlice(HttpServletResponse response, @RequestHeader String range, @RequestParam String params, @RequestParam String sign) throws IOException {
    log.info("range -> {}", range);
    if (MD5Utils.md5(params + FileUtils.KEY).equals(sign)) {
        File file = new File(appConfiguration.getFilePath() + File.separator + params);
        if (file.exists()) {
            try (OutputStream out = response.getOutputStream();RandomAccessFile accessFile = new RandomAccessFile(file, "r")) {
                long fileLength = file.length();
                long start = Long.parseLong(range.substring(range.indexOf("=") + 1, range.indexOf("-")));
                long end = Math.min(start + 1024 * 1024 - 1, fileLength - 1);

                //设定文件读取开始位置(以字节为单位)
                accessFile.seek(start);
                int contentLength = (int) (end - start + 1);

                //返回码需要为206,代表只处理了部分请求,响应了部分数据
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                response.setHeader("Content-Type", "video/mp4");
                //设置此次相应返回的数据长度
                response.setContentLength(contentLength);
                //设置此次相应返回的数据范围,  range的end最大为fileLen-1,最后一段需设置为 xxx-fileLen-1/fileLen
                response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
                response.setHeader("Accept-Ranges", "bytes");

                byte[] cache = new byte[1024];
                int len;
                while ((len = accessFile.read(cache)) != -1) {
                    out.write(cache, 0, len);
                }
            } catch (Exception e) {
                log.error("file down", e);
            }
        } else {
            response.setContentType(ContentType.TEXT_PLAIN.getValue());
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("文件不存在");
            response.getWriter().close();
        }
    }
}

分片播放参考地址:Spring boot实现视频播放断点续传 - BuptWade - 博客园 (cnblogs.com)

相关文章

  • springboot + vue 分片上传,分片下载

    分片上传 前端代码 后端代码 重点:只要使用了RandomAccessFile对文件进行处理(好像还有FileCh...

  • Vue + Springboot 大文件分片上传

    Vue + Springboot 大文件分片上传 思路来自于「刘悦的技术博客」https://v3u.cn/a_i...

  • JS分片上传

    JS分片上传 //分片上传 function ScarecrowPatchUpload (uploadPath, ...

  • 文件分片上传.md

    文件分片上传 文件分片上传的总体思路是 先将文件通过blob.slice()方法将文件切割成多个分片。然后循环上传...

  • webuploader前端分片上传

    前端分片上传附件 分片上传定义: 所谓的分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块...

  • SpringBoot文件分片上传

    背景 最近好几个项目在运行过程中客户都提出文件上传大小的限制能否设置的大一些,用户经常需要上传好几个G的资料文件,...

  • 七牛云上传及上传方法封装

    分片及七牛云上传封装项目里面用到七牛云,有分片和简单上传 在此做下简单的记录,分享 下面是分片上传封装 proce...

  • SpringBoot 文件下载、分片下载

    普通下载功能的实现 支持分片下载功能的实现 这里是因为测试中ios safari 浏览器无法正常打开播放视频文件,...

  • 阿里oss文件分片上传

    OSS文件分片上传 依赖 基础参数dto 具体上传方法 小文件上传 大文件上传,分片oss自己处理 处理逻辑:前段...

  • vue页面使用阿里云oss

    web上传文件到阿里云oss 基于vue2.0的上传页面,采用分片上传 源码 Install 1.git clon...

网友评论

      本文标题:springboot + vue 分片上传,分片下载

      本文链接:https://www.haomeiwen.com/subject/xwcjprtx.html