大文件面临的问题
    
        1. 上传速度慢 -- 应对: 分块上传
        2. 上传文件到一半中断后,继续上传却只能重头开始上传 -- 应对: 断点续传
        3. 相同文件未修改再次上传, 却只能重头开始上传 -- 应对: 秒传

分片上传

1、什么分片上传

分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件

2、分片上传适用场景

  1. 大文件上传
  2. 网络环境环境不好,存在需要重传风险的场景

3、上传的具体流程

因为这个上传流程和断点续传类似,就在下边介绍断点续传中介绍

断点续传

1、什么是断点续传

断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景

2、应用场景

断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传

3、实现断点续传的核心逻辑

在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。

为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端也可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传。

4、实现流程步骤

  • 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小
  • 服务端创建conf文件用来记录分块位置,conf文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤)
  • 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件

秒传

1、什么是秒传

通俗的说,你把要上传的东西上传,服务器会先做MD5校验,如果服务器上有一样的东西,它就直接给你个新地址,其实你下载的都是服务器上的同一个文件,想要不秒传,其实只要让MD5改变,就是对文件本身做一下修改(改名字不行),例如一个文本文件,你多加几个字,MD5就变了,就不会秒传了

2、本文实现的秒传核心逻辑

a、利用redis的set方法存放文件上传状态,其中key为文件上传的md5,value为是否上传完成的标志位,

b、当标志位true为上传已经完成,此时如果有相同文件上传,则进入秒传逻辑。如果标志位为false,则说明还没上传完成,此时需要在调用set的方法,保存块号文件记录的路径,其中key为上传文件md5加一个固定前缀,value为块号文件记录路径

WebUploader

1,什么是 WebUploader?

WebUploader 是由百度公司团队开发的一个以 HTML5 为主,FLASH 为辅的现代文件上传组件。

  • 官网地址:Web Uploader

2,功能特点

  • 分片、并发:WebUploader 采用大文件分片并发上传,极大的提高了文件上传效率
  • 预览、压缩:WebUploader 支持常用图片格式 jpg,jpeg,gif,bmp,png 预览与压缩,节省网络数据传输。
  • 多途径添加文件:支持文件多选,类型过滤,拖拽(文件 & 文件夹),图片粘贴功能。
  • HTML5 & FLASH:兼容主流浏览器,接口一致,实现了两套运行时支持,用户无需关心内部用了什么内核。
  • MD5 秒传:当文件体积大、量比较多时,支持上传前做文件 md5 值验证,一致则可直接跳过
  • 易扩展、可拆分:采用可拆分机制, 将各个功能独立成了小组件,可自由搭配。

3. 接口说明

  • before-send-file 此hook在文件发送之前执行
  • before-file 此hook在文件分片(如果没有启用分片,整个文件被当成一个分片)后,上传之前执行。
  • after-send-file 此hook在文件所有分片都上传完后,且服务端没有错误返回后执行。

Web Uploader的所有代码都在一个内部闭包中,对外暴露了唯一的一个变量WebUploader,所以完全不用担心此框架会与其他框架冲突。

内部所有的类和功能都暴露在WebUploader名字空间下面。

Demo中使用的是WebUploader.create方法来初始化的,实际上可直接访问WebUploader.Uploader

var uploader = new WebUploader.Uploader({swf: 'path_of_swf/Uploader.swf'// 其他配置项
});

具体有哪些内部类,请转到API页面。

4. 事件

Uploader实例具有Backbone同样的事件API:onoffoncetrigger

uploader.on( 'fileQueued', function( file ) {// do some things.
});

除了通过on绑定事件外,Uploader实例还有一个更便捷的添加事件方式。

uploader.onFileQueued = function( file ) {// do some things.
};

如同Document Element中的onEvent一样,他的执行比on添加的handler的要晚。如果那些handler里面,有一个return false了,此onEvent里面是不会执行到的。

5. Hook

Uploader里面的功能被拆分成了好几个widget,由command机制来通信合作。

如下,filepicker在用户选择文件后,直接把结果request出去,然后负责队列的queue widget,监听命令,根据配置项中的accept来决定是否加入队列。

// in file picker
picker.on( 'select', function( files ) {me.owner.request( 'add-file', [ files ]);
});
// in queue picker
Uploader.register({'add-file': 'addFiles'// xxxx
}, {addFiles: function( files ) {// 遍历files中的文件, 过滤掉不满足规则的。}
});

Uploader.regeister方法用来说明,该widget要响应哪些命令,并指定由什么方法来响应。上面的例子,当add-file命令派送时,内部的addFiles成员方法将被执行到,同一个命令,可以指定多次handler, 各个handler会按添加顺序依次执行,且后续的handler,不能被前面的handler截断。

handler里面可以是同步过程,也可以是异步过程。是异步过程时,只需要返回一个promise对象即可。存在异步可能的request调用者会等待此过程结束后才继续。举个例子,webuploader运行在flash模式下时,需要等待flash加载完毕后才能算ready了,此过程为一个异步过程,目前的做法是如下:

// uploader在初始化的时候
me.request( 'init', opts, function() {me.state = 'ready';me.trigger('ready');
});
// filepicker `widget`中的初始化过程。
Uploader.register({'init': 'init'
}, {init: function( opts ) {var deferred = Base.Deferred();// 加载flash// 当flash ready执行deferred.resolve方法。return deferred.promise();}
});

目前webuploader内部有很多种command,在此列出比较重要的几个。

名称 参数 说明
add-file files: File对象或者File数组 用来向队列中添加文件。
before-send-file file: File对象 在文件发送之前request,此时还没有分片(如果配置了分片的话),可以用来做文件整体md5验证。
before-send block: 分片对象 在分片发送之前request,可以用来做分片验证,如果此分片已经上传成功了,可返回一个rejected promise来跳过此分片上传
after-send-file file: File对象 在所有分片都上传完毕后,且没有错误后request,用来做分片验证,此时如果promise被reject,当前文件上传会触发错误。

代码实现:基于SpringBoot和WebUploader

前端页面(WebUploader.html)

<html>
<head><meta charset="utf-8"><title>BigFile-WebUploader</title><link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"><link rel="stylesheet" href="/css/webuploader.css"><script type="text/javascript" src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script><script type="text/javascript" src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script><script type="text/javascript" src="/js/webuploader.js"></script>
</head>
<body>
<div id="uploader" class="wu-example"><div id="thelist" class="uploader-list"></div><div class="btns"><div id="picker">选择大文件</div><button id="ctlBtn" class="btn btn-default">开始上传</button><button id="stopBtn" class="btn btn-default">暂停</button><button id="restart" class="btn btn-default">开始</button></div>
</div>
</body>
<!--业务js文件-->
<script>var $btn = $('#ctlBtn');var $thelist = $('#thelist');var startDate;// HOOK 这个必须要再uploader实例化前面WebUploader.Uploader.register({// 在文件发送之前执行'before-send-file': 'beforeSendFile',// 在文件分片(如果没有启用分片,整个文件被当成一个分片)后,上传之前执行'before-send': 'beforeSend',// 在文件所有分片都上传完后,且服务端没有错误返回后执行"after-send-file": "afterSendFile"}, {beforeSendFile: function (file) {startDate = new Date();console.log("开始上传时间" + startDate)console.log("beforeSendFile");// Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。var deferred = WebUploader.Deferred();//1、计算文件的唯一标记MD5,用于断点续传uploader.md5File(file, 0, 3 * 1024 * 1024).progress(function (percentage) {// 上传进度console.log('上传进度:', percentage);getProgressBar(file, percentage, "MD5", "MD5");}).then(function (val) { // 完成console.log('File MD5 Result:', val);file.md5 = val;file.uid = WebUploader.Base.guid();// 判断文件是否上传过,是否存在分片,断点续传$.ajax({type: "POST",url: "bigfile/check",async: false,data: {fileMd5: val},success: function (data) {var resultCode = data.resultCode;// 秒传if (resultCode == -1) {// 文件已经上传过,忽略上传过程,直接标识上传成功;uploader.skipFile(file);file.pass = true;} else {//文件没有上传过,下标为0//文件上传中断过,返回当前已经上传到的下标file.indexcode = resultCode;}}, error: function () {}});//获取文件信息后进入下一步deferred.resolve();});return deferred.promise();},beforeSend: function (block) {//获取已经上传过的下标var indexchunk = block.file.indexcode;var deferred = WebUploader.Deferred();if (indexchunk > 0) {if (block.chunk > indexchunk) {//分块不存在,重新发送该分块内容deferred.resolve();} else {//分块存在,跳过deferred.reject();}} else {//分块不存在,重新发送该分块内容deferred.resolve();}//返回Deferred的Promise对象。return deferred.promise();}, afterSendFile: function (file) {//如果所有分块上传成功,则通知后台合并分块$.ajax({type: "POST",url: "bigfile/merge",data: {fileName: file.name,fileMd5: file.md5},success: function (data) {}, error: function () {}});}});// 实例化var uploader = WebUploader.create({pick: {id: '#picker',label: '点击选择文件'},duplicate: true,//去重, 根据文件名字、文件大小和最后修改时间来生成hash Keyswf: 'js/Uploader.swf',chunked: true,chunkSize: 10 * 1024 * 1024, // 10M 每个分片的大小限制threads: 3,server: 'bigfile/upload',auto: true,// 禁掉全局的拖拽功能。这样不会出现图片拖进页面的时候,把图片打开。disableGlobalDnd: true,fileNumLimit: 1024,fileSizeLimit: 50 * 1024 * 1024 * 1024,//50G 验证文件总大小是否超出限制, 超出则不允许加入队列fileSingleSizeLimit: 10 * 1024 * 1024 * 1024 //10G 验证单个文件大小是否超出限制, 超出则不允许加入队列});// 当有文件被添加进队列的时候uploader.on('fileQueued', function (file) {$thelist.append('<div id="' + file.id + '" class="item">' +'<h4 class="info">' + file.name + '</h4>' +'<p class="state">等待上传...</p>' +'</div>');$("#stopBtn").click(function () {uploader.stop(true);});$("#restart").click(function () {uploader.upload(file);});});//当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。uploader.onUploadBeforeSend = function (obj, data) {//console.log("onUploadBeforeSend");var file = obj.file;data.md5 = file.md5 || '';data.uid = file.uid;};// 上传中uploader.on('uploadProgress', function (file, percentage) {getProgressBar(file, percentage, "FILE", "上传进度");});// 上传返回结果uploader.on('uploadSuccess', function (file) {var endDate = new Date();console.log("文件上传耗时:" + (endDate - startDate) / 1000 + "s")var text = '已上传';if (file.pass) {text = "文件妙传功能,文件已上传。"}$('#' + file.id).find('p.state').text(text);});uploader.on('uploadError', function (file) {$('#' + file.id).find('p.state').text('上传出错');});uploader.on('uploadComplete', function (file) {// 隐藏进度条fadeOutProgress(file, 'MD5');fadeOutProgress(file, 'FILE');});// 文件上传$btn.on('click', function () {uploader.upload();});/***  生成进度条封装方法* @param file 文件* @param percentage 进度值* @param id_Prefix id前缀* @param titleName 标题名*/function getProgressBar(file, percentage, id_Prefix, titleName) {var $li = $('#' + file.id), $percent = $li.find('#' + id_Prefix + '-progress-bar');// 避免重复创建if (!$percent.length) {$percent = $('<div id="' + id_Prefix + '-progress" class="progress progress-striped active">' +'<div id="' + id_Prefix + '-progress-bar" class="progress-bar" role="progressbar" style="width: 0%">' +'</div>' +'</div>').appendTo($li).find('#' + id_Prefix + '-progress-bar');}var progressPercentage = parseInt(percentage * 100) + '%';$percent.css('width', progressPercentage);$percent.html(titleName + ':' + progressPercentage);}/*** 隐藏进度条* @param file 文件对象* @param id_Prefix id前缀*/function fadeOutProgress(file, id_Prefix) {$('#' + file.id).find('#' + id_Prefix + '-progress').fadeOut();}
</script>
</html>

后端

application.properties

server.port=8081
#server.servlet.context-path=/recordLog#数据源必填项
spring.datasource.url=jdbc:mysql://localhost:3306/test?generateSimpleParameterMetadata=true&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#验证连接的有效性
spring.datasource.secondary.test-while-idle=true
#获取连接时候验证,会影响性能
spring.datasource.secondary.test-on-borrow=false
#在连接归还到连接池时是否测试该连接
spring.datasource.secondary.test-on-return=false
spring.datasource.secondary.validation-query=SELECT 1 FROM DUAL
#空闲连接回收的时间间隔,与test-while-idle一起使用,设置5分钟
spring.datasource.secondary.time-between-eviction-runs-millis=300000
#连接池空闲连接的有效时间 ,设置30分钟
spring.datasource.secondary.min-evictable-idle-time-millis=1800000
spring.datasource.secondary.initial-size=5
#指定连接池中最大的活跃连接数.
spring.datasource.secondary.max-active=50
#指定连接池等待连接返回的最大等待时间,毫秒单位.
spring.datasource.secondary.max-wait=60000
#指定必须保持连接的最小值
spring.datasource.secondary.min-idle=5#Mybatis配置
#mybatis.config-location=classpath:config/sqlMapConfig.xml
mybatis.type-aliases-package=com.example.recordlog.bean
#mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
#引用mybatis-plus-boot-starter写法如下
mybatis-plus.mapper-locations=classpath:mybatis/mapper/*.xml#redis
spring.redis.host=127.0.0.1
spring.redis.port=6380
spring.redis.database=0
spring.redis.password=123456
spring.redis.timeout=10000
# 设置jedis连接池
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.min-idle=20#springboot 默认 multipart.max-file-size大小是1M,max-request-size默认大小是10M ,
# 解决方法:在application.properties文件中配置上传信息
#spring.http.multipart.max-file-size=10240MB
#spring.http.multipart.max-request-size=500MB#注意,此处一定要配置,否则报错
server.tomcat.basedir=./deployer/tomcat

WebConfig静态资源文件配置

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Component
public class WebConfig implements WebMvcConfigurer {/*** 添加静态资源文件,外部可以直接访问地址** @param registry*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/templates/").addResourceLocations("classpath:/static/").addResourceLocations("classpath:/public/");}
}

文件上传大小配置

import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;import javax.servlet.MultipartConfigElement;@Configuration
public class multipartConfig {@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory factory = new MultipartConfigFactory();factory.setMaxRequestSize(DataSize.of(500, DataUnit.MEGABYTES));factory.setMaxFileSize(DataSize.of(500, DataUnit.MEGABYTES));return factory.createMultipartConfig();}
}

controller


import com.example.recordlog.bean.MultipartFileParam;
import com.example.recordlog.tools.JsonResult;
import org.apache.commons.io.FileUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.*;/*** @version V1.0* @Title: 大文件上传* @Description: 断点续传.秒传.分块上传* @author:*/
@RestController
@RequestMapping(value = "/bigfile")
public class BigFileController {private Logger logger = LoggerFactory.getLogger(BigFileController.class);@Autowiredprivate StringRedisTemplate stringRedisTemplate;// @Value("${breakpoint.upload.dir}")private String fileStorePath = "D:/新建文件夹/";/*** @param fileMd5* @Title: 判断文件是否上传过,是否存在分片,断点续传* @MethodName: checkBigFile* @Return com.lovecyy.file.up.example3.vo.JsonResult* @Exception* @Description: 文件已存在,下标为-1* 文件没有上传过,下标为零* 文件上传中断过,返回当前已经上传到的下标*/@RequestMapping(value = "/check", method = RequestMethod.POST)@ResponseBodypublic JsonResult checkBigFile(String fileMd5) {JsonResult jr = new JsonResult();// 秒传File mergeMd5Dir = new File(fileStorePath + "/" + "merge" + "/" + fileMd5);if (mergeMd5Dir.exists()) {mergeMd5Dir.mkdirs();jr.setResultCode(-1);//文件已存在,下标为-1return 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)@ResponseBodypublic void filewebUpload(MultipartFileParam param, HttpServletRequest request) {boolean isMultipart = ServletFileUpload.isMultipartContent(request);// 文件名String fileName = param.getName();// 文件每次分片的下标int chunkIndex = param.getChunk();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)@ResponseBodypublic JsonResult filewebMerge(HttpServletRequest request) {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<File> fileList = new ArrayList<File>(Arrays.asList(childs));Collections.sort(fileList, new Comparator<File>() {@Overridepublic 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;}
}

MultipartFileParam


import org.springframework.web.multipart.MultipartFile;public class MultipartFileParam {// 用户idprivate String uid;//任务IDprivate String id;//总分片数量private int chunks;//当前为第几块分片private int chunk;//当前分片大小private long size = 0L;//文件名private String name;//分片对象private MultipartFile file;// MD5private 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;}@Overridepublic String toString() {return "MultipartFileParam{" +"uid='" + uid + '\'' +", id='" + id + '\'' +", chunks=" + chunks +", chunk=" + chunk +", size=" + size +", name='" + name + '\'' +", file=" + file +", md5='" + md5 + '\'' +'}';}
}

JsonResult


public class JsonResult<T> {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;}
}

项目目录

页面放在templates文件夹下

webuploader.js和webuploader.css

下载 - Web Uploader

页面效果

SpringBoot实现断点续传相关推荐

  1. SpringBoot 分片上传、断点续传、秒传、直传Minio

    最近在学习,在SpringBoot上进行分片上传.断点续传.直接上传到Minio服务器上,中间也遇到的不少坑.自定义minio继承MinioClient来实现分片上传.比较适合初学者. 一.大致的流程 ...

  2. SpringBoot Java实现Http方式分片下载断点续传+实现H5大视频渐进式播放

    项目Git地址:h5-video 一.功能目的 SpringBoot 实现Http分片下载断点续传,从而实现H5页面的大视频播放问题,实现渐进式播放,每次只播放需要播放的内容就可以了,不需要加载整个文 ...

  3. SpringBoot分片上传、断点续传、大文件极速秒传功能(典藏版)

    SpringBoot分片上传.断点续传.大文件极速秒传功能,这篇都帮你搞定!(典藏版) Java研发军团 2023-02-03 21:00 文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可 ...

  4. Springboot断点续传的两种方法

    本文介绍:SpringBoot 中大文件(分片上传)断点续传与极速秒传功能的实现 使用背景介绍 由于涉及到大文件就会出现前端提交到后端很慢或者超时的现象.所以本文讲一下断点 / 分片续传的方案.以下提 ...

  5. 精品分享:基于 SpringBoot + Vue 开发的云盘系统(含大文件断点续传剖析)

    引言 作为开发人员,我们经常需要存储和下载文件,为了使用方便,通常都会将文件存储在云端,市面上使用率最高的云端存储莫过于百度网盘了,但使用别人的东西难免会受到各种各样的限制,必须花钱才会享受到更好的服 ...

  6. springboot+阿里云OSS分片上传、断点续传、秒传

    最近工作中有使用到OSS的分片上传API,整体流程就是前端将大文件进行分割,每个分片大小是1MB,分片个数是:(文件总大小 / 单个分片大小),前端多线程处理上传分片到后端,后端接收到分片后调用OSS ...

  7. springboot 大文件分片上传、断点续传和秒传

    目录 前置说明 获取文件分片 项目流程简述 关键代码解读 表设计SQL 接口测试 测试项目获取地址 前置说明 目前没弄前端,搁置后续再说.前端若打算使用element-ui的el-upload改造分片 ...

  8. layui 文件实现分片上传和 断点续传 和 急速秒传 SpringBoot JAVA

    一丶分片上传 分片上传 slice()这个方法功能是将一个文件切割为一系列特定大小的小数据片,分别将这些小数据片分别上传到服务端,全部上传完后再由服务端将这些小数据片合并成为一个完整的资源. 二 丶 ...

  9. 基于springboot断点续传或分片上传

    前言 在做文件上传,尤其是大文件上传过程中,比如大视频等,经常会碰到这么一种情况,就是用户希望这一次没有上传完,或者中途因为网络原因上传失败了,下一次继续上传的时候可以接着上次没有传完的地方继续上传, ...

最新文章

  1. 进阶学习(3.1) Simple Factory Pattern 简单工厂模式
  2. 验证OpenStack安装
  3. 利用ionic3进行上一行和左一行不动,中间移动的功能
  4. Dapper的正确使用姿势
  5. consul的安装搭建
  6. gentoo php,gentoo下的use原始配置让我安装php折腾好久。
  7. Visio画图删去四周白边
  8. 数据安全-整体解决方案
  9. 重装电脑xp系统找不到服务器,技术员为你解答XP系统提示“没有启动服务器服务”的修复方案...
  10. html中加分割线,多种HTML分割线
  11. 计算机网络配置——静态路由的配置
  12. idea或者goland更改主题颜色背景颜色
  13. 【IT之路】连接MySQL遇到ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using passwor:yes)问题
  14. Intel无线网卡AX210驱动bug
  15. deepnode软件下载地址_安卓苹果软件下载地址恢复
  16. 图片批量改名,改后缀
  17. PCL点云库可视化常用函数与经验总结
  18. Matlab2018a崩溃解决的办法No sandbox or build area path
  19. Mysql 中 “必知” 的单行处理函数
  20. C++中的reverse()函数

热门文章

  1. IBM带库加磁带操作
  2. Background背景
  3. 网际快车FlashGet1.65
  4. frontpage css,在Frontpage 中定义网页CSS样式
  5. 【web++_第四阶段_精美网页设计专题篇_1】
  6. Android动态创建快捷方式
  7. 因为计算机x3daudio1_7.dll,x3daudio1_7.dll
  8. 零知模块新品上线 SPI接口CAN总线模块扩展板 附使用示例
  9. 从0构建AI推荐系统demo(数据准备)
  10. 数字孪生 3D 科技馆的科学传播新模式