layui 文件实现分片上传和 断点续传 和 急速秒传 SpringBoot JAVA

1,384 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一丶分片上传

分片上传 slice()这个方法功能是将一个文件切割为一系列特定大小的小数据片,分别将这些小数据片分别上传到服务端,全部上传完后再由服务端将这些小数据片合并成为一个完整的资源。

二 丶 断点续传

断点续传需要使用 MD5生成一个文件的唯一码,后台数据库会存下上传记录也就是分片位置和总分片数。 思路就是每次上传之前先拿到MD5的唯一码去数据库查到文件的上传记录以及分片位置和总片数返回给前端从下一个分片开始上传。

三丶 急速秒传

上传之前去拿到MD5唯一码去查数据如果已经有了文件就返回给前台,不用重新上传实现急速秒传..

在这里插入图片描述

后端就是每次上传时判断下标等不等文件总片数 等于时就去合并文件 不等于继续上传下个分片文件

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link type="text/css" rel="stylesheet" href="/static/layui-v2.5.5/css/layui.css">
<!--    <link rel="stylesheet" href="lib/layui-v2.5.5/css/layui.css" media="all">-->
    <style type="text/css">
        .loading{position:fixed;
            width:300px;
            left:50%;
            margin-left:-150px;
            top:200px;
            height:18px;
            border-radius:10px;
            z-index:9999;
            overflow:hidden;
            display:none;}
        .barmy{
            background:#5FB878;
        }
        #bigdiv{
            width: 600px;
            height: 600px;
            position: absolute;
            top: 50%;
            left: 50%;
            -webkit-transform: translate(-50%, -50%);
            -moz-transform: translate(-50%, -50%);
            -ms-transform: translate(-50%, -50%);
            -o-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
            box-shadow: 0 2px 4px rgba(0, 0, 0, .12),
            0 0 6px rgba(0, 0, 0, .04);
        }
        #fileUpload{
            position: absolute;
            top: 80%;
            left: 50%;
            -webkit-transform: translate(-50%, -50%);
            -moz-transform: translate(-50%, -50%);
            -ms-transform: translate(-50%, -50%);
            -o-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
            box-shadow: 0 2px 4px rgba(0, 0, 0, .12),
            0 0 6px rgba(0, 0, 0, .04);
        }
    </style>
</head>
<body>
<div id="bigdiv">
    <button type="button" class="layui-btn" id="fileUpload">上传</button>
        <div id="mm">

        </div>
    <div class="loading">
        <div class="layui-progress layui-progress-big" lay-showpercent="true" lay-filter="uploadProgress">
            <div class="layui-progress-bar barmy" lay-percent="0%"></div>
        </div>
    </div>
</div>
</body>
<script src="/static/layui-v2.5.5/layui.js"></script>
<script src="/static/layui-v2.5.5/sparkmd5.js"></script>
<script type="text/javascript">
    /**
     * @param file 文件
     * @param chunkSize 分片大小
     * @returns Promise
     */
    function getmd5(file, chunkSize) {
        return new Promise((resolve, reject) => {
            let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
            let chunks = Math.ceil(file.size / chunkSize);
            let currentChunk = 0;
            let spark = new SparkMD5.ArrayBuffer();
            let fileReader = new FileReader();
            fileReader.onload = function(e) {
                spark.append(e.target.result);
                currentChunk++;
                if (currentChunk < chunks) {
                    loadNext();
                } else {
                    let md5 = spark.end();
                    resolve(md5);
                    //  console.log(md5);
                }
            };
            fileReader.onerror = function(e) {
                reject(e);
            };
            function loadNext() {
                let start = currentChunk * chunkSize;
                let end = start + chunkSize;
                if (end > file.size){
                    end = file.size;
                }
                fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
            }
            loadNext();
        });
    }



    layui.use([ 'upload', 'element'], function () {
        var $ = layui.jquery
            , upload = layui.upload;
        var element = layui.element;
        /**
         * start end 文件切割的开始和结束位置
         * switchC 开关
         * updateobj 存出layui的update对象方便后面再掉上传
         */
        let fileShardIndex,start,end,fileSize,fileKey,data,fileShard,
            fileShardTotal,fileName,fileSuffix,fileShardSize,fileY,
            updateobj,switchC;
        upload.render({
            elem: '#fileUpload',
            url: '/bigupdate', //处理上传文件接口
            accept: 'file',
            auto: false,
            acceptMime: '*',//允许上传的文件类型
            ext: 'mp4|jpg', //
            choose: function (obj) {
                fileShardSize = 1*1024 * 1024; //每片文件大小
                data = this.data;
                //var files = obj.pushFile();
                updateobj=obj;
                obj.preview(function (index, file, result) {
                    getmd5(file,fileShardSize).then(e =>{
                        switchC=false;
                        fileShardIndex=1;//分片索引
                        fileY=file;
                        fileKey=e;
                        fileSize=file.size;
                        fileShardTotal=Math.ceil(file.size/fileShardSize);//分片总数
                        fileName=file.name;
                        fileSuffix = fileName.substr(fileName.lastIndexOf('.') + 1);
                        fileName = fileName.substr(0, fileName.lastIndexOf('.'));
                        data.fileName = fileName;
                        data.fileShardTotal=fileShardTotal;
                        data.fileKey=fileKey;
                        data.fileSuffix=fileSuffix;
                        data.fileShardSize=fileShardSize;
                        data.fileSize=fileSize;
                        element.progress('uploadProgress', fileShardIndex*10+'%');
                        $('.loading').show();
                        $.ajax({
                            type:"post",
                            data: {"shardKey":fileKey},
                            url:"/checkFile",
                            success:function(res){
                                if(res.code==200){
                                    //新文件
                                    start=(fileShardIndex-1)*fileShardSize;
                                    end =Math.min(file.size,start+fileShardSize);
                                    fileShard=file.slice(start,end);//从文件中获取当前分片数据

                                }else if(res.code==220){
                                    fileShardIndex=res.ShardIndex;
                                    //有上传未完成的
                                    start=(fileShardIndex-1)*fileShardSize;
                                    end =Math.min(file.size,start+fileShardSize);
                                        fileShard=file.slice(start,end);//从文件中获取当前分片数据
                                }else if (res.code==240){
                                    //急速上传
                                    element.progress('uploadProgress', '100%');
                                   // shardIndex=res.ShardIndex;
                                    switchC=true;
                                    $('.loading').show();
                                    setTimeout(function (){
                                        alert("极速上传成功");
                                        $('.loading').hide();
                                    },1000);
                                    var div ='<video id="mp4s"  controls="controls" autobuffer="autobuffer"  autoplay="autoplay" loop="loop">\n' +
                                        '                        <source  src='+res.src+' type="video/mp4"></source>\n' +
                                        '                    </video>';

                                    $("#mm").append(div);
                                    $("#mp4s").css({"width":"352px","height":"200px","margin-left":"120px","margin-top":"150px"});
                                }
                                //读取base64str
                                    let fileReader = new FileReader();
                                    fileReader.onload=function (e){
                                        let  base64str=e.target.result;
                                        data.base64=base64str;
                                        data.fileShardIndex=fileShardIndex;
                                        if(switchC==false){
                                            obj.upload(data.base64,data.fileName,data.fileKey,
                                                data.fileShardTotal,data.fileShardIndex,data.fileSuffix,data.fileSize,data.fileShardSize);
                                        }
                                    }
                                    fileReader.readAsDataURL(fileShard);
                            }
                        });


                    })

                });
            },
            done: function (res) {
                if(res.code==200){
                    if(res.shardIndex<fileShardTotal){
                        fileShardIndex=fileShardIndex+1;
                        start=(fileShardIndex-1)*fileShardSize;
                        end =Math.min(fileY.size,start+fileShardSize);
                        fileSize=fileY.size;
                        data.fileShardIndex=fileShardIndex;
                        var fileShardtem=fileY.slice(start,end);//从文件中获取当前分片数据
                            let fileReader = new FileReader();
                            fileReader.onload = function (e) {
                              let  base64str = e.target.result;
                                data.base64=base64str;
                                updateobj.upload(data.base64,fileName,fileKey,
                                    fileShardTotal,data.fileShardIndex,fileSuffix,fileSize,fileShardSize);
                            }
                            fileReader.readAsDataURL(fileShardtem);
                        element.progress('uploadProgress', Math.ceil(fileShardIndex * 100 / fileShardTotal) + '%');

                    }
                }else if(res.code==100){
                    var div ='<video id="mp4s"  controls="controls" autobuffer="autobuffer"  autoplay="autoplay" loop="loop">\n' +
                        '                        <source  src='+res.src+' type="video/mp4"></source>\n' +
                        '                    </video>';
                    $("#mm").append(div);
                    $("#mp4s").css({"width":"352px","height":"200px","margin-left":"120px","margin-top":"150px"});
                    setTimeout(function (){
                        alert("上传完成");
                        $("#mp4s").attr("src",res.src);
                        $('.loading').hide();
                    },1000)
                }

            }
        });
    });
</script>
</html>

后端代码

package com.xk.controller;


import com.xk.entity.FileUpdate;
import com.xk.service.IFileUpdateService;
import com.xk.utils.Base64ToFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author xk柠檬
 * @since 2020-12-17
 */

@Controller
public class FileUpdateController {

    @Autowired
    IFileUpdateService iFileUpdateService;

    @RequestMapping(value = "/bigupdate",method = RequestMethod.POST)
    @ResponseBody
    public Map<String,Object>  upload(FileUpdate fileUpdate) throws Exception {
        Map<String,Object> map = new HashMap<String, Object>() ;
        //base64转文件
        MultipartFile multipartFile = Base64ToFile.Base64ToFilepart(fileUpdate.getBase64());
        //实际存储路径
        String path ="F:\\updateTest\\"+fileUpdate.getFileKey()+"."+fileUpdate.getFileShardIndex();
        multipartFile.transferTo(new File(path));
        //插入逻辑地址 MvcConfig.class中做了一个路径映射
        fileUpdate.setFilePath("/Path/"+fileUpdate.getFileKey());
        iFileUpdateService.selectWhereKeyUpdate(fileUpdate);
        //判断当前分片是否为最后一个
        if(fileUpdate.getFileShardIndex().equals(fileUpdate.getFileShardTotal())){
            map.put("code",100);
            map.put("src",fileUpdate.getFilePath()+"."+fileUpdate.getFileSuffix());
            fileUpdate.setFilePath("F:\\updateTest\\"+fileUpdate.getFileKey());
            //合并分片
            merge(fileUpdate);
            return map;
        }else{
            map.put("code",200);
            map.put("shardIndex",fileUpdate.getFileShardIndex());
        }
        return map;
    }
    @RequestMapping("/checkFile")
    @ResponseBody
    public Map<String,Object> checkFile(@RequestParam String shardKey){
        Map<String,Object> map = new HashMap<String, Object>();
        FileUpdate fileUpdate = iFileUpdateService.selectWhereKey(shardKey);
        if(fileUpdate==null){
            //新的文件
            map.put("code",200);
        }else if(fileUpdate!=null){
            if(fileUpdate.getFileShardIndex()<fileUpdate.getFileShardTotal()){
                map.put("code",220);
                map.put("ShardIndex",fileUpdate.getFileShardIndex()+1);
            }else if(fileUpdate.getFileShardIndex()==fileUpdate.getFileShardTotal()){
                map.put("code",240);
                map.put("src",fileUpdate.getFilePath()+"."+fileUpdate.getFileSuffix());
            }
        }
        return map;
    }
    /*@GetMapping("/GetInfo")
    public FileUpdate GetInfo(){
        FileUpdate byId = iFileUpdateService.getById(1);
        return byId;
    }*/
    @GetMapping("/")
    public String  gouindex(){
        System.out.println("去首页");
            return "index";
        }
    //@RequestMapping("merge")
    public void merge(FileUpdate fileUpdate) throws Exception {
            Map<String,Object> map = new HashMap<String, Object>();
            //输出文件
            File newfile = new File(fileUpdate.getFilePath()+"."+fileUpdate.getFileSuffix());
            //文件追加
            FileOutputStream outputStream =new FileOutputStream(newfile,true);
            //分片文件
            FileInputStream fileInputStream=null;
            byte[] byt=new byte[10*1024*1024];
            int len;
            try {
                for (int i =0;i<fileUpdate.getFileShardTotal();i++){
                    fileInputStream=new FileInputStream(new File(fileUpdate.getFilePath()+"."+(i+1)));
                    while ((len=fileInputStream.read(byt))!=-1){
                        outputStream.write(byt,0,len);
                    }
                }
            }catch(IOException e){
                System.out.println("合并异常"+e.toString());
            }finally {
                try{
                if(fileInputStream!=null){
                    fileInputStream.close();
                }
                outputStream.close();
                System.out.println("IO关闭成功");
                }catch (Exception e){
                    System.out.println("IO关闭异常"+e);
                }
            }
            System.gc();
            Thread.sleep(100);
            //删除分片
        for (int i=0;i<fileUpdate.getFileShardTotal();i++){
            String path=fileUpdate.getFilePath()+"."+(i+1);
            File file = new File(path);
            boolean delete = file.delete();
        }
        //删除分片结束
       }

}

数据库 在这里插入图片描述

CREATE TABLE `file_update` (
  `file_id` int(11) NOT NULL AUTO_INCREMENT,
  `file_name` varchar(255) DEFAULT NULL,
  `file_path` varchar(255) DEFAULT NULL,
  `file_suffix` varchar(255) DEFAULT NULL,
  `file_size` varchar(255) DEFAULT NULL,
  `file_shard_index` int(11) DEFAULT NULL,
  `file_shard_size` varchar(255) DEFAULT NULL,
  `file_shard_total` varchar(255) DEFAULT NULL,
  `file_key` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`file_id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;

效果展示 在这里插入图片描述 测试 分片上传 断点续传 急速秒传 成功没有问题!!页面不好凑活看看 源码 源码我放在码云上了有需要的可以去下载