vue上传大文件/视频前后端(java)代码

一、上传组件

<template>
  <div>
    <!-- 上传组件 -->
    <el-upload action drag :auto-upload="false" :show-file-list="false" :on-change="handleChange">
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip" slot="tip">大小不超过 200M 的视频</div>
    </el-upload>

    <!-- 进度显示 -->
    <div class="progress-box">
      <span>上传进度:{{ percent.toFixed() }}%</span>
      <el-button type="primary" size="mini" @click="handleClickBtn">{{ upload | btnTextFilter}}</el-button>
    </div>

    
  </div>
</template>

<script>
  import { getUUID } from '@/utils'
  import axios from 'axios'
  export default {
    name: 'singleUpload',
    props: {
     value: String
    },
    filters: {
      btnTextFilter(val) {
        return val ? '暂停' : '继续'
      }
    },
    data() {
      return {
        videoUrl: this.value,
        percent: 0,
        upload: true,
        percentCount: 0,
        suffix: '',
        fileName: '',
        preName: ''
      }
    },
    methods: {
      emitInput(val) {
        this.$emit('input', val)
      },

    async handleChange(file) {
      if (!file) return
      this.percent = 0
      this.percentCount = 0
      // 获取文件并转成 ArrayBuffer 对象
      const fileObj = file.raw
    
      let buffer
      try {
        buffer = await this.fileToBuffer(fileObj)
      } catch (e) {
        console.log(e)
      }
      // 将文件按固定大小(2M)进行切片,注意此处同时声明了多个常量
      const chunkSize = 2097152,
          chunkList = [], // 保存所有切片的数组
          chunkListLength = Math.ceil(fileObj.size / chunkSize), // 计算总共多个切片
          suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1] // 文件后缀名

      this.preName = getUUID() //生成文件名前缀
      this.fileName = this.preName+'.'+suffix //文件名 
      

      // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
      let curChunk = 0 // 切片时的初始位置
      for (let i = 0; i < chunkListLength; i++) {
        const item = {
          chunk: fileObj.slice(curChunk, curChunk + chunkSize),
          fileName: `${this.preName}_${i}.${suffix}` // 文件名规则按照 filename_1.jpg 命名
        }
        curChunk += chunkSize
        chunkList.push(item)
      }
      this.chunkList = chunkList // sendRequest 要用到
      this.sendRequest()
    },
    // 发送请求
    sendRequest() {
      const requestList = [] // 请求集合
      this.chunkList.forEach((item, index) => {
        const fn = () => {
          const formData = new FormData()
          formData.append('chunk', item.chunk)
          formData.append('filename', item.fileName)
          return axios({
            url: 'http://localhost/api/chunk',
            method: 'post',
            headers: { 'Content-Type': 'multipart/form-data' },
            data: formData
          }).then(response => {
            if (response.data.errcode === 0) { // 成功
              if (this.percentCount === 0) { // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                this.percentCount = 100 / this.chunkList.length
              }
              if (this.percent >= 100) {
                this.percent = 100;
              }else {
                this.percent += this.percentCount // 改变进度
              }
              if (this.percent >= 100) {
                this.percent = 100;
              }
              this.chunkList.splice(index, 1) // 一旦上传成功就删除这一个 chunk,方便断点续传
            }else{
                this.$mseeage({
                  type: "error",
                  message: response.data.message
                })
                return 
            }
          })
        }
        requestList.push(fn)
      })

      let i = 0 // 记录发送的请求个数
      // 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件名传递给服务器
      const complete = () => {
        axios({
          url: 'http://localhost/api/merge',
          method: 'get',
          params: {filename: this.fileName },
          timeout: 60000
        }).then(response => {
          if (response.data.errcode === 0) { // 请求发送成功
            // this.videoUrl = res.data.path
            console.log(response.data)
          }
        })
      }
      const send = async () => {
        if (!this.upload) return
        if (i >= requestList.length) {
          // 发送完毕
          complete()
          return
        }
        await requestList[i]()
        i++
        send()
      }
      send() // 发送请求
        this.emitInput(this.fileName)
      },

      // 按下暂停按钮
      handleClickBtn() {
        this.upload = !this.upload
        // 如果不暂停则继续上传
        if (this.upload) this.sendRequest()
      },

      // 将 File 对象转为 ArrayBuffer 
      fileToBuffer(file) {
        return new Promise((resolve, reject) => {
          const fr = new FileReader()
          fr.onload = e => {
            resolve(e.target.result)
          }
          fr.readAsArrayBuffer(file)
          fr.onerror = () => {
            reject(new Error('转换文件格式发生错误'))
          }
        })
      }
    }
  }
</script>

<style scoped "">
  .progress-box {
    box-sizing: border-box;
    width: 360px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 10px;
    padding: 8px 10px;
    background-color: #ecf5ff;
    font-size: 14px;
    border-radius: 4px;
  }
  
.videoShow{
  width: 100%;
  height:600px;
  padding: 10px 0 50px;
  position: relative;
}
#videoBox{
  object-fit:fill;
  border-radius: 8px;
  display: inline-block;
  vertical-align: baseline;
}
.video-img{
	position: absolute;
    top: 0;
    bottom: 0;
    width: 100%;
    z-index: 999;
    background-size:100%;
	cursor:pointer;
    
  }
.video-img img {
  display:block;
  width: 60px;
  height: 60px;
  position: relative;
  top:260px;
  left: 48%;
}
video:focus {
  outline: -webkit-focus-ring-color auto 0px;

}
</style>

二、后端java代码

String dirPath = "D:\\video\\train"
@PostMapping("/chunk")
public Result upLoadChunk(@RequestParam("chunk") MultipartFile chunk,
                          @RequestParam("filename") String fileName) {
    // 用于存储文件分片的文件夹
    File folder = new File(dirPath);
    if (!folder.exists() && !folder.isDirectory())
        folder.mkdirs();
    // 文件分片的路径
    String filePath = dirPath + fileName;
    try {
        File saveFile = new File(filePath);
        // 写入文件中
        //FileOutputStream fileOutputStream = new FileOutputStream(saveFile);
        //fileOutputStream.write(chunk.getBytes());
        //fileOutputStream.close();
        chunk.transferTo(saveFile);
        return new Result();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return new Result();
}



@GetMapping("/merge")
public Result MergeChunk(@RequestParam("filename") String filename) {

    String preName = filename.substring(0,filename.lastIndexOf("."));
    // 文件分片所在的文件夹
    File chunkFileFolder = new File(dirPath);
    // 合并后的文件的路径
    File mergeFile = new File(dirPath + filename);
    // 得到文件分片所在的文件夹下的所有文件
    File[] chunks = chunkFileFolder.listFiles();
    System.out.println(chunks.length);
    assert chunks != null;
    // 排序
    File[] files = Arrays.stream(chunks)
            .filter(file -> file.getName().startsWith(preName))
            .sorted(Comparator.comparing(o -> Integer.valueOf(o.getName().split("\\.")[0].split("_")[1])))
            .toArray(File[]::new);

    try {
        // 合并文件
        RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
        byte[] bytes = new byte[1024];
        for (File chunk : files) {
            RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r");
            int len;
            while ((len = randomAccessFileReader.read(bytes)) != -1) {
                randomAccessFileWriter.write(bytes, 0, len);
            }
            randomAccessFileReader.close();
            System.out.println(chunk.getName());
            chunk.delete(); // 删除已经合并的文件
        }
        randomAccessFileWriter.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return new Result();
}

注意:

vue组件中导入的utils/index.js

/**
 * 获取uuid
 */
 export function getUUID () {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
  })
}
Logo

前往低代码交流专区

更多推荐