首页前端开发VUEVue Elementui+SpringBoot做大文件切片上传

Vue Elementui+SpringBoot做大文件切片上传

时间2023-07-07 00:38:01发布访客分类VUE浏览704
导读:主要思想是前端做大文件的MD5计算和大文件的切分,后台做小文件的合并和MD5的校验,直接上代码。前端VUE:<template> <el-upload ref="upload" class="uploa...

主要思想是前端做大文件的MD5计算和大文件的切分,后台做小文件的合并和MD5的校验,直接上代码。

前端VUE:

template>
    
  el-upload
    ref="upload"
    class="upload-demo"
    action="#"
    :http-request="handleUpload"
    :on-change="handleFileChange"
    :before-upload="handleBeforeUpload"
    :auto-upload="true"
    :multiple="false"
    :file-list="fileList">
    
    el-button slot="trigger" size="small" type="primary">
    选取文件/el-button>
    
    !-- el-button style="margin-left: 10px;
    " size="small" type="success" @click="handleUpload">
    上传文件/el-button>
     -->
    
  /el-upload>
    
/template>
    

script>
    
import SparkMD5 from 'spark-md5'
import axios from "axios";

import {

  getKey,completeUpload
}
     from "@/api/fileupload";

export default {

  data() {

    return {

      percent: 0,
      fileList: [],
      chunkSize: 6 * 1024 * 1024, // 分片大小为2MB
    }

  }
,
  methods: {

    handleFileChange(file) {

      this.fileList = [file]
    }
,
    handleBeforeUpload(file) {
    
      // 根据文件大小判断是否需要分片
      if (file.size >
 this.chunkSize) {

        file.chunked = true
        file.percent = 0
      }

      return true
    }
,
    handleUpload(params) {

      const file = params.file
      let startTime = new Date().getTime()
      if (file.chunked) {

        const loading = this.$loading({

          lock: true,
          text: '大文件正在计算M5,请稍后',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        }
    );
    
        const readerTotal = new FileReader()
        readerTotal.readAsArrayBuffer(file)
        readerTotal.onload = () =>
 {

          const sparkTotal = new SparkMD5.ArrayBuffer()
          sparkTotal.append(readerTotal.result)
          const md5 = sparkTotal.end()
          let query = {
name:md5}
    
          getKey(query).then(resKey=>
{

            const totalChunks = Math.ceil(file.size / this.chunkSize)
            let currentChunk = 0
            const config = {

              headers: {
 "Content-Type": "multipart/form-data" }
    ,
              onUploadProgress: (progressEvent) =>
 {

                this.percent = Math.round((currentChunk + 1) / totalChunks * 100)
                loading.setText('大文件正在分片上传,请稍后'+this.percent+'%')
              }
,
            }
    ;
    
            const reader = new FileReader()
            const spark = new SparkMD5.ArrayBuffer()
            const uploadChunk = () =>
 {
    
              const start = currentChunk * this.chunkSize
              const end = Math.min(file.size, start + this.chunkSize)
              reader.readAsArrayBuffer(file.slice(start, end))
              reader.onload = () =>
 {
    
                spark.append(reader.result)
                const chunk = new Blob([reader.result])
                chunk.file = file
                chunk.currentChunk = currentChunk
                chunk.totalChunks = totalChunks
                chunk.sparkMD5 = spark.end()
                const formData = new FormData()
                formData.append('chunk', chunk)
                formData.append('filename', file.name)
                formData.append('totalChunks', totalChunks)
                formData.append('key', resKey.data)
                formData.append('currentChunk', currentChunk)
                axios.post(process.env.VUE_APP_BASE_API + "app/fileupload/file/upload/chunk", formData, config)
                  .then(response =>
 {

                    if (currentChunk  totalChunks - 1) {

                      currentChunk++
                      uploadChunk()
                    }
 else {

                      let data = {
key:resKey.data,fileName:file.name}
    
                      completeUpload(data).then(res=>
{
    
                        this.fileList = []
                        loading.close();

                        this.percent = 0
                        this.$message.success('上传成功')
                        let endTime = new Date().getTime()
                        console.log('Chunk Cost:'+endTime-startTime)
                      }
)
                    }

                  }
    )
                  .catch(error =>
 {

                    this.$message.error(error.message)
                  }
)
              }

            }

            uploadChunk()
          }
    );

        }

      }
 else {

        const loading = this.$loading({

          lock: true,
          text: '正在上传,请稍后',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        }
    );
    
        const self = this;
    
        const file = params.file
        const formData = new FormData();
    
        formData.append(self.upload_name, file);

        const config = {

          headers: {
 "Content-Type": "multipart/form-data" }
    ,
          onUploadProgress: (progressEvent) =>
 {
    
            this.percent =
              ((progressEvent.loaded / progressEvent.total) * 100) | 0;

            loading.setText('正在上传,请稍后'+this.percent+'%')
          }
,
        }
    ;
    
        axios
        .post(
          process.env.VUE_APP_BASE_API + "app/fileupload/file/upload",
          formData,
          config
        )
        .then((res) =>
 {
    
          loading.close();

          this.$message.success('上传成功')
          this.percent = 0
          let endTime = new Date().getTime()
          console.log('Common Cost:'+endTime-startTime)
        }
    )
        .catch((err) =>
 {
    
          console.log(err);

        }
    );

      }

    }

  }

}
    
/script>
    

api/fileupload.js

import request from '@/utils/request'

export function getKey(query) {

  return request({

    url: '/app/fileupload/getKey',
    method: 'get',
    params: query
  }
)
}


export function completeUpload(data) {

  return request({

    url: '/app/fileupload/completeUpload',
    method: 'post',
    data
  }
)
}
    

utils/request.js

import axios from 'axios'
import {
 MessageBox, Message }
 from 'element-ui'
import store from '@/store'
import {
 getToken }
     from '@/utils/auth'
import router from '@/router'

axios.defaults.headers.common['Content-Type'] = 'application/json;
charset=UTF-8'
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
axios.defaults.withCredentials = true
// create an axios instance
const service = axios.create({

  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 50000 // request timeout
}
    )

// request interceptor
service.interceptors.request.use(
  config =>
 {

    // do something before request is sent
    if (store.getters.token) {

      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }

    return config
  }
    ,
  error =>
 {

    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
    
)

// response interceptor
service.interceptors.response.use(
  /**
   * If you want to get http information such as headers or status
   * Please return  response =>
     response
  */

  /**
   * Determine the request status by custom code
   * Here is just an example
   * You can also judge the status by HTTP Status Code
   */
  response =>
 {

    const res = response.data

    if(res.ret===200 || res.ret===404){
    
      return res;

    }

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
    
      // 50008: Illegal token;
     50012: Other clients logged in;
     50014: Token expired;

      // if (res.code === 50008 || res.code === 50012 || res.code === 50014) {

      if (res.code === 50014) {

        // to re-login
        MessageBox.confirm('您已经登出了,您可以取消继续待在此页面或者重新登录', '登出确认', {

          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }
    ).then(() =>
 {
    
          store.dispatch('user/resetToken').then(() =>
 {

            if (res.data === 2) {

              router.push(`/login`)
            }
 else {

              router.push(`/member/login`)
            }

            // location.reload()
            // const type = this.$store.getters.type
            // if (type === 1) {

            //   this.$router.push(`/member/login?redirect=${
this.$route.fullPath}
`)
            // }
 else {

            //   this.$router.push(`/login?redirect=${
this.$route.fullPath}
`)
            // }

          }
)
        }
)
      }
 else {

        Message({

          message: res.message || 'Error',
          type: 'error',
          duration: 5 * 1000
        }
)
      }

      return Promise.reject(new Error(res.message || 'Error'))
    }
 else {

      return res
    }

  }
    ,
  error =>
 {

    Message({

      message: '网络异常',
      type: 'error',
      duration: 5 * 1000
    }
)
    return Promise.reject(error)
  }
    
)

export default service

自行安装axios,spark-md5库,下面就是后端代码

import jakarta.servlet.http.HttpServletRequest;
    
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 org.springframework.web.multipart.MultipartHttpServletRequest;
    

import java.io.*;
    
import java.nio.channels.FileChannel;
    
import java.nio.file.Files;
    
import java.nio.file.Paths;
    
import java.nio.file.Path;
    
import java.text.SimpleDateFormat;
    
import java.util.*;
    
import java.util.stream.Collectors;


@RestController
@RequestMapping("/fileupload")
public class FileUploadController {


    @Value("${
file.dir}
    ")
    private String filedir;
    

    @Autowired
    private MessageUtils messageUtils;


    @RequestMapping(value="/getKey")
    public @ResponseBody ResponseData  getKey(@RequestParam String name) throws Exception{
    
        ResponseData rest = new ResponseData();
    
        rest.setRet(20000);
    
        rest.setMsg("success");
    
        String key = UUID.randomUUID().toString();
    
        rest.setData(key);
    
        AppConstants.fileKeyMd5Map.put(key,name);
    
        AppConstants.fileKeyChunkMap.put(key,new ArrayList>
    ());
    
        return rest;

    }


    @RequestMapping(value="/completeUpload")
    public @ResponseBody ResponseData  completeUpload(@RequestBody FileCompleteUploadDto fileCompleteUploadDto,HttpServletRequest request) throws Exception{
    
        ResponseData rest = new ResponseData();
    
        rest.setRet(20000);
    
        rest.setMsg("success");
    
        String key = fileCompleteUploadDto.getKey();
    
        String md5 = AppConstants.fileKeyMd5Map.get(key);
    
        //按照顺序合并文件
        ListFileChunkObjDto>
     fileChunkObjDtoList = AppConstants.fileKeyChunkMap.get(key);
    
        ListFileChunkObjDto>
     newfileChunkObjDtoList = fileChunkObjDtoList.stream().sorted(Comparator.comparing(FileChunkObjDto::getIndex)).collect(Collectors.toList());
    
        String fileExt = fileCompleteUploadDto.getFileName().substring(fileCompleteUploadDto.getFileName().lastIndexOf(".") + 1).toLowerCase();
    
        String localHost = UrlUtils.getLocalRealIp();
    
        String port = String.valueOf(request.getLocalPort());
    
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
    
        String newFileName = df.format(new Date()) + "_" + localHost + "_" + port + "_" + new Random().nextInt(1000) + "." + fileExt;
    
        File newFile = new File(tifiledir+newFileName);
    
        FileChannel resultfileChannel = new FileOutputStream(newFile).getChannel();

        for(FileChunkObjDto fileChunkObjDto:newfileChunkObjDtoList){
    
            FileChannel fileChannel = new FileInputStream(tifiledir+fileChunkObjDto.getFileName()).getChannel();
    
            resultfileChannel.transferFrom(fileChannel,resultfileChannel.size(),fileChannel.size());
    
            fileChannel.close();

        }
    
        resultfileChannel.close();
    
        //校验md5
//      System.out.println("a md5:"+md5);
    
//      System.out.println("b md5:"+ FileUtils.getMD5(tifiledir+newFileName));

        for(FileChunkObjDto fileChunkObjDto:newfileChunkObjDtoList){

            try {
    
                Path sourcePath = Paths.get(tifiledir + fileChunkObjDto.getFileName());
    
                Files.delete(sourcePath);

            }
catch (Exception e){


            }

        }
    
        //删除key
        AppConstants.fileKeyMd5Map.remove(key);
    
        AppConstants.fileKeyChunkMap.remove(key);
    
        return rest;

    }
    

    @RequestMapping(value="/file/upload/chunk")
    public @ResponseBody ResponseData  uploadChunkFile(@RequestParam MapString,String>
 map, HttpServletRequest request) throws Exception{
    
        ResponseData rest = new ResponseData();
    
        rest.setRet(20000);
    
        rest.setMsg("success");
    
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    
        MapString, MultipartFile>
     fileMap = multipartRequest.getFileMap();
    
        String key = map.get("key");
    
        String currentChunk = map.get("currentChunk");
    
        ListFileChunkObjDto>
     fileChunkObjDtoList = AppConstants.fileKeyChunkMap.get(key);
    
        FileChunkObjDto fileChunkObjDto = new FileChunkObjDto();
    
        fileChunkObjDto.setIndex(Integer.parseInt(currentChunk));
    
        String ctxPath = filedir;
    
        //创建文件夹
        File file = new File(ctxPath);

        if (!file.exists()) {
    
            file.mkdirs();

        }
    
        String localHost = UrlUtils.getLocalRealIp();
    
        String port = String.valueOf(request.getLocalPort());
    
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
    
        for (Map.EntryString, MultipartFile>
 entity : fileMap.entrySet()) {
    
            // 上传文件名
            MultipartFile mf = entity.getValue();
    
            String newFileName = df.format(new Date()) + "_" + localHost + "_" + port + "_" + new Random().nextInt(1000);
    
            fileChunkObjDto.setFileName(newFileName);
    
            fileChunkObjDtoList.add(fileChunkObjDto);
    
            String filePath = ctxPath + newFileName;
    
            File uploadFile = new File(filePath);
    
            InputStream in = null;
    
            FileOutputStream out = null;

            try {
    
                byte b[] = new byte[1024 * 1024];
    
                int i = 0;
    
                in = mf.getInputStream();
    
                out = new FileOutputStream(uploadFile);

                while ((i = in.read(b)) != -1) {
    
                    //写出读入的字节数组,每次从0到所读入的位置,不会多写
                    out.write(b, 0, i);

                }

            }
 catch (IOException e) {
    
                e.printStackTrace();

            }
 finally {

                try {

                    //关闭流
                    if (in != null) {
    
                        in.close();

                    }

                    if (out != null) {
    
                        out.close();

                    }

                }
 catch (IOException e) {
    
                    throw new RuntimeException(e);

                }

            }

        }
    
        return rest;

    }
    

    @RequestMapping(value="/file/upload")
    public @ResponseBody ResponseData  uploadFile(@RequestParam MapString,String>
 map, HttpServletRequest request) throws Exception{
    
        ResponseData rest = new ResponseData();
    
        rest.setRet(20000);
    
        rest.setMsg("success");
    
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    
        MapString, MultipartFile>
     fileMap = multipartRequest.getFileMap();
    
        String ctxPath = filedir;
    
        //创建文件夹
        File file = new File(ctxPath);

        if (!file.exists()) {
    
            file.mkdirs();

        }
    
        String fileName = null;
    
        String localHost = UrlUtils.getLocalRealIp();
    
        String port = String.valueOf(request.getLocalPort());
    
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
    
        for (Map.EntryString, MultipartFile>
 entity : fileMap.entrySet()) {
    
            // 上传文件名
            MultipartFile mf = entity.getValue();
    
            fileName = mf.getOriginalFilename();
    
            String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
    
            String newFileName = df.format(new Date()) + "_" + localHost + "_" + port + "_" + new Random().nextInt(1000) + "." + fileExt;
    
            String filePath = ctxPath + newFileName;
    
            File uploadFile = new File(filePath);
    
            InputStream in = null;
    
            FileOutputStream out = null;

            try {
    
                byte b[] = new byte[1024 * 1024];
    
                int i = 0;
    
                in = mf.getInputStream();
    
                out = new FileOutputStream(uploadFile);

                while ((i = in.read(b)) != -1) {
    
                    //写出读入的字节数组,每次从0到所读入的位置,不会多写
                    out.write(b, 0, i);

                }

            }
 catch (IOException e) {
    
                e.printStackTrace();

            }
 finally {

                try {

                    //关闭流
                    if (in != null) {
    
                        in.close();

                    }

                    if (out != null) {
    
                        out.close();

                    }

                }
 catch (IOException e) {
    
                    throw new RuntimeException(e);

                }

            }

        }
    
        return rest;

    }



}
    

AppConstants.java

public static final MapString,String>
     fileKeyMd5Map = new HashMap>
(){
}
    ;
    
public static final MapString, ListFileChunkObjDto>
    >
     fileKeyChunkMap = new HashMap>
(){
}
    ;
    

UrlUtils.java

public class UrlUtils {


    public static int validateUrl(String url){
    
        URL u = null;
    
        HttpURLConnection urlconn=null;
    
        int state = 0;

        do{

            try {
    
                u = new URL(url);
    
                urlconn = (HttpURLConnection) u.openConnection();
    
                //System.out.println(urlconn.);
    
                urlconn.setConnectTimeout(2000);
    
                state = urlconn.getResponseCode();

            }
catch(SocketTimeoutException e){
    
                state = 2;
    
                e.printStackTrace();

            }
catch (Exception e) {
    
                e.printStackTrace();
    
                break;

            }

        }
    while(false);
    
        
        return state;

    }


    public static  String getLocalRealIp(){
    
            InetAddress addr = null;

            try {
    
                addr = InetAddress.getLocalHost();

            }
 catch (UnknownHostException e) {
    
                e.printStackTrace();

            }
    

            byte[] ipAddr = addr.getAddress();
    
            //String ipAddrStr = "";
    
            StringBuilder ipAddrStr = new StringBuilder();
    
            for (int i = 0;
     i  ipAddr.length;
 i++) {
    
                if (i >
 0) {
    
                    ipAddrStr.append(".");

                }
    
                ipAddrStr.append(ipAddr[i] &
     0xFF);

            }
    
            //System.out.println(ipAddrStr);
    
            return ipAddrStr.toString();

    }

    
    /** 
     * 获取当前网络ip 
     * @param request 
     * @return 
     */  
    public static String getIpAddr(HttpServletRequest request){
      
        String ipAddress = request.getHeader("x-forwarded-for");
  
            if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
      
                ipAddress = request.getHeader("Proxy-Client-IP");
  
            }
  
            if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
      
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
  
            }
  
            if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
      
                ipAddress = request.getRemoteAddr();
  
                if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
      
                    //根据网卡取本机配置的IP  
                    InetAddress inet=null;
  
                    try {
      
                        inet = InetAddress.getLocalHost();
  
                    }
 catch (UnknownHostException e) {
      
                        e.printStackTrace();
  
                    }
      
                    ipAddress= inet.getHostAddress();
  
                }
  
            }
      
            //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割  
            if(ipAddress!=null &
    &
     ipAddress.length()>
15){
     //"***.***.***.***".length() = 15  
                if(ipAddress.indexOf(",")>
0){
      
                    ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
  
                }
  
            }
      
            return ipAddress;
   
    }

    
    public static void main(String []args){
    
        System.out.println(getLocalRealIp());

    }

}
    

ResponseData.java

import com.alibaba.fastjson.JSON;
    
import com.github.pagehelper.PageInfo;
    
import org.springframework.util.Assert;
    

import java.util.Iterator;
    
import java.util.LinkedHashMap;
    
import java.util.Map;
    

/**
 * 目前vue前台解析返回格式是这样的
 */
public class ResponseData extends LinkedHashMapString, Object>
 {
    
    //设置返回标准
    public static final String RET = "code";
    //返回代码
    public static final String MSG = "message";
    //返回信息
    public static final String DATA = "data";
    //其他内容
    public static final String TIMESTAMP = "timestamp";


    public ResponseData() {
    
        this.setRet(200).setMsg("请求成功").setTimestamp(System.currentTimeMillis());

    }


    public ResponseData(int ret, String msg) {
    
        this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis());

    }


    public ResponseData(int ret, String msg, Object attributeValue) {
    
        this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()).setData(attributeValue);

    }


    public ResponseData(int ret, String msg, PageInfo pageData) {
    
        this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()).setPageData(pageData);

    }


    public ResponseData(int ret, String msg, String attributeName, Object attributeValue) {
    
        this.setRet(ret).setMsg(msg).setTimestamp(System.currentTimeMillis()).addAttribute(attributeName, attributeValue);

    }


    public int getRet() {
    
        return (Integer)super.get(RET);

    }


    public ResponseData setRet(int ret) {
    
        this.put(RET, ret);
    
        return this;

    }


    public long getTimestamp() {
    
        return (Long)super.get(TIMESTAMP);

    }


    public ResponseData setTimestamp(long timestamp) {
    
        this.put(TIMESTAMP, timestamp);
    
        return this;

    }


    public String getMsg() {
    
        return (String)super.get(MSG);

    }


    public ResponseData setMsg(String msg) {
    
        this.put(MSG, msg);
    
        return this;

    }


    public ResponseData setData(Object attributeValue) {
    
        this.put(DATA, attributeValue);
    
        return this;

    }


    public ResponseData setPageData(PageInfo pageinfo){
    
        PageData pageData = new PageData(pageinfo);
    
        this.put(DATA,pageData);
    
        return this;

    }


    public ResponseData addAttribute(String attributeName, Object attributeValue) {
    
        Assert.notNull(attributeName, "Model attribute name must not be null");
    
        this.put(attributeName, attributeValue);
    
        return this;

    }
    

    public ResponseData addAllAttributes(MapString, ?>
 attributes) {

        if (attributes != null) {
    
            this.putAll(attributes);

        }
    

        return this;

    }
    

    public ResponseData mergeAttributes(MapString, ?>
 attributes) {

        if (attributes != null) {
    
            Iterator var2 = attributes.entrySet().iterator();


            while(var2.hasNext()) {
    
                Map.EntryString,?>
     entry = (Map.EntryString,?>
    )var2.next();

                if (!this.containsKey(entry.getKey())) {
    
                    this.put(entry.getKey(), entry.getValue());

                }

            }

        }
    

        return this;

    }


    public boolean containsAttribute(String attributeName) {
    
        return this.containsKey(attributeName);

    }

    public String toJsonString() {
    
        return JSON.toJSONString(this);

    }

}
    

FileChunkObjDto.java和FileCompleteUploadDto.java

@Data
public class FileChunkObjDto {
    
    private Integer index;
    
    private String fileName;

}


@Data
public class FileCompleteUploadDto {
    

    private String key;
    
    private String fileName;

}
    

md5的比较校验可以自行做业务判断,缓存的处理可以使用redis做终端断点续传的长期存储改造。

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


若转载请注明出处: Vue Elementui+SpringBoot做大文件切片上传
本文地址: https://pptw.com/jishu/292978.html
高性能的本地缓存方案选型,看这篇就够了! Vue3 尝鲜 Hook + TypeScript 取代 Vuex 实现图书管理小型应用

游客 回复需填写必要信息