大文件分块上传.断点续传.秒传

大文件分块上传

分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为
Part )来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
分片上传适用场景
1. 大文件上传
2. 网络环境环境不好,存在需要重传风险的场景
分片上传的基本流程图
大文件分块上传.断点续传.秒传_第1张图片
1. 将待上传文件按照一定大小进行分片。
2. 使用 InitiateMultipartUpload 接口初始化一个分片上传任务。
3. 使用 UploadPart 接口上传分片。
文件切分成 Part 之后,文件顺序是通过上传过程中指定的 partNumber 来确定,所以您可以并发上
传这些碎片。并发数并非越多越快,请结合自身网络状况和设备负载综合考虑。
4. 使用 CompleteMultipartUpload 接口将 Part 组合成一个 Object

 断点续传

断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部
分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分
开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。
断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。
实现流程步骤 1. 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和
大小
2. 服务端创建 conf 文件用来记录分块位置, conf 文件长度为总分片数,每上传一个分块即向
conf 文件中写入一个 127 ,那么没上传的位置就是默认的 0, 已上传的就是 Byte.MAX_VALUE
127 (这步是实现断点续传和秒传的核心步骤)
3. 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位
置,与读取到的文件片段数据,写入文件

秒传

通俗的说,你把要上传的东西上传,服务器会先做 MD5 校验,如果服务器上有一样的东西,它就直
接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让 MD5 改变,
就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字。
核心逻辑
1. 利用 redis set 方法存放文件上传状态,其中 key 为文件上传的 md5 value 为是否上传完成的
标志位,
2. 当标志位 true 为上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为
false ,则说明还没上传完成,此时需要在调用 set 的方法,保存块号文件记录的路径,其中
key 为上传文件 md5 加一个固定前缀, value 为块号文件记录路径

代码实现

前端



BigFile-WebUploader









后端
controller
import com.zxh.pojo.MultipartFileParam;
import com.zxh.utils.JsonResult;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.*;
/**
* @Title: 大文件上传
* @Description: 断点续传.秒传.分块上传
*/
@Controller
@RequestMapping(value = "/bigfile")
public class BigFileController {
private Logger logger =
LoggerFactory.getLogger(BigFileController.class);
private final String FILESTOREPATH = "E:/data0/uploads/" ;
/**
* @Title: 判断文件是否上传过,是否存在分片,断点续传
* @MethodName: checkBigFile
* @param fileMd5
* @Description:
* 文件已存在,下标为-1
* 文件没有上传过,下标为零
* 文件上传中断过,返回当前已经上传到的下标
*/
@RequestMapping(value = "/check", method = RequestMethod.POST)
@ResponseBody
public JsonResult checkBigFile(String fileMd5) {
JsonResult jr = new JsonResult();
// 秒传
File mergeMd5Dir = new File(FILESTOREPATH + "/" + "merge"+ "/" +
fileMd5);
if(mergeMd5Dir.exists()){
mergeMd5Dir.mkdirs();
jr.setResultCode(-1);//文件已存在,下标为-1
return jr;
}
// 读取目录里的所有文件
File dir = new File(FILESTOREPATH + "/" + fileMd5);
File[] childs = dir.listFiles();
if(childs==null){
jr.setResultCode(0);//文件没有上传过,下标为零
}else{
jr.setResultCode(childs.length-1);//文件上传中断过,返回当前已经
上传到的下标
}
return jr;
}
/**
* 上传文件
*
* @param param
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public void filewebUpload(MultipartFileParam param,
HttpServletRequest request) {
System.out.println("param"+param.toString());
// 文件名
String fileName = param.getName();
// 文件每次分片的下标
int chunkIndex = param.getChunk();
//用于判断是否是上传.可以简单理解,就是判断encType="multipart/formdata"
boolean isMultipart =
ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
File file = new File(FILESTOREPATH + "/" + param.getMd5());
if (!file.exists()) {
file.mkdir();
}
File chunkFile = new File(
FILESTOREPATH + "/" + param.getMd5() + "/" +
chunkIndex);
try{
FileUtils.copyInputStreamToFile(param.getFile().getInputStream(),
chunkFile);
}catch (Exception e){
e.printStackTrace();
}
}
logger.info("文件-:{}的小标-:{},上传成功",fileName,chunkIndex);
return;
}
/**
* 分片上传成功之后,合并文件
* @param request
* @return
*/
@RequestMapping(value = "/merge", method = RequestMethod.POST)
@ResponseBody
public JsonResult filewebMerge(HttpServletRequest request) {
//FileChannel是一个用读写,映射和操作一个文件的通道。它能直接连接输入输出流
的文件通道,将数据直接写入到目标文件中去。而且效率更高。
FileChannel outChannel = null;
try {
String fileName = request.getParameter("fileName");
String fileMd5 = request.getParameter("fileMd5");
// 读取目录里的所有文件
File dir = new File(FILESTOREPATH + "/" + fileMd5);
File[] childs = dir.listFiles();
if(Objects.isNull(childs)|| childs.length==0){
return null;
}
// 转成集合,便于排序
List fileList = new ArrayList
(Arrays.asList(childs));
Collections.sort(fileList, new Comparator() {
@Override
public int compare(File o1, File o2) {
if (Integer.parseInt(o1.getName()) <
Integer.parseInt(o2.getName())) {
return -1;
}
return 1;
}
});
// 合并后的文件
File outputFile = new File(FILESTOREPATH + "/" + "merge"+
"/" + fileMd5 + "/" + fileName);
// 创建文件
if(!outputFile.exists()){
File mergeMd5Dir = new File(FILESTOREPATH + "/" +
"merge"+ "/" + fileMd5);
if(!mergeMd5Dir.exists()){
mergeMd5Dir.mkdirs();
}
logger.info("创建文件");
outputFile.createNewFile();
}
//开始合并
outChannel = new FileOutputStream(outputFile).getChannel();
FileChannel inChannel = null;
try {
for (File file : fileList) {
inChannel = new FileInputStream(file).getChannel();
inChannel.transferTo(0, inChannel.size(),
outChannel);
inChannel.close();
// 删除分片
file.delete();
}
}catch (Exception e){
e.printStackTrace();
//发生异常,文件合并失败 ,删除创建的文件
outputFile.delete();
dir.delete();//删除文件夹
}finally {
if(inChannel!=null){
inChannel.close();
}
}
dir.delete(); //删除分片所在的文件夹
// FIXME: 数据库操作, 记录文件存档位置
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(outChannel!=null){
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
统一响应类
public class JsonResult {
private int resultCode;
private String resultMsg;
private Object resultData;
public JsonResult() {
}
public JsonResult(int resultCode, String resultMsg, Object
resultData) {
this.resultCode = resultCode;
this.resultMsg = resultMsg;
this.resultData = resultData;
}
public int getResultCode() {
return this.resultCode;
}
public void setResultCode(int resultCode) {
this.resultCode = resultCode;
}
public String getResultMsg() {
return this.resultMsg;
}
public void setResultMsg(String resultMsg) {
this.resultMsg = resultMsg;
}
public Object getResultData() {
return this.resultData;
}
public void setResultData(Object resultData) {
this.resultData = resultData;
}
}
MultipartFileParam
import org.springframework.web.multipart.MultipartFile;
public class MultipartFileParam {
// 用户id
private String uid;
//任务ID
private String id;
//总分片数量
private int chunks;
//当前为第几块分片
private int chunk;
//当前分片大小
private long size = 0L;
//文件名
private String name;
//分片对象
private MultipartFile file;
// MD5
private String md5;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getChunks() {
return chunks;
}
public void setChunks(int chunks) {
this.chunks = chunks;
}
public int getChunk() {
return chunk;
}
public void setChunk(int chunk) {
this.chunk = chunk;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file = file;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
@Override
public String toString() {
return "MultipartFileParam{" +
"uid='" + uid + '\'' +
", id='" + id + '\'' +
", chunks=" + chunks +
", chunk=" + chunk +
", size=" + size +
", name='" + name + '\'' +
", file=" + file +
", md5='" + md5 + '\'' +
'}';
}
}

你可能感兴趣的:(html5,css,javascript,spring,boot)