大文件分片、并发上传,断点续传,秒传 第二弹

继上次大文件分块上传原理见:http://blog.csdn.net/haohao123nana/article/details/51279098,博主终于有时间来真正的代码实现它。

关键部分

  • 前端用file.slice()分块
  • 前端用FileReader获取每一分块的md5值
  • 前端上传每一分块前,都会去服务器校验分片是否已上传,以此来支持断点续传
  • 后端用MultipartFile接受分块文件
  • 后端用FileOutputStream拼装分块文件

一、话不多说,直接上代码,我想这是你们最喜欢的

html

<!DOCTYPE HTML>
<html>
<head>

    <meta charset="utf-8">

    <title>HTML5大文件分片上传示例</title>

    <script src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
	<script src="md5.js"></script>
	<script>
	
	var	i = -1;
					
	var succeed = 0;
	
	var databgein;  //开始时间
	
	var dataend;    //结束时间
	
	var action=false;    //false检验分片是否上传过(默认); true上传文件

    var page = {

        init: function(){
            
			$("#upload").click(function(){
			databgein=new Date();
            var file = $("#file")[0].files[0];  //文件对象

			 isUpload(file);
            });

        } 

    };

    $(function(){

        page.init();

    });
	
	function isUpload (file) {

                //构造一个表单,FormData是HTML5新增的

                var form = new FormData();

			   
             var r = new FileReader();
		
              r.readAsBinaryString(file);
		       
			   $(r).load(function(e){
			    
                var bolb = e.target.result;
				
               var md5 = hex_md5(bolb);
		         
				form.append("md5", md5);  
		
		 
                //Ajax提交

                $.ajax({

                    url: "http://localhost:8080//bookQr/test/isUpload",

                    type: "POST",

                    data: form,

                    async: true,        //异步

                    processData: false,  //很重要,告诉jquery不要对form进行处理

                    contentType: false,  //很重要,指定为false才能形成正确的Content-Type

                    success: function(data){
					
					var dataObj = eval("("+data+")");
					
					     var uuid = dataObj.fileId;
						 var date = dataObj.date;
						 
						if (dataObj.flag == "1") {
						//没有上传过文件
                         upload(file,uuid,md5,date);

                        } else if(dataObj.flag == "2") {
                        //已经上传部分
						upload(file,uuid,md5,date);
						                               
                       } else if(dataObj.flag == "3") {
                        //文件已经上传过
                        alert("文件已经上传过,秒传了!!");	
                        $("#uuid").append(uuid);						
						                                				 
                       }

                    },error: function(XMLHttpRequest, textStatus, errorThrown) {
					 
					  alert("服务器出错!");

                    }

                });
  
               })               
	
	}
	
	/*
	 * file 文件对象
	 * uuid 后端生成的uuid
	 * filemd5 整个文件的md5
	 * date  文件第一个分片上传的日期(:20170122)
	*/
	function upload (file,uuid,filemd5,date) {

    name = file.name;        //文件名

    size = file.size;        //总大小
	              
    var shardSize = 5 * 1024 * 1024,    //以5MB为一个分片

    shardCount = Math.ceil(size / shardSize);  //总片数
	
		
		if (i > shardCount) {
		
            i = -1;
			
            i = shardCount;
			
        } else {
		
			if(!action){
				
			i += 1;  //只有在检测分片时,i才去加1; 上传文件时无需加1
				
			}

        }
       
                //计算每一片的起始与结束位置

                var start = i * shardSize,

                end = Math.min(size, start + shardSize);


                //构造一个表单,FormData是HTML5新增的

                var form = new FormData();
				
				if(!action){
				
				form.append("action", "check");  //检测分片是否上传
				$("#param").append("action==check ");
				
				}else{
				
				form.append("action", "upload");  //直接上传分片
				
				form.append("data", file.slice(start,end));  //slice方法用于切出文件的一部分
				$("#param").append("action==upload ");
				}
                
                form.append("uuid", uuid);
				
				form.append("filemd5", filemd5);
				
				form.append("date", date);
				
				form.append("name", name);
				
				form.append("size", size);

                form.append("total", shardCount);  //总片数

                form.append("index", i+1);        //当前是第几片
				
				var ssindex = i+1;
				$("#param").append("index=="+ssindex+"<br/>");
				
			   //按大小切割文件段  
               var data = file.slice(start, end);
			   
             var r = new FileReader();
		
              r.readAsBinaryString(data);
		       
			   $(r).load(function(e){
			    
                var bolb = e.target.result;
				
               var md5 = hex_md5(bolb);
		         
				form.append("md5", md5);  
		
		 
                //Ajax提交

                $.ajax({

                    url: "http://localhost:8080//bookQr/test/upload",

                    type: "POST",

                    data: form,

                    async: true,        //异步

                    processData: false,  //很重要,告诉jquery不要对form进行处理

                    contentType: false,  //很重要,指定为false才能形成正确的Content-Type

                    success: function(data){
					
						var dataObj = eval("("+data+")");
					
					    var fileuuid = dataObj.fileId;
						
						var flag = dataObj.flag;
						
						//服务器返回该分片是否上传过
						if(!action){
						
						   if (flag == "1") {
						    //未上传
                            action = true;
                           } else if(dataObj.flag == "2") {
                            //已上传
						    action = false;
                             ++succeed;						       
                          }
				
				           //递归调用                        
                          upload(file,uuid,filemd5,date);
				
				        }else{
						//服务器返回分片是否上传成功
				
						   //改变界面
                           ++succeed;

                           $("#output").text(succeed + " / " + shardCount);
						
						   if (i+1 == shardCount) {
												 
					    	 dataend=new Date();
						 
						    $("#uuid").append(fileuuid);
						 
						    $("#usetime").append(dataend.getTime()-databgein.getTime());

                           } else {						   
						     //已上传成功,然后检测下一个分片
						    action = false;
						     //递归调用                        
                            upload(file,uuid,filemd5,date);
				 
                         }

				
				        }	

                    },error: function(XMLHttpRequest, textStatus, errorThrown) {
					 
					  alert("服务器出错!");

                    }

                });
  
               })               
	
	}


	</script>



</head>

<body>

    <input type="file" id="file" />

    <button id="upload">上传</button>

    <span id="output" style="font-size:12px">等待</span>
		
	
	<span id="usetime" style="font-size:12px;margin-left:20px;">用时</span>
	
	<span id="uuid" style="font-size:12px;margin-left:20px;">uuid==</span>
	
	<br/>
	
	<br/>
	<br/>
	<br/>
	<span id="param" style="font-size:12px;margin-left:20px;">param==</span>
	


</body>

</html>


controller

package com.yxtech.sys.controller;

import com.yxtech.common.Constant;
import com.yxtech.sys.domain.FileRes;
import com.yxtech.sys.service.FileResService;
import com.yxtech.sys.service.ResService;
import com.yxtech.utils.file.FileMd5Util;
import com.yxtech.utils.file.NameUtil;
import com.yxtech.utils.file.PathUtil;
import jodd.datetime.JDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by Administrator on 2015/10/9.
 */
@RestController
@Scope("prototype")
@RequestMapping(value = "/test")
public class testController {
    @Autowired
    private FileResService fileResService;
    @Autowired
    private ResService resService;


    /**
     * 上传文件
     *
     * @param request
     * @return
     * @throws IllegalStateException
     * @throws IOException
     */
    @RequestMapping(value = "/upload")
    public Map<String, Object> upload(
            HttpServletRequest request, @RequestParam(value = "data",required = false) MultipartFile multipartFile) throws IllegalStateException, IOException, Exception {

        String action = request.getParameter("action");
        String uuid = request.getParameter("uuid");
        String fileName = request.getParameter("name");
        String size = request.getParameter("size");//总大小
        int total = Integer.valueOf(request.getParameter("total"));//总片数
        int index = Integer.valueOf(request.getParameter("index"));//当前是第几片
        String fileMd5 = request.getParameter("filemd5"); //整个文件的md5
        String date = request.getParameter("date"); //文件第一个分片上传的日期(:20170122)
        String md5 = request.getParameter("md5"); //分片的md5

        //生成上传文件的路径信息,按天生成
        String savePath = Constant.FILE_PATH + File.separator + date;
        String saveDirectory = PathUtil.getAppRootPath(request) + savePath + File.separator + uuid;
        //验证路径是否存在,不存在则创建目录
        File path = new File(saveDirectory);
        if (!path.exists()) {
            path.mkdirs();
        }
        //文件分片位置
        File file = new File(saveDirectory, uuid + "_" + index);

        //根据action不同执行不同操作. check:校验分片是否上传过; upload:直接上传分片
        Map<String, Object> map = null;
        if("check".equals(action)){
            String md5Str = FileMd5Util.getFileMD5(file);
            if (md5Str != null && md5Str.length() == 31) {
                System.out.println("check length =" + md5.length() + " md5Str length" + md5Str.length() + "   " + md5 + " " + md5Str);
                md5Str = "0" + md5Str;
            }
            if (md5Str != null && md5Str.equals(md5)) {
                //分片已上传过
                map = new HashMap<>();
                map.put("flag", "2");
                map.put("fileId", uuid);
                map.put("status", true);
                return map;
            } else {
                //分片未上传
                map = new HashMap<>();
                map.put("flag", "1");
                map.put("fileId", uuid);
                map.put("status", true);
                return map;
            }
        }else if("upload".equals(action)){
            //分片上传过程中出错,有残余时需删除分块后,重新上传
            if (file.exists()) {
                file.delete();
            }
            multipartFile.transferTo(new File(saveDirectory, uuid + "_" + index));
        }

        if (path.isDirectory()) {
            File[] fileArray = path.listFiles();
            if (fileArray != null) {
                if (fileArray.length == total) {
                    //分块全部上传完毕,合并
                    String suffix = NameUtil.getExtensionName(fileName);

                    File newFile = new File(PathUtil.getAppRootPath(request) + savePath, uuid + "." + suffix);
                    FileOutputStream outputStream = new FileOutputStream(newFile, true);//文件追加写入
                    byte[] byt = new byte[10 * 1024 * 1024];
                    int len;

                    FileInputStream temp = null;//分片文件
                    for (int i = 0; i < total; i++) {
                        int j = i + 1;
                        temp = new FileInputStream(new File(saveDirectory, uuid + "_" + j));
                        while ((len = temp.read(byt)) != -1) {
                            System.out.println("-----" + len);
                            outputStream.write(byt, 0, len);
                        }
                    }
                    //关闭流
                    temp.close();
                    outputStream.close();
                    //修改FileRes记录为上传成功
                    Example example = new Example(FileRes.class);
                    Example.Criteria criteria = example.createCriteria();
                    criteria.andEqualTo("md5",fileMd5);
                    FileRes fileRes = new FileRes();
                    fileRes.setStatus(Constant.ONE);
                    fileResService.updateByExampleSelective(fileRes,example);
                }else if(index == 1){
                    //文件第一个分片上传时记录到数据库
                    FileRes fileRes = new FileRes();
                    String name = NameUtil.getFileNameNoEx(fileName);
                    if (name.length() > 50) {
                        name = name.substring(0, 50);
                    }
                    fileRes.setName(name);
                    fileRes.setSuffix(NameUtil.getExtensionName(fileName));
                    fileRes.setUuid(uuid);
                    fileRes.setPath(savePath + File.separator + uuid + "." + fileRes.getSuffix());
                    fileRes.setSize(Integer.parseInt(size));
                    fileRes.setMd5(fileMd5);
                    fileRes.setStatus(Constant.ZERO);
                    fileRes.setCreateTime(new Date());
                    this.fileResService.insert(fileRes);
                }
            }
        }

        map = new HashMap<>();
        map.put("flag", "3");
        map.put("fileId", uuid);
        map.put("status", true);
        return map;
    }

    /**
     * 上传文件前校验
     *
     * @param request
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/isUpload")
    public Map<String, Object> isUpload(HttpServletRequest request) throws Exception {

        String md5 = request.getParameter("md5");

        Example example = new Example(FileRes.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("md5", md5);
        List<FileRes> list = fileResService.selectByExample(example);

        Map<String, Object> map = null;


        return map;
    }


}

FileMd5Util

package com.yxtech.utils.file;

import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.security.MessageDigest;

/**
 * @author cuihao
 * @create 2017-01-20-15:13
 */

public class FileMd5Util {
    public static final String KEY_MD5 = "MD5";
    public static final String CHARSET_ISO88591 = "ISO-8859-1";
    /**
     * Get MD5 of one file:hex string,test OK!
     *
     * @param file
     * @return
     */
    public static String getFileMD5(File file) {
        if (!file.exists() || !file.isFile()) {
            return null;
        }
        MessageDigest digest = null;
        FileInputStream in = null;
        byte buffer[] = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }

    /***
     * Get MD5 of one file!test ok!
     *
     * @param filepath
     * @return
     */
    public static String getFileMD5(String filepath) {
        File file = new File(filepath);
        return getFileMD5(file);
    }

    /**
     * MD5 encrypt,test ok
     *
     * @param data
     * @return byte[]
     * @throws Exception
     */
    public static byte[] encryptMD5(byte[] data) throws Exception {

        MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
        md5.update(data);
        return md5.digest();
    }

    public static byte[] encryptMD5(String data) throws Exception {
        return encryptMD5(data.getBytes(CHARSET_ISO88591));
    }
    /***
     * compare two file by Md5
     *
     * @param file1
     * @param file2
     * @return
     */
    public static boolean isSameMd5(File file1,File file2){
        String md5_1= FileMd5Util.getFileMD5(file1);
        String md5_2= FileMd5Util.getFileMD5(file2);
        return md5_1.equals(md5_2);
    }
    /***
     * compare two file by Md5
     *
     * @param filepath1
     * @param filepath2
     * @return
     */
    public static boolean isSameMd5(String filepath1,String filepath2){
        File file1=new File(filepath1);
        File file2=new File(filepath2);
        return isSameMd5(file1, file2);
    }

    public static void main(String[] args) {
//        for(int i=1;i<79;i++){
//            File file = new File("F:\\bookQr_SYS\\target\\bookQr\\files\\20170122\\2124b3f9-a4c8-4297-a182-6249010dcd72\\2124b3f9-a4c8-4297-a182-6249010dcd72_"+i);
            File file = new File("F:\\bookQr_SYS\\target\\bookQr\\files\\20170122\\2124b3f9-a4c8-4297-a182-6249010dcd72.mp4");
            String md5Str = getFileMD5(file);
            System.out.println(""+md5Str.length()+" "+md5Str);
//        }

    }
}

NameUtil


/**
 * Created by Administrator on 2015/10/12.
 */
public class NameUtil {
    /**
     * Java文件操作 获取文件扩展名
     */
    public static String getExtensionName(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length() - 1))) {
                return filename.substring(dot + 1);
            }
        }
        return filename.toLowerCase();
    }

    /**
     * Java文件操作 获取不带扩展名的文件名
     */
    public static String getFileNameNoEx(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length()))) {
                return filename.substring(0, dot);
            }
        }
        return filename.toLowerCase();
    }

    public static void main(String[] args) {
        String str = "AAAbb.jpg";
        System.out.println(getExtensionName(str).toLowerCase());
        System.out.println(getFileNameNoEx(str).toUpperCase());
    }
}

PathUtil


import com.yxtech.sys.controller.MailController;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * Created by Administrator on 2015/10/12.
 */
public class PathUtil {

    /**
     *
     * @param request
     * @return 返回结果类似于 “F:\workSpace\bookQr\src\main\webapp\”
     */
    public static String  getAppRootPath(HttpServletRequest request){
        //ServletActionContext.getServletContext().getRealPath("/")+"upload";
        return request.getSession().getServletContext().getRealPath("/");
    }

    /**
     *自定义文件保存路径
     * @param request
     */
    public static String  getCustomRootPath(HttpServletRequest request){
        String path = "";
        Properties prop = new Properties();
        InputStream in = MailController.class.getResourceAsStream("/config/jdbc.properties");
        try {
            prop.load(in);
            path = prop.getProperty("FILE_PATH").trim();

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

    /**
     *
     * @param request
     * @return http://www.qh.com:8080/projectName
     */
    public static String  getHttpURL(HttpServletRequest request) {
        StringBuffer buff = new StringBuffer();
        buff.append("http://");
        buff.append(request.getServerName());
        buff.append(":");
        buff.append(request.getServerPort());
        buff.append(request.getContextPath());
        return buff.toString();
    }


}

二、效果图

下面以一个56.1M的视频演示。5M一片,总共分了12片。

1、上传完成界面:
在这里插入图片描述

2、重复上传界面:
在这里插入图片描述

3、服务端界面,左边是分片文件夹,右边是合成的视频
在这里插入图片描述
在这里插入图片描述
4、数据库表存储记录
在这里插入图片描述

下载地址: http://download.csdn.net/download/haohao123nana/10015607(已失效)

新的下载地址: https://download.csdn.net/download/haohao123nana/13107245

在这里插入图片描述

有问题加群qq:221070971

  • 22
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 53
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 53
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haohao123nana

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值