问题:

  公司现在的业务需求是要上传一个大文件,上一次写了一篇博客,做了一个简单的文件上传,支持单文件,大型文件上传

  现在对之前的上传进行优化,支持断点续传,秒传功能

  上次博客:【http://www.cnblogs.com/hackxiyu/p/8194066.html】

分析:

  这篇文章参考了其它博主的文章,参考地址:【https://github.com/Fourwenwen/Breakpoint-http】

  环境需要:

    1.本地测试的话需要配置好Redis,用来保存文件的MD5校验值,和秒传功能的实现

    2.jquery,bootstrap,webUploader的相关js,css文件

    3.我用的是springBoot来实现的,页面是首页嵌套的,所以没有html,body标签,大家根据自己情况来定

解决:

  1.页面html文件,业务js文件杂糅到一起,大家可以拆开清晰一些

  1. <!--引入css文件-->
  2. <link rel="stylesheet" type="text/css" href="static/html/bigFileUpload/assets/bootstrap-3.3.7-dist/css/bootstrap.css">
  3. <link rel="stylesheet" type="text/css" href="static/html/bigFileUpload/assets/webuploader.css">
  4.  
  5. <div id="uploader" class="wu-example">
  6. <div id="thelist" class="uploader-list"></div>
  7. <div class="btns">
  8. <div id="picker">选择大文件</div>
  9. <button id="ctlBtn" class="btn btn-default">开始上传</button>
  10. </div>
  11. </div>
  12.  
  13. <!--引入JS,jquery的js已经引入-->
  14. <script type="text/javascript" src="static/html/bigFileUpload/assets/webuploader.js"></script>
  15. <script type="text/javascript" src="static/html/bigFileUpload/assets/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
  16.  
  17. <!--业务js文件-->
  18. <script>
  19. var $btn = $('#ctlBtn');
  20. var $thelist = $('#thelist');
  21. var chunkSize = 5 * 1024 * 1024;
  22.  
  23. // HOOK 这个必须要再uploader实例化前面
  24. WebUploader.Uploader.register({
  25. 'before-send-file': 'beforeSendFile',
  26. 'before-send': 'beforeSend'
  27. }, {
  28. beforeSendFile: function (file) {
  29. console.log("beforeSendFile");
  30. // Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。
  31. var task = new $.Deferred();
  32. // 根据文件内容来查询MD5
  33. uploader.md5File(file).progress(function (percentage) { // 及时显示进度
  34. console.log('计算md5进度:', percentage);
  35. getProgressBar(file, percentage, "MD5", "MD5");
  36. }).then(function (val) { // 完成
  37. console.log('md5 result:', val);
  38. file.md5 = val;
  39. // 模拟用户id
  40. // file.uid = new Date().getTime() + "_" + Math.random() * 100;
  41. file.uid = WebUploader.Base.guid();
  42. // 进行md5判断
  43. $.post("break/checkFileMd5", {uid: file.uid, md5: file.md5,"Authorization": localStorage.token},
  44. function (data) {
  45. console.log(data.status);
  46. var status = data.status.value;
  47. task.resolve();
  48. if (status == 101) {
  49. // 文件不存在,那就正常流程
  50. } else if (status == 100) {
  51. // 忽略上传过程,直接标识上传成功;
  52. uploader.skipFile(file);
  53. file.pass = true;
  54. } else if (status == 102) {
  55. // 部分已经上传到服务器了,但是差几个模块。
  56. file.missChunks = data.data;
  57. }
  58. });
  59. });
  60. return $.when(task);
  61. },
  62. beforeSend: function (block) {
  63. console.log("block")
  64. var task = new $.Deferred();
  65. var file = block.file;
  66. var missChunks = file.missChunks;
  67. var blockChunk = block.chunk;
  68. console.log("当前分块:" + blockChunk);
  69. console.log("missChunks:" + missChunks);
  70. if (missChunks !== null && missChunks !== undefined && missChunks !== '') {
  71. var flag = true;
  72. for (var i = 0; i < missChunks.length; i++) {
  73. if (blockChunk == missChunks[i]) {
  74. console.log(file.name + ":" + blockChunk + ":还没上传,现在上传去吧。");
  75. flag = false;
  76. break;
  77. }
  78. }
  79. if (flag) {
  80. task.reject();
  81. } else {
  82. task.resolve();
  83. }
  84. } else {
  85. task.resolve();
  86. }
  87. return $.when(task);
  88. }
  89. });
  90.  
  91. // 实例化
  92. var uploader = WebUploader.create({
  93. pick: {
  94. id: '#picker',
  95. label: '点击选择文件'
  96. },
  97. formData: {
  98. uid: 0,
  99. md5: '',
  100. chunkSize: chunkSize,
  101. "Authorization": localStorage.token
  102. },
  103. //dnd: '#dndArea',
  104. //paste: '#uploader',
  105. swf: 'static/html/bigFileUpload/assets/Uploader.swf',
  106. chunked: true,
  107. chunkSize: chunkSize, // 字节 1M分块
  108. threads: 3,
  109. server: 'break/fileUpload',
  110. auto: false,
  111.  
  112. // 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。
  113. disableGlobalDnd: true,
  114. fileNumLimit: 1024,
  115. fileSizeLimit: 1024 * 1024 * 1024, // 200 M
  116. fileSingleSizeLimit: 1024 * 1024 * 1024 // 50 M
  117. });
  118.  
  119. // 当有文件被添加进队列的时候
  120. uploader.on('fileQueued', function (file) {
  121. console.log("fileQueued");
  122. $thelist.append('<div id="' + file.id + '" class="item">' +
  123. '<h4 class="info">' + file.name + '</h4>' +
  124. '<p class="state">等待上传...</p>' +
  125. '</div>');
  126. });
  127.  
  128. //当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
  129. uploader.onUploadBeforeSend = function (obj, data) {
  130. console.log("onUploadBeforeSend");
  131. var file = obj.file;
  132. data.md5 = file.md5 || '';
  133. data.uid = file.uid;
  134. };
  135. // 上传中
  136. uploader.on('uploadProgress', function (file, percentage) {
  137. getProgressBar(file, percentage, "FILE", "上传进度");
  138. });
  139. // 上传返回结果
  140. uploader.on('uploadSuccess', function (file) {
  141. var text = '已上传';
  142. if (file.pass) {
  143. text = "文件妙传功能,文件已上传。"
  144. }
  145. $('#' + file.id).find('p.state').text(text);
  146. });
  147. uploader.on('uploadError', function (file) {
  148. $('#' + file.id).find('p.state').text('上传出错');
  149. });
  150. uploader.on('uploadComplete', function (file) {
  151. // 隐藏进度条
  152. fadeOutProgress(file, 'MD5');
  153. fadeOutProgress(file, 'FILE');
  154. });
  155. // 文件上传
  156. $btn.on('click', function () {
  157. console.log("上传...");
  158. uploader.upload();
  159. console.log("上传成功");
  160. });
  161.  
  162. /**
  163. * 生成进度条封装方法
  164. * @param file 文件
  165. * @param percentage 进度值
  166. * @param id_Prefix id前缀
  167. * @param titleName 标题名
  168. */
  169. function getProgressBar(file, percentage, id_Prefix, titleName) {
  170. var $li = $('#' + file.id), $percent = $li.find('#' + id_Prefix + '-progress-bar');
  171. // 避免重复创建
  172. if (!$percent.length) {
  173. $percent = $('<div id="' + id_Prefix + '-progress" class="progress progress-striped active">' +
  174. '<div id="' + id_Prefix + '-progress-bar" class="progress-bar" role="progressbar" style="width: 0%">' +
  175. '</div>' +
  176. '</div>'
  177. ).appendTo($li).find('#' + id_Prefix + '-progress-bar');
  178. }
  179. var progressPercentage = percentage * 100 + '%';
  180. $percent.css('width', progressPercentage);
  181. $percent.html(titleName + ':' + progressPercentage);
  182. }
  183.  
  184. /**
  185. * 隐藏进度条
  186. * @param file 文件对象
  187. * @param id_Prefix id前缀
  188. */
  189. function fadeOutProgress(file, id_Prefix) {
  190. $('#' + file.id).find('#' + id_Prefix + '-progress').fadeOut();
  191. }
  192. </script>

   2.API接口

  1. package org.triber.portal.breakPoint;
  2.  
  3. import org.apache.commons.io.FileUtils;
  4. import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.data.redis.core.StringRedisTemplate;
  9. import org.springframework.http.ResponseEntity;
  10. import org.springframework.stereotype.Controller;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.RequestMethod;
  13. import org.springframework.web.bind.annotation.ResponseBody;
  14.  
  15. import javax.servlet.http.HttpServletRequest;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.util.LinkedList;
  19. import java.util.List;
  20.  
  21. /**
  22. * 断点续传上传大文件类
  23. */
  24. @Controller
  25. @RequestMapping(value = "/break")
  26. public class BreakPointController {
  27.  
  28. private Logger logger = LoggerFactory.getLogger(BreakPointController.class);
  29.  
  30. @Autowired
  31. private StringRedisTemplate stringRedisTemplate;
  32.  
  33. @Autowired
  34. private StorageService storageService;
  35.  
  36. /**
  37. * 秒传判断,断点判断
  38. *
  39. * @return
  40. */
  41. @RequestMapping(value = "checkFileMd5", method = RequestMethod.POST)
  42. @ResponseBody
  43. public Object checkFileMd5(String md5) throws IOException {
  44. Object processingObj = stringRedisTemplate.opsForHash().get(Constants.FILE_UPLOAD_STATUS, md5);
  45. if (processingObj == null) {
  46. return new ResultVo(ResultStatus.NO_HAVE);
  47. }
  48. String processingStr = processingObj.toString();
  49. boolean processing = Boolean.parseBoolean(processingStr);
  50. String value = stringRedisTemplate.opsForValue().get(Constants.FILE_MD5_KEY + md5);
  51. if (processing) {
  52. return new ResultVo(ResultStatus.IS_HAVE, value);
  53. } else {
  54. File confFile = new File(value);
  55. byte[] completeList = FileUtils.readFileToByteArray(confFile);
  56. List<String> missChunkList = new LinkedList<>();
  57. for (int i = 0; i < completeList.length; i++) {
  58. if (completeList[i] != Byte.MAX_VALUE) {
  59. missChunkList.add(i + "");
  60. }
  61. }
  62. return new ResultVo<>(ResultStatus.ING_HAVE, missChunkList);
  63. }
  64. }
  65.  
  66. /**
  67. * 上传文件
  68. *
  69. * @param param
  70. * @param request
  71. * @return
  72. * @throws Exception
  73. */
  74. @RequestMapping(value = "/fileUpload", method = RequestMethod.POST)
  75. @ResponseBody
  76. public ResponseEntity fileUpload(MultipartFileParam param, HttpServletRequest request) {
  77. boolean isMultipart = ServletFileUpload.isMultipartContent(request);
  78. if (isMultipart) {
  79. logger.info("上传文件start。");
  80. try {
  81. // 方法1
  82. //storageService.uploadFileRandomAccessFile(param);
  83. // 方法2 这个更快点
  84. storageService.uploadFileByMappedByteBuffer(param);
  85. } catch (IOException e) {
  86. e.printStackTrace();
  87. logger.error("文件上传失败。{}", param.toString());
  88. }
  89. logger.info("上传文件end。");
  90. }
  91. return ResponseEntity.ok().body("上传成功。");
  92. }
  93. }

  3.业务service的实现

  1. package org.triber.portal.breakPoint;
  2.  
  3. import java.io.IOException;
  4.  
  5. /**
  6. * 存储操作的service
  7. * Created by 超文 on 2017/5/2.
  8. */
  9. public interface StorageService {
  10.  
  11. /**
  12. * 删除全部数据
  13. */
  14. void deleteAll();
  15.  
  16. /**
  17. * 初始化方法
  18. */
  19. void init();
  20.  
  21. /**
  22. * 上传文件方法1
  23. *
  24. * @param param
  25. * @throws IOException
  26. */
  27. void uploadFileRandomAccessFile(MultipartFileParam param) throws IOException;
  28.  
  29. /**
  30. * 上传文件方法2
  31. * 处理文件分块,基于MappedByteBuffer来实现文件的保存
  32. *
  33. * @param param
  34. * @throws IOException
  35. */
  36. void uploadFileByMappedByteBuffer(MultipartFileParam param) throws IOException;
  37.  
  38. }

  实现

  1. package org.triber.portal.breakPoint;
  2.  
  3. import org.apache.commons.io.FileUtils;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.data.redis.core.StringRedisTemplate;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.util.FileSystemUtils;
  11.  
  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.io.RandomAccessFile;
  15. import java.nio.MappedByteBuffer;
  16. import java.nio.channels.FileChannel;
  17. import java.nio.file.FileAlreadyExistsException;
  18. import java.nio.file.Files;
  19. import java.nio.file.Path;
  20. import java.nio.file.Paths;
  21.  
  22. /**
  23. * Created by 超文 on 2017/5/2.
  24. */
  25. @Service
  26. public class StorageServiceImpl implements StorageService {
  27.  
  28. private final Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class);
  29. // 保存文件的根目录
  30. private Path rootPaht;
  31.  
  32. @Autowired
  33. private StringRedisTemplate stringRedisTemplate;
  34.  
  35. //这个必须与前端设定的值一致
  36. @Value("${breakpoint.upload.chunkSize}")
  37. private long CHUNK_SIZE;
  38.  
  39. @Value("${breakpoint.upload.dir}")
  40. private String finalDirPath;
  41.  
  42. @Autowired
  43. public StorageServiceImpl(@Value("${breakpoint.upload.dir}") String location) {
  44. this.rootPaht = Paths.get(location);
  45. }
  46.  
  47. @Override
  48. public void deleteAll() {
  49. logger.info("开发初始化清理数据,start");
  50. FileSystemUtils.deleteRecursively(rootPaht.toFile());
  51. stringRedisTemplate.delete(Constants.FILE_UPLOAD_STATUS);
  52. stringRedisTemplate.delete(Constants.FILE_MD5_KEY);
  53. logger.info("开发初始化清理数据,end");
  54. }
  55.  
  56. @Override
  57. public void init() {
  58. try {
  59. Files.createDirectory(rootPaht);
  60. } catch (FileAlreadyExistsException e) {
  61. logger.error("文件夹已经存在了,不用再创建。");
  62. } catch (IOException e) {
  63. logger.error("初始化root文件夹失败。", e);
  64. }
  65. }
  66.  
  67. @Override
  68. public void uploadFileRandomAccessFile(MultipartFileParam param) throws IOException {
  69. String fileName = param.getName();
  70. String tempDirPath = finalDirPath + param.getMd5();
  71. String tempFileName = fileName + "_tmp";
  72. File tmpDir = new File(tempDirPath);
  73. File tmpFile = new File(tempDirPath, tempFileName);
  74. if (!tmpDir.exists()) {
  75. tmpDir.mkdirs();
  76. }
  77.  
  78. RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw");
  79. long offset = CHUNK_SIZE * param.getChunk();
  80. //定位到该分片的偏移量
  81. accessTmpFile.seek(offset);
  82. //写入该分片数据
  83. accessTmpFile.write(param.getFile().getBytes());
  84. // 释放
  85. accessTmpFile.close();
  86.  
  87. boolean isOk = checkAndSetUploadProgress(param, tempDirPath);
  88. if (isOk) {
  89. boolean flag = renameFile(tmpFile, fileName);
  90. System.out.println("upload complete !!" + flag + " name=" + fileName);
  91. }
  92. }
  93.  
  94. @Override
  95. public void uploadFileByMappedByteBuffer(MultipartFileParam param) throws IOException {
  96. String fileName = param.getName();
  97. String uploadDirPath = finalDirPath + param.getMd5();
  98. String tempFileName = fileName + "_tmp";
  99. File tmpDir = new File(uploadDirPath);
  100. File tmpFile = new File(uploadDirPath, tempFileName);
  101. if (!tmpDir.exists()) {
  102. tmpDir.mkdirs();
  103. }
  104.  
  105. RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
  106. FileChannel fileChannel = tempRaf.getChannel();
  107.  
  108. //写入该分片数据
  109. long offset = CHUNK_SIZE * param.getChunk();
  110. byte[] fileData = param.getFile().getBytes();
  111. MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
  112. mappedByteBuffer.put(fileData);
  113. // 释放
  114. FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
  115. fileChannel.close();
  116.  
  117. boolean isOk = checkAndSetUploadProgress(param, uploadDirPath);
  118. if (isOk) {
  119. boolean flag = renameFile(tmpFile, fileName);
  120. System.out.println("upload complete !!" + flag + " name=" + fileName);
  121. }
  122. }
  123.  
  124. /**
  125. * 检查并修改文件上传进度
  126. *
  127. * @param param
  128. * @param uploadDirPath
  129. * @return
  130. * @throws IOException
  131. */
  132. private boolean checkAndSetUploadProgress(MultipartFileParam param, String uploadDirPath) throws IOException {
  133. String fileName = param.getName();
  134. File confFile = new File(uploadDirPath, fileName + ".conf");
  135. RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
  136. //把该分段标记为 true 表示完成
  137. System.out.println("set part " + param.getChunk() + " complete");
  138. accessConfFile.setLength(param.getChunks());
  139. accessConfFile.seek(param.getChunk());
  140. accessConfFile.write(Byte.MAX_VALUE);
  141.  
  142. //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
  143. byte[] completeList = FileUtils.readFileToByteArray(confFile);
  144. byte isComplete = Byte.MAX_VALUE;
  145. for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
  146. //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
  147. isComplete = (byte) (isComplete & completeList[i]);
  148. System.out.println("check part " + i + " complete?:" + completeList[i]);
  149. }
  150.  
  151. accessConfFile.close();
  152. if (isComplete == Byte.MAX_VALUE) {
  153. stringRedisTemplate.opsForHash().put(Constants.FILE_UPLOAD_STATUS, param.getMd5(), "true");
  154. stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), uploadDirPath + "/" + fileName);
  155. return true;
  156. } else {
  157. if (!stringRedisTemplate.opsForHash().hasKey(Constants.FILE_UPLOAD_STATUS, param.getMd5())) {
  158. stringRedisTemplate.opsForHash().put(Constants.FILE_UPLOAD_STATUS, param.getMd5(), "false");
  159. }
  160. if (stringRedisTemplate.hasKey(Constants.FILE_MD5_KEY + param.getMd5())) {
  161. stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), uploadDirPath + "/" + fileName + ".conf");
  162. }
  163. return false;
  164. }
  165.  
  166. }
  167.  
  168. /**
  169. * 文件重命名
  170. *
  171. * @param toBeRenamed 将要修改名字的文件
  172. * @param toFileNewName 新的名字
  173. * @return
  174. */
  175. public boolean renameFile(File toBeRenamed, String toFileNewName) {
  176. //检查要重命名的文件是否存在,是否是文件
  177. if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
  178. logger.info("File does not exist: " + toBeRenamed.getName());
  179. return false;
  180. }
  181. String p = toBeRenamed.getParent();
  182. File newFile = new File(p + File.separatorChar + toFileNewName);
  183. //修改文件名
  184. return toBeRenamed.renameTo(newFile);
  185. }
  186.  
  187. }

  4.依赖的MD5工具类

  1. package org.triber.portal.breakPoint;
  2.  
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5.  
  6. import java.io.File;
  7. import java.io.FileInputStream;
  8. import java.io.FileNotFoundException;
  9. import java.io.IOException;
  10. import java.lang.reflect.Method;
  11. import java.math.BigInteger;
  12. import java.nio.MappedByteBuffer;
  13. import java.nio.channels.FileChannel;
  14. import java.security.AccessController;
  15. import java.security.MessageDigest;
  16. import java.security.PrivilegedAction;
  17.  
  18. /**
  19. * 文件md5值
  20. * Created by 超文 on 2016/10/10.
  21. * version 1.0
  22. */
  23. public class FileMD5Util {
  24.  
  25. private final static Logger logger = LoggerFactory.getLogger(FileMD5Util.class);
  26.  
  27. public static String getFileMD5(File file) throws FileNotFoundException {
  28. String value = null;
  29. FileInputStream in = new FileInputStream(file);
  30. MappedByteBuffer byteBuffer = null;
  31. try {
  32. byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
  33. MessageDigest md5 = MessageDigest.getInstance("MD5");
  34. md5.update(byteBuffer);
  35. BigInteger bi = new BigInteger(1, md5.digest());
  36. value = bi.toString(16);
  37. if (value.length() < 32) {
  38. value = "0" + value;
  39. }
  40. } catch (Exception e) {
  41. e.printStackTrace();
  42. } finally {
  43. if (null != in) {
  44. try {
  45. in.getChannel().close();
  46. in.close();
  47. } catch (IOException e) {
  48. logger.error("get file md5 error!!!", e);
  49. }
  50. }
  51. if (null != byteBuffer) {
  52. freedMappedByteBuffer(byteBuffer);
  53. }
  54. }
  55. return value;
  56. }
  57.  
  58. /**
  59. * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash,在并发情况下很容易发生
  60. * 正在释放时另一个线程正开始读取,于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
  61. *
  62. * @param mappedByteBuffer
  63. */
  64. public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
  65. try {
  66. if (mappedByteBuffer == null) {
  67. return;
  68. }
  69.  
  70. mappedByteBuffer.force();
  71. AccessController.doPrivileged(new PrivilegedAction<Object>() {
  72. @Override
  73. public Object run() {
  74. try {
  75. Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
  76. getCleanerMethod.setAccessible(true);
  77. sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
  78. new Object[0]);
  79. cleaner.clean();
  80. } catch (Exception e) {
  81. logger.error("clean MappedByteBuffer error!!!", e);
  82. }
  83. logger.info("clean MappedByteBuffer completed!!!");
  84. return null;
  85. }
  86. });
  87.  
  88. } catch (Exception e) {
  89. e.printStackTrace();
  90. }
  91. }
  92. }

  5.分片实体

  1. package org.triber.portal.breakPoint;
  2.  
  3. import org.springframework.web.multipart.MultipartFile;
  4.  
  5. /**
  6. * Created by wenwen on 2017/4/16.
  7. * version 1.0
  8. */
  9. public class MultipartFileParam {
  10.  
  11. // 用户id
  12. private String uid;
  13. //任务ID
  14. private String id;
  15. //总分片数量
  16. private int chunks;
  17. //当前为第几块分片
  18. private int chunk;
  19. //当前分片大小
  20. private long size = 0L;
  21. //文件名
  22. private String name;
  23. //分片对象
  24. private MultipartFile file;
  25. // MD5
  26. private String md5;
  27.  
  28. public String getUid() {
  29. return uid;
  30. }
  31.  
  32. public void setUid(String uid) {
  33. this.uid = uid;
  34. }
  35.  
  36. public String getId() {
  37. return id;
  38. }
  39.  
  40. public void setId(String id) {
  41. this.id = id;
  42. }
  43.  
  44. public int getChunks() {
  45. return chunks;
  46. }
  47.  
  48. public void setChunks(int chunks) {
  49. this.chunks = chunks;
  50. }
  51.  
  52. public int getChunk() {
  53. return chunk;
  54. }
  55.  
  56. public void setChunk(int chunk) {
  57. this.chunk = chunk;
  58. }
  59.  
  60. public long getSize() {
  61. return size;
  62. }
  63.  
  64. public void setSize(long size) {
  65. this.size = size;
  66. }
  67.  
  68. public String getName() {
  69. return name;
  70. }
  71.  
  72. public void setName(String name) {
  73. this.name = name;
  74. }
  75.  
  76. public MultipartFile getFile() {
  77. return file;
  78. }
  79.  
  80. public void setFile(MultipartFile file) {
  81. this.file = file;
  82. }
  83.  
  84. public String getMd5() {
  85. return md5;
  86. }
  87.  
  88. public void setMd5(String md5) {
  89. this.md5 = md5;
  90. }
  91.  
  92. @Override
  93. public String toString() {
  94. return "MultipartFileParam{" +
  95. "uid='" + uid + '\'' +
  96. ", id='" + id + '\'' +
  97. ", chunks=" + chunks +
  98. ", chunk=" + chunk +
  99. ", size=" + size +
  100. ", name='" + name + '\'' +
  101. ", file=" + file +
  102. ", md5='" + md5 + '\'' +
  103. '}';
  104. }
  105. }

  6.响应常量类

  1. package org.triber.portal.breakPoint;
  2.  
  3. import com.fasterxml.jackson.annotation.JsonFormat;
  4.  
  5. /**
  6. * 结果类型枚举
  7. * Created by 超文 on 2017/5/2.
  8. * version 1.0
  9. */
  10. @JsonFormat(shape = JsonFormat.Shape.OBJECT)
  11. public enum ResultStatus {
  12. /**
  13. * 1 开头为判断文件在系统的状态
  14. */
  15. IS_HAVE(100, "文件已存在!"),
  16.  
  17. NO_HAVE(101, "该文件没有上传过。"),
  18.  
  19. ING_HAVE(102, "该文件上传了一部分。");
  20.  
  21. private final int value;
  22.  
  23. private final String reasonPhrase;
  24.  
  25. ResultStatus(int value, String reasonPhrase) {
  26. this.value = value;
  27. this.reasonPhrase = reasonPhrase;
  28. }
  29.  
  30. public int getValue() {
  31. return value;
  32. }
  33.  
  34. public String getReasonPhrase() {
  35. return reasonPhrase;
  36. }
  37. }

  7.响应实体

  1. package org.triber.portal.breakPoint;
  2.  
  3. /**
  4. * 统一返回结果pojo
  5. * Created by wenwen on 2017/4/23.
  6. * version 1.0
  7. */
  8. public class ResultVo<T> {
  9.  
  10. private ResultStatus status;
  11.  
  12. private String msg;
  13.  
  14. private T data;
  15.  
  16. public ResultVo(ResultStatus status) {
  17. this(status, status.getReasonPhrase(), null);
  18. }
  19.  
  20. public ResultVo(ResultStatus status, T data) {
  21. this(status, status.getReasonPhrase(), data);
  22. }
  23.  
  24. public ResultVo(ResultStatus status, String msg, T data) {
  25. this.status = status;
  26. this.msg = msg;
  27. this.data = data;
  28. }
  29.  
  30. public ResultStatus getStatus() {
  31. return status;
  32. }
  33.  
  34. public void setStatus(ResultStatus status) {
  35. this.status = status;
  36. }
  37.  
  38. public String getMsg() {
  39. return msg;
  40. }
  41.  
  42. public void setMsg(String msg) {
  43. this.msg = msg;
  44. }
  45.  
  46. public T getData() {
  47. return data;
  48. }
  49.  
  50. public void setData(T data) {
  51. this.data = data;
  52. }
  53.  
  54. @Override
  55. public String toString() {
  56. return "ResultVo{" +
  57. "status=" + status +
  58. ", msg='" + msg + '\'' +
  59. ", data=" + data +
  60. '}';
  61. }
  62. }

  8.常量类

  1. package org.triber.portal.breakPoint;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. /**
  7. * 常量表
  8. * Created by 超文 on 2017/05/02.
  9. * version 1.0
  10. */
  11. public interface Constants {
  12. /**
  13. * 异常信息统一头信息<br>
  14. * 非常遗憾的通知您,程序发生了异常
  15. */
  16. public static final String Exception_Head = "boom。炸了。";
  17. /**
  18. * 缓存键值
  19. */
  20. public static final Map<Class<?>, String> cacheKeyMap = new HashMap<>();
  21. /**
  22. * 保存文件所在路径的key,eg.FILE_MD5:1243jkalsjflkwaejklgjawe
  23. */
  24. public static final String FILE_MD5_KEY = "FILE_MD5:";
  25. /**
  26. * 保存上传文件的状态
  27. */
  28. public static final String FILE_UPLOAD_STATUS = "FILE_UPLOAD_STATUS";
  29.  
  30. }

  9.本机Redis配置

  1. #开发环境
  2. breakpoint:
  3. upload:
  4. dir: E:/data0/uploads/
  5. #1024*1024=1 048 576,5M=5 242 880
  6. chunkSize: 5 242 880
  7.  
  8. spring:
  9. redis:
  10. host: 127.0.0.1
  11. port: 6379
  12. # password: test //密码我本机没有所以不配
  13. pool:
  14. max-active: 30
  15. max-idle: 10
  16. max-wait: 10000
  17. timeout: 0
  18. http:
  19. multipart:
  20. max-file-size: 10MB //可以自定义这些值
  21. max-request-size: 100MB

总结:

  其实重要的也就是,页面js文件,和后台接口服务,MD5工具类

webUploader实现大文件分片,断点续传的更多相关文章

  1. ASP.NET CORE使用WebUploader对大文件分片上传,并通过ASP.NET CORE SignalR实时反馈后台处理进度给前端展示

    本次,我们来实现一个单个大文件上传,并且把后台对上传文件的处理进度通过ASP.NET CORE SignalR反馈给前端展示,比如上传一个大的zip压缩包文件,后台进行解压缩,并且对压缩包中的文件进行 ...

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

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

  3. thinkphp+webuploader实现大文件分片上传

    大文件分片上传,简单来说就是把大文件切分为小文件,然后再一个一个的上传,到最后由这些小文件再合并成原来的文件 webuploader下载地址及其文档:http://fex.baidu.com/webu ...

  4. 在React中使用WebUploader实现大文件分片上传的踩坑日记!

    前段时间公司项目有个大文件分片上传的需求,项目是用React写的,大文件分片上传这个功能使用了WebUploader这个组件. 具体交互是: 1. 点击上传文件button后出现弹窗,弹窗内有选择文件 ...

  5. 30分钟玩转Net MVC 基于WebUploader的大文件分片上传、断网续传、秒传(文末附带demo下载)

    现在的项目开发基本上都用到了上传文件功能,或图片,或文档,或视频.我们常用的常规上传已经能够满足当前要求了, 然而有时会出现如下问题: 文件过大(比如1G以上),超出服务端的请求大小限制: 请求时间过 ...

  6. 使用webuploader实现大文件分片上传

    文件夹数据库处理逻辑 public class DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject() ...

  7. Webuploader 大文件分片上传

    百度Webuploader 大文件分片上传(.net接收)   前阵子要做个大文件上传的功能,找来找去发现Webuploader还不错,关于她的介绍我就不再赘述. 动手前,在园子里找到了一篇不错的分片 ...

  8. php使用WebUploader做大文件的分块和断点续传

    核心原理: 该项目核心就是文件分块上传.前后端要高度配合,需要双方约定好一些数据,才能完成大文件分块,我们在项目中要重点解决的以下问题. * 如何分片: * 如何合成一个文件: * 中断了从哪个分片开 ...

  9. php+html5实现无刷新上传,大文件分片上传,断点续传

    核心原理: 该项目核心就是文件分块上传.前后端要高度配合,需要双方约定好一些数据,才能完成大文件分块,我们在项目中要重点解决的以下问题. * 如何分片: * 如何合成一个文件: * 中断了从哪个分片开 ...

随机推荐

  1. ubuntu 13.10 无法播放 mp3

    添加源: #deb cdrom:[Ubuntu 13.10 _Saucy Salamander_ - Release i386 (20131016.1)]/ saucy main restricted ...

  2. C++Primer学习笔记《三》

    数组名事实上就是一个常指针,指向数组元素中第一个的地址,在程序中假设要用指针遍历数组,不能直接用数组名来自增或自减.由于它是常量,一般先把数组名保存一份同类型的指针,然后再用这个指针来自增或是自减来实 ...

  3. BZOJ3174:[TJOI2013]拯救小矮人(DP)

    Description 一群小矮人掉进了一个很深的陷阱里,由于太矮爬不上来,于是他们决定搭一个人梯.即:一个小矮人站在另一小矮人的 肩膀上,知道最顶端的小矮人伸直胳膊可以碰到陷阱口.对于每一个小矮人, ...

  4. 3171. [TJOI2013]循环格【费用流】

    Description 一个循环格就是一个矩阵,其中所有元素为箭头,指向相邻四个格子.每个元素有一个坐标(行,列),其中左上角元素坐标为(0,0).给定一个起始位置(r,c) ,你可以沿着箭头防线在格 ...

  5. 如何利用java程序实现加密所需的公钥、密钥、数字证书

    本篇的主要目的在于实现pdf的数字签名问题,只是作为我学习知识的总结. 1.数字签名算法的概述 本部分主要参考于:https://blog.csdn.net/lovelichao12/article/ ...

  6. sqoop数据迁移

    3.1 概述 sqoop是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具. 导入数据:MySQL,Oracle导入数据到Hadoop的HDFS.HIVE.HBASE等数据存储 ...

  7. centos安装GD库失败

    Error: Package: php-gd-5.6.11-1.el6.remi.x86_64 (remi-php56) Requires: gd-last(x86-64) >= 2.1.1 E ...

  8. JAVA语言编程思维入门

    Java语言是一门强数据类型语言,也就是所有的数据有自己的数据类型,不能搞混淆.比如整数int 字符串String 不能用int a="字符串123";这样写是错的,因为数据类型不 ...

  9. 记一次jvm异常排查及优化

    为方便自己查看,根据工作遇到的问题,转载并整理以下jvm优化内容 有次接到客服反馈,生产系统异常,无法访问.接到通知紧急上后台跟踪,查看了数据库死锁情况--正常,接着查看tomcat 内存溢出--正常 ...

  10. web前端时间戳转时间类型显示

    1.jsp头部加:<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> ...