继上次大文件分块上传原理见: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-Typesuccess: 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-Typesuccess: 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 {@Autowiredprivate FileResService fileResService;@Autowiredprivate 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"); //整个文件的md5String 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

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

  1. .NetCore+WebUploader实现大文件分片上传

    项目要求通过网站上传大文件,比如视频文件,通过摸索实现了文件分片来上传,然后后台进行合并. 使用了开源的前台上传插件WebUploader(http://fex.baidu.com/webupload ...

  2. python 大文件分片上传_Python实现大文件分片上传

    转载请注明出处:http://blog.csdn.net/jinixin/article/details/77545140 引言想借着这篇文章简要谈谈WebUploader大文件上传与Python结合 ...

  3. 大文件分片上传,断点续传,秒传 实现

    前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长, ...

  4. vue前端上传文件夹的插件_基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件...

    1. 前言 之前公司要在管理系统中做一个全局上传插件,即切换各个页面的时候,上传界面还在并且上传不会受到影响,这在vue这种spa框架面前并不是什么难题.然而后端大佬说我们要实现分片上传.秒传以及断点 ...

  5. formdata上传文件_封装一个多文件断点续传、分片上传、秒传、重试机制的组件...

    本文为:多文件断点续传.分片上传.秒传.重试机制 的更新版,若想看初始版本的实现,请查看该文章. 凡是要知其然知其所以然 文件上传相信很多朋友都有遇到过,那或许你也遇到过当上传大文件时,上传时间较长, ...

  6. 使用webuploader组件实现大文件分片上传,断点续传

    无组件断点续传.gif 1. 组件简介 webuploader:是一个以HTML5为主, Flash为辅的文件上传组件,采用大文件分片/并发上传的方式,极大地提高了文件上传的效率,同时兼容多种浏览器版 ...

  7. html上传文件_.NET基于WebUploader大文件分片上传、断网续传、秒传

    (给DotNet加星标,提升.Net技能) 转自:学习中的苦与乐 cnblogs.com/xiongze520/p/10412693.html 现在的项目开发基本上都用到了上传文件功能,或图片,或文档 ...

  8. 大文件分片上传前端框架_无插件实现大文件分片上传,断点续传

    文件上传.gif 1. 简介: 本篇文章基于实际项目的开发,将介绍项目中关于大文件分片上传.文件验证.断点续传.手动重试上传等需求的使用场景及实现: 2. 项目需求 在一个音视频的添加中,既要有音视频 ...

  9. 无插件实现大文件分片上传,断点续传

    代码地址如下: http://www.demodashi.com/demo/11888.html 1. 简介: 本篇文章基于实际项目的开发,将介绍项目中关于大文件分片上传.文件验证.断点续传.手动重试 ...

最新文章

  1. Leetcode | Binary Tree Maximum Path Sum
  2. 某程序员哀叹工资低:二本计算机毕业,四年前端开发,年包才四十万!薪资真的和学历挂钩吗?...
  3. react表格无缝滚动_js实现表格无缝滚动效果
  4. VC中的宏 (#define) 与预处理 (#if/#ifdef/#pragma) 的使用方法总结。
  5. 查看端口被占用,被结束此端口占用
  6. 【DWR系列04】- DWR配置详解
  7. 【渝粤教育】电大中专就业指导作业 题库
  8. [笔记]前端 - 下拉菜单的实现
  9. oracle中循环读出一个表的信息插入到另外一个表中
  10. 程序员究竟还需要读书么?
  11. session与cookie之间的关系
  12. 移动目录下的隐藏文件
  13. java嗅探网页视频_网页视频嗅探器( API钩子 )
  14. 仿网易云音乐html代码,HTML作业-仿网易云音乐项目
  15. Hive面试题系列-连续登录问题1
  16. android开发中,apk文件安装到\system\app 的解决办法 仅限root机
  17. 1.6 电源树中电流的计算方法(硬件基础系列)
  18. SICP-Notes-Lecture 21 SQL I
  19. 人工神经网络:多层感知器
  20. 加速扩张中的喜茶、奈雪们,为其它品牌提供了怎样的私域启示

热门文章

  1. cin后使用getline
  2. 逆波兰表达式求值(leetcode 150)
  3. Python入门程序 字符串应用(学号判断程序、密码破解程序、身份证的秘密)
  4. 复旦计算机英语面试问题,北大、清华、人大、复旦英语面试经典35题
  5. phpstorm软件安装
  6. 认认真真给你推荐一本书(附10本赠书)
  7. oracle数据库物化视图的作用,Oracle物化视图的用法与总结
  8. 完美解决None of the following candidates is applicable because of receiver type mismatch:
  9. 有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母牛?输入数据由多个测试实例组成,每个测试实例占一行,包括一个整数n(0n
  10. 消灭星星山寨版-我代表星星消灭你