参考自:https://blog.csdn.net/u014150463/article/details/74044467

这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数

下面直接贴代码吧,一些难懂的我大部分都加上注释了:

上传文件实体类:

/**
* 文件传输对象
* @ApiModel和@ApiModelProperty及Controller中@Api开头的注解 是swagger中的注解 用于项目Api的自动生成,如果有没接触过的同学,可以把他理解为一个注释
*/
@ApiModel("大文件分片入参实体")
public class MultipartFileParam {
@ApiModelProperty("文件传输任务ID")
private String taskId;
@ApiModelProperty("当前为第几分片")
private int chunk;
@ApiModelProperty("每个分块的大小")
private long size; @ApiModelProperty("分片总数")
private int chunkTotal;
@ApiModelProperty("主体类型--这个字段是我项目中的其他业务逻辑可以忽略")
private int objectType;
@ApiModelProperty("分块文件传输对象")
private MultipartFile file;

首先是Controller层:

     @ApiOperation("大文件分片上传")
@PostMapping("chunkUpload")
public void fileChunkUpload(MultipartFileParam param, HttpServletResponse response, HttpServletRequest request){
/**
* 判断前端Form表单格式是否支持文件上传
*/
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart){
//这里是我向前端发送数据的代码,可理解为 return 数据; 具体的就不贴了
resultData = ResultData.buildFailureResult("不支持的表单格式", ResultCodeEnum.NOTFILE.getCode());
printJSONObject(resultData,response);
return;
}
logger.info("上传文件 start...");
try {
String taskId = fileManage.chunkUploadByMappedByteBuffer(param);
} catch (IOException e) {
logger.error("文件上传失败。{}", param.toString());
}
logger.info("上传文件结束");
}

  Service层: FileManage 我这里是使用 ---直接字节缓冲器 MappedByteBuffer 来实现分块上传,还有另外一种方法使用RandomAccessFile 来实现的,使用前者速度较快所以这里就直说 MappedByteBuffer 的方法

  具体步骤如下:

第一步:获取RandomAccessFile,随机访问文件类的对象
第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
第三步:获取当前是第几个分块,计算文件的最后偏移量
第四步:获取当前文件分块的字节数组,用于获取文件字节长度
第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
第六步:将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b);
第七步:释放缓冲区
第八步:检查文件是否全部完成上传

  如下代码:

package com.zcz.service.impl;

import com.zcz.bean.dto.MultipartFileParam;
import com.zcz.exception.ServiceException;
import com.zcz.service.IFileManage;
import com.zcz.util.FileUtil;
import com.zcz.util.ImageUtil;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.*; /**
* 文件上传服务层
*/
@Service("fileManage")
public class FileManageImpl implements IFileManage { @Value("${basePath}")
private String basePath; @Value("${file-url}")
private String fileUrl; /**
* 分块上传
* 第一步:获取RandomAccessFile,随机访问文件类的对象
* 第二步:调用RandomAccessFile的getChannel()方法,打开文件通道 FileChannel
* 第三步:获取当前是第几个分块,计算文件的最后偏移量
* 第四步:获取当前文件分块的字节数组,用于获取文件字节长度
* 第五步:使用文件通道FileChannel类的 map()方法创建直接字节缓冲器 MappedByteBuffer
* 第六步:将分块的字节数组放入到当前位置的缓冲区内 mappedByteBuffer.put(byte[] b);
* 第七步:释放缓冲区
* 第八步:检查文件是否全部完成上传
* @param param
* @return
* @throws IOException
*/
@Override
public String chunkUploadByMappedByteBuffer(MultipartFileParam param) throws IOException {
if(param.getTaskId() == null || "".equals(param.getTaskId())){
param.setTaskId(UUID.randomUUID().toString());
}
/**
* basePath是我的路径,可以替换为你的
* 1:原文件名改为UUID
* 2:创建临时文件,和源文件一个路径
* 3:如果文件路径不存在重新创建
*/
String fileName = param.getFile().getOriginalFilename();
     //fileName.substring(fileName.lastIndexOf(".")) 这个地方可以直接写死 写成你的上传路径
String tempFileName = param.getTaskId() + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
String filePath = basePath + getFilePathByType(param.getObjectType()) + "/original";
File fileDir = new File(filePath);
if(!fileDir.exists()){
fileDir.mkdirs();
}
File tempFile = new File(filePath,tempFileName);
//第一步
RandomAccessFile raf = new RandomAccessFile(tempFile,"rw");
//第二步
FileChannel fileChannel = raf.getChannel();
//第三步
long offset = param.getChunk() * param.getSize();
//第四步
byte[] fileData = param.getFile().getBytes();
//第五步
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,offset,fileData.length);
//第六步
mappedByteBuffer.put(fileData);
//第七步
FileUtil.freeMappedByteBuffer(mappedByteBuffer);
fileChannel.close();
raf.close();
//第八步
boolean isComplete = checkUploadStatus(param,fileName,filePath);
if(isComplete){
renameFile(tempFile,fileName);
}
return "";
} /**
* 文件重命名
* @param toBeRenamed 将要修改名字的文件
* @param toFileNewName 新的名字
* @return
*/
public boolean renameFile(File toBeRenamed, String toFileNewName) {
//检查要重命名的文件是否存在,是否是文件
if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
return false;
}
String p = toBeRenamed.getParent();
File newFile = new File(p + File.separatorChar + toFileNewName);
//修改文件名
return toBeRenamed.renameTo(newFile);
} /**
* 检查文件上传进度
* @return
*/
public boolean checkUploadStatus(MultipartFileParam param,String fileName,String filePath) throws IOException {
File confFile = new File(filePath,fileName+".conf");
RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
//设置文件长度
confAccessFile.setLength(param.getChunkTotal());
//设置起始偏移量
confAccessFile.seek(param.getChunk());
//将指定的一个字节写入文件中 127,
confAccessFile.write(Byte.MAX_VALUE);
byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
byte isComplete = Byte.MAX_VALUE;
     //这一段逻辑有点复杂,看的时候思考了好久,创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
for(int i = 0; i<completeStatusList.length && isComplete==Byte.MAX_VALUE; i++){
       // 按位与运算,将&两边的数转为二进制进行比较,有一个为0结果为0,全为1结果为1 eg.3&5  即 0000 0011 & 0000 0101 = 0000 0001   因此,3&5的值得1。
isComplete = (byte)(isComplete & completeStatusList[i]);
System.out.println("check part " + i + " complete?:" + completeStatusList[i]);
}
if(isComplete == Byte.MAX_VALUE){
       //如果全部文件上传完成,删除conf文件
       confFile.delete();
return true;
}
return false;
}
  
    /**
   * 根据主体类型,获取每个主题所对应的文件夹路径 我项目内的需求可以忽略
   * @param objectType
  * @return filePath 文件路径
   */
  private String getFilePathByType(Integer objectType){
   //不同主体对应的文件夹
   Map<Integer,String> typeMap = new HashMap<>();
   typeMap.put(1,"Article");
   typeMap.put(2,"Question");
   typeMap.put(3,"Answer");
   typeMap.put(4,"Courseware");
   typeMap.put(5,"Lesson");
   String objectPath = typeMap.get(objectType);
   if(objectPath==null || "".equals(objectPath)){
   throw new ServiceException("主体类型不存在");
   }
   return objectPath;
  }
}

  FileUtil:

    /**
* 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash,在并发情况下很容易发生
* 正在释放时另一个线程正开始读取,于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
* @param mappedByteBuffer
*/
public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
try {
if (mappedByteBuffer == null) {
return;
}
mappedByteBuffer.force();
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
//可以访问private的权限
getCleanerMethod.setAccessible(true);
//在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
new Object[0]);
cleaner.clean();
} catch (Exception e) {
logger.error("clean MappedByteBuffer error!!!", e);
}
logger.info("clean MappedByteBuffer completed!!!");
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}

  好了,到此就全部结束了,如果有疑问或批评,欢迎评论和私信,我们一起成长一起学习。

  

java springboot 大文件分片上传处理的更多相关文章

  1. Webuploader 大文件分片上传

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

  2. vue+大文件分片上传

    最近公司在使用vue做工程项目,实现大文件分片上传. 网上找了一天,发现网上很多代码都存在很多问题,最后终于找到了一个符合要求的项目. 工程如下: 对项目的大文件上传功能做出分析,怎么实现大文件分片上 ...

  3. iOS大文件分片上传和断点续传

    总结一下大文件分片上传和断点续传的问题.因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况.http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件 ...

  4. js实现大文件分片上传的方法

    借助js的Blob对象FormData对象可以实现大文件分片上传的功能,关于Blob和FormData的具体使用方法可以到如下地址去查看FormData 对象的使用Blob 对象的使用以下是实现代码, ...

  5. Node + js实现大文件分片上传基本原理及实践(一)

    _ 阅读目录 一:什么是分片上传? 二:理解Blob对象中的slice方法对文件进行分割及其他知识点 三. 使用 spark-md5 生成 md5文件 四. 使用koa+js实现大文件分片上传实践 回 ...

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

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

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

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

  8. nodeJs + js 大文件分片上传

    简单的文件上传 一.准备文件上传的条件: 1.安装nodejs环境 2.安装vue环境 3.验证环境是否安装成功 二.实现上传步骤 1.前端部分使用 vue-cli 脚手架,搭建一个 demo 版本, ...

  9. PHP大文件分片上传的实现方法

    一.前言 在网站开发中,经常会有上传文件的需求,有的文件size太大直接上传,经常会导致上传过程中耗时太久,大量占用带宽资源,因此有了分片上传. 分片上传主要是前端将一个较大的文件分成等分的几片,标识 ...

随机推荐

  1. 《AutoCAD Civil 3D .NET二次开发》勘误1

    第十三章atc文件中Displayname应为DisplayName,注意Name的N为大写,否则参数名称无法正常显示. 给您带来的不便深表歉意!

  2. POJ 1015 Jury Compromise (算竞进阶习题)

    01背包 我们对于这类选或者不选的模型应该先思考能否用01背包来解. 毫无疑问物体的价值可以看成最大的d+p值,那么体积呢?题目的另一个限制条件是d-p的和的绝对值最小,这启发我们把每个物体的d-p的 ...

  3. JS在一个数组中查找某个用户输入的值,返回对应值所在索引值

    方法有很多种 第一:直接循环,判断输出 第二:使用indexOf 正常来说,为了增加工作效率一般会选择indexOf,但是indexOf存在兼容性问题,因此最完善的写法如下 function inde ...

  4. [GoogleBlog]new-approach-to-china

    https://googleblog.blogspot.com/2010/01/new-approach-to-china.html

  5. 一文入门HTML5

    1.HTML5 上节回顾:一文读懂ES6(附PY3对比) | 一文入门NodeJS 演示demo:https://github.com/lotapp/BaseCode/tree/master/java ...

  6. luogu5283 异或粽子

    题目链接 思路 首先求个前缀异或和,这样就可以\(O(1)\)的得到区间异或和了. 然后发现问题转化为 找出不同的\(k\)个二元组\(x,y\).使得\(a_x \otimes a_y\)的和最大. ...

  7. 应用系统如何分析和获取SQL语句的执行代码

    大部分开发人员都有这样一个需求,在程序连接数据库执行时,有时需要获取具体的执行语句,以便进行相关分析,这次我向大家介绍一下通用权限管理系统提供的SQL语句执行跟踪记录,直接先看看代码吧:(这个功能我也 ...

  8. 第二篇--PCI设备解析

    介绍:参考 一个系统上最多有256个PCI总线,每个总线最多有32个设备,每个设备最多有8个功能,每个功能最多有256字节的配置地址空间,所以总的配置地址空间是16M. PCI设备有物理设备和逻辑设备 ...

  9. 微信, qq 支付宝 等相关开发 资源 记录

    手机QQweb开发平台  api : http://open.mobile.qq.com/api/component/share qq 微信  分享 简介 :https://segmentfault. ...

  10. [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [E:\soft\studySoft\tomcat\apache-tomcat-8.5.33\webapp

    问题 启动tomcat,就一直卡在了这里 继续往上查看日志 解决方法: