当前位置:   article > 正文

springboot整合vue2-uploader文件分片上传、秒传、断点续传_vue-simple-uploader 怎么判断进行分片合并

vue-simple-uploader 怎么判断进行分片合并

1:vue-simple-uploader介绍

vue-simple-uploader是基于 simple-uploader.js 封装的vue上传插件。它的优点包括且不限于以下几种:

  • 支持文件、多文件、文件夹上传;支持拖拽文件、文件夹上传
  • 可暂停、继续上传
  • 错误处理
  • 支持“秒传”,通过文件判断服务端是否已存在从而实现“秒传”
  • 分片上传
  • 支持进度、预估剩余时间、出错自动重试、重传等操作

2:图片便于理解:

秒传:(将文件使用MD5加密,生成一个串,我们拿到这个串到redis 中查看是否存在)

3:服务端Java代码

3.1  UploaderController

  1. package com.xialj.demoend.controller;
  2. import com.xialj.demoend.common.RestApiResponse;
  3. import com.xialj.demoend.common.Result;
  4. import com.xialj.demoend.dto.FileChunkDTO;
  5. import com.xialj.demoend.dto.FileChunkResultDTO;
  6. import com.xialj.demoend.service.IUploadService;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.*;
  9. import javax.annotation.Resource;
  10. /**
  11. * @ProjectName UploaderController
  12. * @author Administrator
  13. * @version 1.0.0
  14. * @Description 附件分片上传
  15. * @createTime 2022/4/13 0013 15:58
  16. */
  17. @RestController
  18. @RequestMapping("upload")
  19. public class UploaderController {
  20. @Resource
  21. private IUploadService uploadService;
  22. /**
  23. * 检查分片是否存在
  24. *
  25. * @return
  26. */
  27. @GetMapping("chunk")
  28. public Result checkChunkExist(FileChunkDTO chunkDTO) {
  29. FileChunkResultDTO fileChunkCheckDTO;
  30. try {
  31. fileChunkCheckDTO = uploadService.checkChunkExist(chunkDTO);
  32. return Result.ok(fileChunkCheckDTO);
  33. } catch (Exception e) {
  34. return Result.fail(e.getMessage());
  35. }
  36. }
  37. /**
  38. * 上传文件分片
  39. *
  40. * @param chunkDTO
  41. * @return
  42. */
  43. @PostMapping("chunk")
  44. public Result uploadChunk(FileChunkDTO chunkDTO) {
  45. try {
  46. uploadService.uploadChunk(chunkDTO);
  47. return Result.ok(chunkDTO.getIdentifier());
  48. } catch (Exception e) {
  49. return Result.fail(e.getMessage());
  50. }
  51. }
  52. /**
  53. * 请求合并文件分片
  54. *
  55. * @param chunkDTO
  56. * @return
  57. */
  58. @PostMapping("merge")
  59. public Result mergeChunks(@RequestBody FileChunkDTO chunkDTO) {
  60. try {
  61. boolean success = uploadService.mergeChunk(chunkDTO.getIdentifier(), chunkDTO.getFilename(), chunkDTO.getTotalChunks());
  62. return Result.ok(success);
  63. } catch (Exception e) {
  64. return Result.fail(e.getMessage());
  65. }
  66. }
  67. }

3.2  IUploadService  接口

  1. package com.xialj.demoend.service;
  2. import com.xialj.demoend.dto.FileChunkDTO;
  3. import com.xialj.demoend.dto.FileChunkResultDTO;
  4. import java.io.IOException;
  5. /**
  6. * @ProjectName IUploadService
  7. * @author Administrator
  8. * @version 1.0.0
  9. * @Description 附件分片上传
  10. * @createTime 2022/4/13 0013 15:59
  11. */
  12. public interface IUploadService {
  13. /**
  14. * 检查文件是否存在,如果存在则跳过该文件的上传,如果不存在,返回需要上传的分片集合
  15. * @param chunkDTO
  16. * @return
  17. */
  18. FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO);
  19. /**
  20. * 上传文件分片
  21. * @param chunkDTO
  22. */
  23. void uploadChunk(FileChunkDTO chunkDTO) throws IOException;
  24. /**
  25. * 合并文件分片
  26. * @param identifier
  27. * @param fileName
  28. * @param totalChunks
  29. * @return
  30. * @throws IOException
  31. */
  32. boolean mergeChunk(String identifier,String fileName,Integer totalChunks)throws IOException;
  33. }

3.3 UploadServiceImpl

  1. package com.xialj.demoend.service.impl;
  2. import com.xialj.demoend.dto.FileChunkDTO;
  3. import com.xialj.demoend.dto.FileChunkResultDTO;
  4. import com.xialj.demoend.service.IUploadService;
  5. import org.apache.tomcat.util.http.fileupload.IOUtils;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.beans.factory.annotation.Value;
  10. import org.springframework.data.redis.core.RedisTemplate;
  11. import org.springframework.stereotype.Service;
  12. import java.io.*;
  13. import java.util.*;
  14. /**
  15. * @ProjectName UploadServiceImpl
  16. * @author Administrator
  17. * @version 1.0.0
  18. * @Description 附件分片上传
  19. * @createTime 2022/4/13 0013 15:59
  20. */
  21. @Service
  22. @SuppressWarnings("all")
  23. public class UploadServiceImpl implements IUploadService {
  24. private Logger logger = LoggerFactory.getLogger(UploadServiceImpl.class);
  25. @Autowired
  26. private RedisTemplate<String, Object> redisTemplate;
  27. @Value("${uploadFolder}")
  28. private String uploadFolder;
  29. /**
  30. * 检查文件是否存在,如果存在则跳过该文件的上传,如果不存在,返回需要上传的分片集合
  31. * 检查分片是否存在
  32. ○ 检查目录下的文件是否存在。
  33. ○ 检查redis存储的分片是否存在。
  34. ○ 判断分片数量和总分片数量是否一致。
  35. 如果文件存在并且分片上传完毕,标识已经完成附件的上传,可以进行秒传操作。
  36. 如果文件不存在或者分片为上传完毕,则返回false并返回已经上传的分片信息。
  37. * @param chunkDTO
  38. * @return
  39. */
  40. @Override
  41. public FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO) {
  42. //1.检查文件是否已上传过
  43. //1.1)检查在磁盘中是否存在
  44. String fileFolderPath = getFileFolderPath(chunkDTO.getIdentifier());
  45. logger.info("fileFolderPath-->{}", fileFolderPath);
  46. String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename());
  47. File file = new File(filePath);
  48. boolean exists = file.exists();
  49. //1.2)检查Redis中是否存在,并且所有分片已经上传完成。
  50. Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
  51. if (uploaded != null && uploaded.size() == chunkDTO.getTotalChunks() && exists) {
  52. return new FileChunkResultDTO(true);
  53. }
  54. File fileFolder = new File(fileFolderPath);
  55. if (!fileFolder.exists()) {
  56. boolean mkdirs = fileFolder.mkdirs();
  57. logger.info("准备工作,创建文件夹,fileFolderPath:{},mkdirs:{}", fileFolderPath, mkdirs);
  58. }
  59. // 断点续传,返回已上传的分片
  60. return new FileChunkResultDTO(false, uploaded);
  61. }
  62. /**
  63. * 上传分片
  64. * 上传附件分片
  65. ○ 判断目录是否存在,如果不存在则创建目录。
  66. ○ 进行切片的拷贝,将切片拷贝到指定的目录。
  67. ○ 将该分片写入redis
  68. * @param chunkDTO
  69. */
  70. @Override
  71. public void uploadChunk(FileChunkDTO chunkDTO) {
  72. //分块的目录
  73. String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier());
  74. logger.info("分块的目录 -> {}", chunkFileFolderPath);
  75. File chunkFileFolder = new File(chunkFileFolderPath);
  76. if (!chunkFileFolder.exists()) {
  77. boolean mkdirs = chunkFileFolder.mkdirs();
  78. logger.info("创建分片文件夹:{}", mkdirs);
  79. }
  80. //写入分片
  81. try (
  82. InputStream inputStream = chunkDTO.getFile().getInputStream();
  83. FileOutputStream outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunkDTO.getChunkNumber()))
  84. ) {
  85. IOUtils.copy(inputStream, outputStream);
  86. logger.info("文件标识:{},chunkNumber:{}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber());
  87. //将该分片写入redis
  88. long size = saveToRedis(chunkDTO);
  89. } catch (Exception e) {
  90. e.printStackTrace();
  91. }
  92. }
  93. @Override
  94. public boolean mergeChunk(String identifier, String fileName, Integer totalChunks) throws IOException {
  95. return mergeChunks(identifier, fileName, totalChunks);
  96. }
  97. /**
  98. * 合并分片
  99. *
  100. * @param identifier
  101. * @param filename
  102. */
  103. private boolean mergeChunks(String identifier, String filename, Integer totalChunks) {
  104. String chunkFileFolderPath = getChunkFileFolderPath(identifier);
  105. String filePath = getFilePath(identifier, filename);
  106. // 检查分片是否都存在
  107. if (checkChunks(chunkFileFolderPath, totalChunks)) {
  108. File chunkFileFolder = new File(chunkFileFolderPath);
  109. File mergeFile = new File(filePath);
  110. File[] chunks = chunkFileFolder.listFiles();
  111. // 切片排序1、2/3、---
  112. List fileList = Arrays.asList(chunks);
  113. Collections.sort(fileList, (Comparator<File>) (o1, o2) -> {
  114. return Integer.parseInt(o1.getName()) - (Integer.parseInt(o2.getName()));
  115. });
  116. try {
  117. RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
  118. byte[] bytes = new byte[1024];
  119. for (File chunk : chunks) {
  120. RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r");
  121. int len;
  122. while ((len = randomAccessFileReader.read(bytes)) != -1) {
  123. randomAccessFileWriter.write(bytes, 0, len);
  124. }
  125. randomAccessFileReader.close();
  126. }
  127. randomAccessFileWriter.close();
  128. } catch (Exception e) {
  129. return false;
  130. }
  131. return true;
  132. }
  133. return false;
  134. }
  135. /**
  136. * 检查分片是否都存在
  137. * @param chunkFileFolderPath
  138. * @param totalChunks
  139. * @return
  140. */
  141. private boolean checkChunks(String chunkFileFolderPath, Integer totalChunks) {
  142. try {
  143. for (int i = 1; i <= totalChunks + 1; i++) {
  144. File file = new File(chunkFileFolderPath + File.separator + i);
  145. if (file.exists()) {
  146. continue;
  147. } else {
  148. return false;
  149. }
  150. }
  151. } catch (Exception e) {
  152. return false;
  153. }
  154. return true;
  155. }
  156. /**
  157. * 分片写入Redis
  158. * 判断切片是否已存在,如果未存在,则创建基础信息,并保存。
  159. * @param chunkDTO
  160. */
  161. private synchronized long saveToRedis(FileChunkDTO chunkDTO) {
  162. Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
  163. if (uploaded == null) {
  164. uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber()));
  165. HashMap<String, Object> objectObjectHashMap = new HashMap<>();
  166. objectObjectHashMap.put("uploaded", uploaded);
  167. objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
  168. objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
  169. // objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFilename()));
  170. objectObjectHashMap.put("path", chunkDTO.getFilename());
  171. redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap);
  172. } else {
  173. uploaded.add(chunkDTO.getChunkNumber());
  174. redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded);
  175. }
  176. return uploaded.size();
  177. }
  178. /**
  179. * 得到文件的绝对路径
  180. *
  181. * @param identifier
  182. * @param filename
  183. * @return
  184. */
  185. private String getFilePath(String identifier, String filename) {
  186. String ext = filename.substring(filename.lastIndexOf("."));
  187. // return getFileFolderPath(identifier) + identifier + ext;
  188. return uploadFolder + filename;
  189. }
  190. /**
  191. * 得到文件的相对路径
  192. *
  193. * @param identifier
  194. * @param filename
  195. * @return
  196. */
  197. private String getFileRelativelyPath(String identifier, String filename) {
  198. String ext = filename.substring(filename.lastIndexOf("."));
  199. return "/" + identifier.substring(0, 1) + "/" +
  200. identifier.substring(1, 2) + "/" +
  201. identifier + "/" + identifier
  202. + ext;
  203. }
  204. /**
  205. * 得到分块文件所属的目录
  206. *
  207. * @param identifier
  208. * @return
  209. */
  210. private String getChunkFileFolderPath(String identifier) {
  211. return getFileFolderPath(identifier) + "chunks" + File.separator;
  212. }
  213. /**
  214. * 得到文件所属的目录
  215. *
  216. * @param identifier
  217. * @return
  218. */
  219. private String getFileFolderPath(String identifier) {
  220. return uploadFolder + identifier.substring(0, 1) + File.separator +
  221. identifier.substring(1, 2) + File.separator +
  222. identifier + File.separator;
  223. // return uploadFolder;
  224. }
  225. }

3.4  FileChunkDTO

  1. package com.xialj.demoend.dto;
  2. import org.springframework.web.multipart.MultipartFile;
  3. /**
  4. * @ProjectName FileChunkDTO
  5. * @author Administrator
  6. * @version 1.0.0
  7. * @Description 附件分片上传
  8. * @createTime 2022/4/13 0013 15:59
  9. */
  10. public class FileChunkDTO {
  11. /**
  12. * 文件 md5
  13. */
  14. private String identifier;
  15. /**
  16. * 分块文件
  17. */
  18. MultipartFile file;
  19. /**
  20. * 当前分块序号
  21. */
  22. private Integer chunkNumber;
  23. /**
  24. * 分块大小
  25. */
  26. private Long chunkSize;
  27. /**
  28. * 当前分块大小
  29. */
  30. private Long currentChunkSize;
  31. /**
  32. * 文件总大小
  33. */
  34. private Long totalSize;
  35. /**
  36. * 分块总数
  37. */
  38. private Integer totalChunks;
  39. /**
  40. * 文件名
  41. */
  42. private String filename;
  43. public String getIdentifier() {
  44. return identifier;
  45. }
  46. public void setIdentifier(String identifier) {
  47. this.identifier = identifier;
  48. }
  49. public MultipartFile getFile() {
  50. return file;
  51. }
  52. public void setFile(MultipartFile file) {
  53. this.file = file;
  54. }
  55. public Integer getChunkNumber() {
  56. return chunkNumber;
  57. }
  58. public void setChunkNumber(Integer chunkNumber) {
  59. this.chunkNumber = chunkNumber;
  60. }
  61. public Long getChunkSize() {
  62. return chunkSize;
  63. }
  64. public void setChunkSize(Long chunkSize) {
  65. this.chunkSize = chunkSize;
  66. }
  67. public Long getCurrentChunkSize() {
  68. return currentChunkSize;
  69. }
  70. public void setCurrentChunkSize(Long currentChunkSize) {
  71. this.currentChunkSize = currentChunkSize;
  72. }
  73. public Long getTotalSize() {
  74. return totalSize;
  75. }
  76. public void setTotalSize(Long totalSize) {
  77. this.totalSize = totalSize;
  78. }
  79. public Integer getTotalChunks() {
  80. return totalChunks;
  81. }
  82. public void setTotalChunks(Integer totalChunks) {
  83. this.totalChunks = totalChunks;
  84. }
  85. public String getFilename() {
  86. return filename;
  87. }
  88. public void setFilename(String filename) {
  89. this.filename = filename;
  90. }
  91. @Override
  92. public String toString() {
  93. return "FileChunkDTO{" +
  94. "identifier='" + identifier + '\'' +
  95. ", file=" + file +
  96. ", chunkNumber=" + chunkNumber +
  97. ", chunkSize=" + chunkSize +
  98. ", currentChunkSize=" + currentChunkSize +
  99. ", totalSize=" + totalSize +
  100. ", totalChunks=" + totalChunks +
  101. ", filename='" + filename + '\'' +
  102. '}';
  103. }
  104. }

3.5 FileChunkResultDTO

  1. package com.xialj.demoend.dto;
  2. import java.util.Set;
  3. /**
  4. * @ProjectName FileChunkResultDTO
  5. * @author Administrator
  6. * @version 1.0.0
  7. * @Description 附件分片上传
  8. * @createTime 2022/4/13 0013 15:59
  9. */
  10. public class FileChunkResultDTO {
  11. /**
  12. * 是否跳过上传
  13. */
  14. private Boolean skipUpload;
  15. /**
  16. * 已上传分片的集合
  17. */
  18. private Set<Integer> uploaded;
  19. public Boolean getSkipUpload() {
  20. return skipUpload;
  21. }
  22. public void setSkipUpload(Boolean skipUpload) {
  23. this.skipUpload = skipUpload;
  24. }
  25. public Set<Integer> getUploaded() {
  26. return uploaded;
  27. }
  28. public void setUploaded(Set<Integer> uploaded) {
  29. this.uploaded = uploaded;
  30. }
  31. public FileChunkResultDTO(Boolean skipUpload, Set<Integer> uploaded) {
  32. this.skipUpload = skipUpload;
  33. this.uploaded = uploaded;
  34. }
  35. public FileChunkResultDTO(Boolean skipUpload) {
  36. this.skipUpload = skipUpload;
  37. }
  38. }

3.6 Result

  1. package com.xialj.demoend.common;
  2. import io.swagger.annotations.ApiModel;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. /**
  6. * @Author
  7. * @Date Created in 2023/2/23 17:25
  8. * @DESCRIPTION: 全局统一返回结果
  9. * @Version V1.0
  10. */
  11. @Data
  12. @ApiModel(value = "全局统一返回结果")
  13. @SuppressWarnings("all")
  14. public class Result<T> {
  15. @ApiModelProperty(value = "返回码")
  16. private Integer code;
  17. @ApiModelProperty(value = "返回消息")
  18. private String message;
  19. @ApiModelProperty(value = "返回数据")
  20. private T data;
  21. private Long total;
  22. public Result(){}
  23. protected static <T> Result<T> build(T data) {
  24. Result<T> result = new Result<T>();
  25. if (data != null)
  26. result.setData(data);
  27. return result;
  28. }
  29. public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
  30. Result<T> result = build(body);
  31. result.setCode(resultCodeEnum.getCode());
  32. result.setMessage(resultCodeEnum.getMessage());
  33. return result;
  34. }
  35. public static <T> Result<T> build(Integer code, String message) {
  36. Result<T> result = build(null);
  37. result.setCode(code);
  38. result.setMessage(message);
  39. return result;
  40. }
  41. public static<T> Result<T> ok(){
  42. return Result.ok(null);
  43. }
  44. /**
  45. * 操作成功
  46. * @param data
  47. * @param <T>
  48. * @return
  49. */
  50. public static<T> Result<T> ok(T data){
  51. Result<T> result = build(data);
  52. return build(data, ResultCodeEnum.SUCCESS);
  53. }
  54. public static<T> Result<T> fail(){
  55. return Result.fail(null);
  56. }
  57. /**
  58. * 操作失败
  59. * @param data
  60. * @param <T>
  61. * @return
  62. */
  63. public static<T> Result<T> fail(T data){
  64. Result<T> result = build(data);
  65. return build(data, ResultCodeEnum.FAIL);
  66. }
  67. public Result<T> message(String msg){
  68. this.setMessage(msg);
  69. return this;
  70. }
  71. public Result<T> code(Integer code){
  72. this.setCode(code);
  73. return this;
  74. }
  75. public boolean isOk() {
  76. if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
  77. return true;
  78. }
  79. return false;
  80. }
  81. }

3.6 ResultCodeEnum

  1. package com.xialj.demoend.common;
  2. import lombok.Getter;
  3. /**
  4. * @Author
  5. * @Date Created in 2023/2/23 17:25
  6. * @DESCRIPTION: 统一返回结果状态信息类
  7. * @Version V1.0
  8. */
  9. @Getter
  10. @SuppressWarnings("all")
  11. public enum ResultCodeEnum {
  12. SUCCESS(200,"成功"),
  13. FAIL(201, "失败"),
  14. PARAM_ERROR( 202, "参数不正确"),
  15. SERVICE_ERROR(203, "服务异常"),
  16. DATA_ERROR(204, "数据异常"),
  17. DATA_UPDATE_ERROR(205, "数据版本异常"),
  18. LOGIN_AUTH(208, "未登陆"),
  19. PERMISSION(209, "没有权限"),
  20. CODE_ERROR(210, "验证码错误"),
  21. // LOGIN_MOBLE_ERROR(211, "账号不正确"),
  22. LOGIN_DISABLED_ERROR(212, "改用户已被禁用"),
  23. REGISTER_MOBLE_ERROR(213, "手机号码格式不正确"),
  24. REGISTER_MOBLE_ERROR_NULL(214, "手机号码为空"),
  25. LOGIN_AURH(214, "需要登录"),
  26. LOGIN_ACL(215, "没有权限"),
  27. URL_ENCODE_ERROR( 216, "URL编码失败"),
  28. ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),
  29. FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),
  30. FETCH_USERINFO_ERROR( 219, "获取用户信息失败"),
  31. //LOGIN_ERROR( 23005, "登录失败"),
  32. PAY_RUN(220, "支付中"),
  33. CANCEL_ORDER_FAIL(225, "取消订单失败"),
  34. CANCEL_ORDER_NO(225, "不能取消预约"),
  35. HOSCODE_EXIST(230, "医院编号已经存在"),
  36. NUMBER_NO(240, "可预约号不足"),
  37. TIME_NO(250, "当前时间不可以预约"),
  38. SIGN_ERROR(300, "签名错误"),
  39. HOSPITAL_OPEN(310, "医院未开通,暂时不能访问"),
  40. HOSPITAL_LOCK(320, "医院被锁定,暂时不能访问"),
  41. HOSPITAL_LOCKKEY(330,"医院对应key不一致")
  42. ;
  43. private Integer code;
  44. private String message;
  45. private ResultCodeEnum(Integer code, String message) {
  46. this.code = code;
  47. this.message = message;
  48. }
  49. }

4:完成vue2前端的创建

4.1 安装uploaderspark-md5的依赖

  1. npm install --save vue-simple-uploader
  2. npm install --save spark-md5

 

 4.2 mainjs导入uploader

  1. import uploader from 'vue-simple-uploader'
  2. Vue.use(uploader)

 4.3 创建uploader组件

  1. <template>
  2. <div>
  3. <uploader
  4. :autoStart="false"
  5. :options="options"
  6. :file-status-text="statusText"
  7. class="uploader-example"
  8. @file-complete="fileComplete"
  9. @complete="complete"
  10. @file-success="fileSuccess"
  11. @files-added="filesAdded"
  12. >
  13. <uploader-unsupport></uploader-unsupport>
  14. <uploader-drop>
  15. <p>将文件拖放到此处以上传</p>
  16. <uploader-btn>选择文件</uploader-btn>
  17. <uploader-btn :attrs="attrs">选择图片</uploader-btn>
  18. <uploader-btn :directory="true">选择文件夹</uploader-btn>
  19. </uploader-drop>
  20. <!-- <uploader-list></uploader-list> -->
  21. <uploader-files> </uploader-files>
  22. </uploader>
  23. <br />
  24. <el-button @click="allStart()" :disabled="disabled">全部开始</el-button>
  25. <el-button @click="allStop()" style="margin-left: 4px">全部暂停</el-button>
  26. <el-button @click="allRemove()" style="margin-left: 4px">全部移除</el-button>
  27. </div>
  28. </template>
  29. <script>
  30. import axios from "axios";
  31. import SparkMD5 from "spark-md5";
  32. import {upload} from "@/api/user";
  33. // import storage from "store";
  34. // import { ACCESS_TOKEN } from '@/store/mutation-types'
  35. export default {
  36. name: "Home",
  37. data() {
  38. return {
  39. skip: false,
  40. options: {
  41. target: "//localhost:9999/upload/chunk",
  42. // 开启服务端分片校验功能
  43. testChunks: true,
  44. parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) {
  45. return parsedTimeRemaining
  46. .replace(/\syears?/, "年")
  47. .replace(/\days?/, "天")
  48. .replace(/\shours?/, "小时")
  49. .replace(/\sminutes?/, "分钟")
  50. .replace(/\sseconds?/, "秒");
  51. },
  52. // 服务器分片校验函数
  53. checkChunkUploadedByResponse: (chunk, message) => {
  54. const result = JSON.parse(message);
  55. if (result.data.skipUpload) {
  56. this.skip = true;
  57. return true;
  58. }
  59. return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0;
  60. },
  61. // headers: {
  62. // // 在header中添加的验证,请根据实际业务来
  63. // "Access-Token": storage.get(ACCESS_TOKEN),
  64. // },
  65. },
  66. attrs: {
  67. accept: "image/*",
  68. },
  69. statusText: {
  70. success: "上传成功",
  71. error: "上传出错了",
  72. uploading: "上传中...",
  73. paused: "暂停中...",
  74. waiting: "等待中...",
  75. cmd5: "计算文件MD5中...",
  76. },
  77. fileList: [],
  78. disabled: true,
  79. };
  80. },
  81. watch: {
  82. fileList(o, n) {
  83. this.disabled = false;
  84. },
  85. },
  86. methods: {
  87. // fileSuccess(rootFile, file, response, chunk) {
  88. // // console.log(rootFile);
  89. // // console.log(file);
  90. // // console.log(message);
  91. // // console.log(chunk);
  92. // const result = JSON.parse(response);
  93. // console.log(result.success, this.skip);
  94. //
  95. // if (result.success && !this.skip) {
  96. // axios
  97. // .post(
  98. // "http://127.0.0.1:9999/upload/merge",
  99. // {
  100. // identifier: file.uniqueIdentifier,
  101. // filename: file.name,
  102. // totalChunks: chunk.offset,
  103. // },
  104. // // {
  105. // // headers: { "Access-Token": storage.get(ACCESS_TOKEN) }
  106. // // }
  107. // )
  108. // .then((res) => {
  109. // if (res.data.success) {
  110. // console.log("上传成功");
  111. // } else {
  112. // console.log(res);
  113. // }
  114. // })
  115. // .catch(function (error) {
  116. // console.log(error);
  117. // });
  118. // } else {
  119. // console.log("上传成功,不需要合并");
  120. // }
  121. // if (this.skip) {
  122. // this.skip = false;
  123. // }
  124. // },
  125. fileSuccess(rootFile, file, response, chunk) {
  126. // console.log(rootFile);
  127. // console.log(file);
  128. // console.log(message);
  129. // console.log(chunk);
  130. const result = JSON.parse(response);
  131. console.log(result.success, this.skip);
  132. const user = {
  133. identifier: file.uniqueIdentifier,
  134. filename: file.name,
  135. totalChunks: chunk.offset,
  136. }
  137. if (result.success && !this.skip) {
  138. upload(user).then((res) => {
  139. if (res.code == 200) {
  140. console.log("上传成功");
  141. } else {
  142. console.log(res);
  143. }
  144. })
  145. .catch(function (error) {
  146. console.log(error);
  147. });
  148. } else {
  149. console.log("上传成功,不需要合并");
  150. }
  151. if (this.skip) {
  152. this.skip = false;
  153. }
  154. },
  155. fileComplete(rootFile) {
  156. // 一个根文件(文件夹)成功上传完成。
  157. // console.log("fileComplete", rootFile);
  158. // console.log("一个根文件(文件夹)成功上传完成。");
  159. },
  160. complete() {
  161. // 上传完毕。
  162. // console.log("complete");
  163. },
  164. filesAdded(file, fileList, event) {
  165. // console.log(file);
  166. file.forEach((e) => {
  167. this.fileList.push(e);
  168. this.computeMD5(e);
  169. });
  170. },
  171. computeMD5(file) {
  172. let fileReader = new FileReader();
  173. let time = new Date().getTime();
  174. let blobSlice =
  175. File.prototype.slice ||
  176. File.prototype.mozSlice ||
  177. File.prototype.webkitSlice;
  178. let currentChunk = 0;
  179. const chunkSize = 1024 * 1024;
  180. let chunks = Math.ceil(file.size / chunkSize);
  181. let spark = new SparkMD5.ArrayBuffer();
  182. // 文件状态设为"计算MD5"
  183. file.cmd5 = true; //文件状态为“计算md5...”
  184. file.pause();
  185. loadNext();
  186. fileReader.onload = (e) => {
  187. spark.append(e.target.result);
  188. if (currentChunk < chunks) {
  189. currentChunk++;
  190. loadNext();
  191. // 实时展示MD5的计算进度
  192. console.log(
  193. `第${currentChunk}分片解析完成, 开始第${
  194. currentChunk + 1
  195. } / ${chunks}分片解析`
  196. );
  197. } else {
  198. let md5 = spark.end();
  199. console.log(
  200. `MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
  201. file.size
  202. } 用时:${new Date().getTime() - time} ms`
  203. );
  204. spark.destroy(); //释放缓存
  205. file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
  206. file.cmd5 = false; //取消计算md5状态
  207. file.resume(); //开始上传
  208. }
  209. };
  210. fileReader.onerror = function () {
  211. this.error(`文件${file.name}读取出错,请检查该文件`);
  212. file.cancel();
  213. };
  214. function loadNext() {
  215. let start = currentChunk * chunkSize;
  216. let end =
  217. start + chunkSize >= file.size ? file.size : start + chunkSize;
  218. fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
  219. }
  220. },
  221. allStart() {
  222. console.log(this.fileList);
  223. this.fileList.map((e) => {
  224. if (e.paused) {
  225. e.resume();
  226. }
  227. });
  228. },
  229. allStop() {
  230. console.log(this.fileList);
  231. this.fileList.map((e) => {
  232. if (!e.paused) {
  233. e.pause();
  234. }
  235. });
  236. },
  237. allRemove() {
  238. this.fileList.map((e) => {
  239. e.cancel();
  240. });
  241. this.fileList = [];
  242. },
  243. },
  244. };
  245. </script>
  246. <style>
  247. .uploader-example {
  248. width: 100%;
  249. padding: 15px;
  250. margin: 0px auto 0;
  251. font-size: 12px;
  252. box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
  253. }
  254. .uploader-example .uploader-btn {
  255. margin-right: 4px;
  256. }
  257. .uploader-example .uploader-list {
  258. max-height: 440px;
  259. overflow: auto;
  260. overflow-x: hidden;
  261. overflow-y: auto;
  262. }
  263. </style>

效果:

 

 

 redis 中的文件:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/66647
推荐阅读