Hi! 我是小小,今天是本周的第五篇,小小又懒惰了,没有半夜更新,没办法,一大早起来开始更新。

前言

文件上传是网络开发中常见的环节,对于文件上传,有以下几种的上传方式,分别是秒传,断点续传,分片上传

秒传

什么是秒传

秒传就是服务器会先做MD5校验,根据校验的结果,如果服务器上有相同的文件,则会直接给出这个文件在服务器上的地址,如果没有则会按照正常的方式进行文件的上传。

实现核心逻辑

利用redis的set方法保存文件的上传状态,其中key为文件上传的md5,value为上传完成的标志位。当标志位为true的时候,上传完成,如果有相同的文件上传,进入秒传逻辑,如果标志位为false,那么文件还没有上传完成,此时需要再次调用set方法, 保存文件记录的路径,其中key为文件上传的md5加上一个前缀,value为块号文件的记录的路径

分片上传

什么是分片上传

分片上传,就是把文件进行分割开来进行上传

上传场景

大文件上传 网络环境不好的时候

断点续传

什么是断点续传

断点续传就是文件在下载或者上传的时候,如果临时中断相关操作,那么过一段时间重新开始相关操作,那么相关的操作仍旧可以继续延续。

应用场景

可以使用分片上传的场景,都可以使用断点续传

核心逻辑

需要记录文件上传的进度,在之后继续上传的时候,从原先的进度重新开始。为了避免客户端数据删除,导致重新上传的问题,服务器端也需要进行相关的记录。

实现步骤

方案一,常规步骤

把需要上传的文件,按照一定的分割规则,分割为相同大小的数据块 初始化一个分片上传任务,返回本次分片上传的唯一标识 按照一定的方式,发送相关的数据块。发送完成以后,服务端根据数据是否完整,重新进行相关的整合。

方案二:本文实现的步骤

前端,需要根据固定大小对文件进行分片,请求后端要带上分片序号和大小 服务器创建一个conf文件进行记录分块的位置,conf文件长度为总分片的数量,每上传一个分片,向conf文件写入一个127,那么美上传的就是0,已经上传的就是127. 服务器搜索相关的分片大小,算出初始位置,开始写入文件。

代码实现

  1. 前端采用百度提供的webuploader的插件,进行分片。具体链接如下所示:http://fex.baidu.com/webuploader/getting-started.html

  2. 后端用两种方式实现文件写入,一种是用RandomAccessFile,如果对RandomAccessFile不熟悉的朋友,可以查看如下链接: https://blog.csdn.net/dimudan2015/article/details/81910690

后端写入的核心代码

RandomAccessFile实现方式

@UploadMode(mode = UploadModeEnum.RANDOM_ACCESS)
@Slf4j
public class RandomAccessUploadStrategy extends SliceUploadTemplate {  @Autowired  private FilePathUtil filePathUtil;  @Value("${upload.chunkSize}")  private long defaultChunkSize;  @Override  public boolean upload(FileUploadRequestDTO param) {  RandomAccessFile accessTmpFile = null;  try {  String uploadDirPath = filePathUtil.getPath(param);  File tmpFile = super.createTmpFile(param);  accessTmpFile = new RandomAccessFile(tmpFile, "rw");  //这个必须与前端设定的值一致  long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024  : param.getChunkSize();  long offset = chunkSize * param.getChunk();  //定位到该分片的偏移量  accessTmpFile.seek(offset);  //写入该分片数据  accessTmpFile.write(param.getFile().getBytes());  boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);  return isOk;  } catch (IOException e) {  log.error(e.getMessage(), e);  } finally {  FileUtil.close(accessTmpFile);  }  return false;  }  }

MappedByteBuffer实现方式

@UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER)
@Slf4j
public class MappedByteBufferUploadStrategy extends SliceUploadTemplate {  @Autowired  private FilePathUtil filePathUtil;  @Value("${upload.chunkSize}")  private long defaultChunkSize;  @Override  public boolean upload(FileUploadRequestDTO param) {  RandomAccessFile tempRaf = null;  FileChannel fileChannel = null;  MappedByteBuffer mappedByteBuffer = null;  try {  String uploadDirPath = filePathUtil.getPath(param);  File tmpFile = super.createTmpFile(param);  tempRaf = new RandomAccessFile(tmpFile, "rw");  fileChannel = tempRaf.getChannel();  long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024  : param.getChunkSize();  //写入该分片数据  long offset = chunkSize * param.getChunk();  byte[] fileData = param.getFile().getBytes();  mappedByteBuffer = fileChannel
.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);  mappedByteBuffer.put(fileData);  boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);  return isOk;  } catch (IOException e) {  log.error(e.getMessage(), e);  } finally {  FileUtil.freedMappedByteBuffer(mappedByteBuffer);  FileUtil.close(fileChannel);  FileUtil.close(tempRaf);  }  return false;  }  }

文件操作核心模板类代码

@Slf4j
public abstract class SliceUploadTemplate implements SliceUploadStrategy {  public abstract boolean upload(FileUploadRequestDTO param);  protected File createTmpFile(FileUploadRequestDTO param) {  FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);  param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));  String fileName = param.getFile().getOriginalFilename();  String uploadDirPath = filePathUtil.getPath(param);  String tempFileName = fileName + "_tmp";  File tmpDir = new File(uploadDirPath);  File tmpFile = new File(uploadDirPath, tempFileName);  if (!tmpDir.exists()) {  tmpDir.mkdirs();  }  return tmpFile;  }  @Override  public FileUploadDTO sliceUpload(FileUploadRequestDTO param) {  boolean isOk = this.upload(param);  if (isOk) {  File tmpFile = this.createTmpFile(param);  FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile);  return fileUploadDTO;  }  String md5 = FileMD5Util.getFileMD5(param.getFile());  Map<Integer, String> map = new HashMap<>();  map.put(param.getChunk(), md5);  return FileUploadDTO.builder().chunkMd5Info(map).build();  }  /**  * 检查并修改文件上传进度  */  public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) {  String fileName = param.getFile().getOriginalFilename();  File confFile = new File(uploadDirPath, fileName + ".conf");  byte isComplete = 0;  RandomAccessFile accessConfFile = null;  try {  accessConfFile = new RandomAccessFile(confFile, "rw");  //把该分段标记为 true 表示完成  System.out.println("set part " + param.getChunk() + " complete");  //创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE 127  accessConfFile.setLength(param.getChunks());  accessConfFile.seek(param.getChunk());  accessConfFile.write(Byte.MAX_VALUE);  //completeList 检查是否全部完成,如果数组里是否全部都是127(全部分片都成功上传)  byte[] completeList = FileUtils.readFileToByteArray(confFile);  isComplete = Byte.MAX_VALUE;  for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {  //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE  isComplete = (byte) (isComplete & completeList[i]);  System.out.println("check part " + i + " complete?:" + completeList[i]);  }  } catch (IOException e) {  log.error(e.getMessage(), e);  } finally {  FileUtil.close(accessConfFile);  }  boolean isOk = setUploadProgress2Redis(param, uploadDirPath, fileName, confFile, isComplete);  return isOk;  }  /**  * 把上传进度信息存进redis  */  private boolean setUploadProgress2Redis(FileUploadRequestDTO param, String uploadDirPath,  String fileName, File confFile, byte isComplete) {  RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);  if (isComplete == Byte.MAX_VALUE) {  redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "true");  redisUtil.del(FileConstant.FILE_MD5_KEY + param.getMd5());  confFile.delete();  return true;  } else {  if (!redisUtil.hHasKey(FileConstant.FILE_UPLOAD_STATUS, param.getMd5())) {  redisUtil.hset(FileConstant.FILE_UPLOAD_STATUS, param.getMd5(), "false");  redisUtil.set(FileConstant.FILE_MD5_KEY + param.getMd5(),  uploadDirPath + FileConstant.FILE_SEPARATORCHAR + fileName + ".conf");  }  return false;  }  }
/**  * 保存文件操作  */  public FileUploadDTO saveAndFileUploadDTO(String fileName, File tmpFile) {  FileUploadDTO fileUploadDTO = null;  try {  fileUploadDTO = renameFile(tmpFile, fileName);  if (fileUploadDTO.isUploadComplete()) {  System.out  .println("upload complete !!" + fileUploadDTO.isUploadComplete() + " name=" + fileName);  //TODO 保存文件信息到数据库  }  } catch (Exception e) {  log.error(e.getMessage(), e);  } finally {  }  return fileUploadDTO;  }
/**  * 文件重命名  *  * @param toBeRenamed 将要修改名字的文件  * @param toFileNewName 新的名字  */  private FileUploadDTO renameFile(File toBeRenamed, String toFileNewName) {  //检查要重命名的文件是否存在,是否是文件  FileUploadDTO fileUploadDTO = new FileUploadDTO();  if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {  log.info("File does not exist: {}", toBeRenamed.getName());  fileUploadDTO.setUploadComplete(false);  return fileUploadDTO;  }  String ext = FileUtil.getExtension(toFileNewName);  String p = toBeRenamed.getParent();  String filePath = p + FileConstant.FILE_SEPARATORCHAR + toFileNewName;  File newFile = new File(filePath);  //修改文件名  boolean uploadFlag = toBeRenamed.renameTo(newFile);  fileUploadDTO.setMtime(DateUtil.getCurrentTimeStamp());  fileUploadDTO.setUploadComplete(uploadFlag);  fileUploadDTO.setPath(filePath);  fileUploadDTO.setSize(newFile.length());  fileUploadDTO.setFileExt(ext);  fileUploadDTO.setFileId(toFileNewName);  return fileUploadDTO;  }
}

关于作者

我是小小,双鱼座的程序猿,我们下期再见~bye

END

「 往期文章 」

测试 | 测试:你会这些命令吗?

心酸 | Bean复制的几种框架对比,看完心酸

线程安全 | i++线程安全?真相是这样……

扫描二维码

获取更多精彩

小明菜市场

来源:网络(侵删)

图片来源:网络(侵删)

点个在看你最好看

教程 | 叮咚!答应你们的文件上传教程,到货了!相关推荐

  1. 【转】NodeJS教程--基于ExpressJS框架的文件上传

    本文是翻译的一篇文章,原文地址:Handle File Uploads in Express (Node.js). 在NodeJS发展早期上传文件是一个较难操作的功能,随后出现了formidable. ...

  2. Spring Boot教程(十三):Spring Boot文件上传

    分享一个零基础,通俗易懂,而且非常风趣幽默的人工智能教程(如不能直接点击访问,请以"右键"->"在新标签页中打开链接"方式打开)网站,网址:https:/ ...

  3. JSP笔记-文件上传

    JSP(全称JavaServer Pages)是由Sun Microsystems公司主导创建的一种动态网页技术标准.JSP部署于网络服务器上,可以响应客户端发送的请求,并根据请求内容动态地生成HTM ...

  4. 用php文件创建表,使用PHP创建单个文件上传表单的最佳方式是什么?

    文件上传教程 HTML > action.php是将处理上传的PHP文件的名称(如下所示) > MAX_FILE_SIZE必须在输入类型文件之前立即出现.该值可以很容易地在客户端上操作,所 ...

  5. data后缀文件解码_封装ThinkPHP6.0通用文件上传

    php中文网最新课程 每日17点准时技术干货分享 本文为php中文网认证作者:"wpj"投稿,欢迎加入php中文网有偿投稿计划! 本文实例讲述了封装ThinkPHP6通用文件上传方 ...

  6. Retrofit2 multpart多文件上传详解

    原文出处:http://www.chenkaihua.com/2016/04/02/retrofit2-upload-multipart-files.html Retrofit2是目前很流行的andr ...

  7. Dubbo + RestEasy 实现文件上传与下载

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. Dubbo+RestEasy实现文件上传与下载 Dubbo+RestEasy实现文件上传与下载 Ma ...

  8. 【JSP HTTP 状态码】【JSP 表单处理】【JSP 过滤器】【JSP Cookie 处理】【JSP Session】【JSP 文件上传】

    JSP HTTP 状态码 HTTP请求与HTTP响应的格式相近,都有着如下结构: 以状态行+CRLF(回车换行)开始 零行或多行头模块+CRLF 一个空行,比如CRLF 可选的消息体比如文件,查询数据 ...

  9. JSP 实现文件上传

    JSP 可以与 HTML form 标签一起使用,来允许用户上传文件到服务器.上传的文件可以是文本文件或图像文件或任何文档. 本章节我们使用 Servlet 来处理文件上传,使用到的文件有: uplo ...

最新文章

  1. oracle 查询天,Oracle查询_ 单表查询
  2. cds.data:=dsp.data赋值有时会出现AV错误剖析
  3. 自绘制View---------左右滑动刻度调频View
  4. C++ semi implicit euler半隐式向后欧拉法解算常微分方程(附完整源码)
  5. 极域电子书包课堂管理系统_【君莲微讯】君莲学校(小学部)开展电子书包第13共同体数学研讨活动...
  6. VMWare serve 2.0 进入 RHEL Linux rescue模式
  7. Java6 WebService学习
  8. Arduino笔记-解决ESP8266上传代码时出现error: espcomm_upload_mem failed问题
  9. Framework4.0 IIS7下urlrewriter设置问题
  10. gerrit docker运行失败 chown: /var/gerrit/review_site: Permission denied 【已解决】
  11. 图像增强_MATLAB图像处理之图像增强一
  12. ie8升级到ie11
  13. R语言实现PVAR(面板向量自回归模型)
  14. 无需编码 9款优秀的数据地图可视化工具平台
  15. 微信推送封面尺寸_微信公众号文章封面图尺寸应该是多大?
  16. Redis和Memcached区别
  17. Python爬虫面试简历 经验分析
  18. 矩阵顺逆时针旋转、翻转 java
  19. [转]1、蓝牙核心技术了解(蓝牙协议、架构、硬件和软件笔记)
  20. http请求digest auth认证

热门文章

  1. 如何优化网站服务器来提高吞吐量并减少延迟时间
  2. mysql 主主+keepalive
  3. asp.net MVC 验证错误信息本地化
  4. python开发的类似stardict工具
  5. 2010/9/12学习历程
  6. 算法高级(26)-在Java8中为什么要使用红黑树来实现的HashMap?
  7. powerDesigner生成excel版本的数据库文件
  8. 水题总结NYOJ74,1094,60,975,111,833
  9. 3d文件与html结合,js和HTML5怎么结合?
  10. Windows 2000安装和配置RIS服务