因为一个政府项目需要上传影像tif文件,文件大小超过20g,以往的上传肯定是不行的,一旦失败就要从头开始,所以了解了分片上传。
分片上传:
分片上传就是将文件分成一个个小块去上传,上传完成之后再将这些文件按顺序合成起来。这里我用来进行分片的组件是百度开源的webuploader,这个组件依赖于jquery
断点上传:
断点上传就是上传分片之前检查分片文件是否存在,如果存在就不再上传。
上后端代码:
`private static final String tifPath = “D:/uploadTiff”;
public static Map<String, Integer> riskmap = new HashMap<String, Integer>();
public static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(6);
@RequestMapping(value = “uploadTiff”)
@Transactional(rollbackFor = Exception.class)
public ResResult uploadTiff(@RequestParam(“file”) MultipartFile file, HttpServletRequest request,
HttpServletResponse response) throws IOException {
//判断文件是否为空
if(file.isEmpty()){
return new ResResult(StatusCode.REQUEST_CODE_BAD,"空文件");
}
//接收request参数
Integer chunk = MyUtil.convertToInt(request.getParameter("chunk"),0);//当前分片
Integer chunks = MyUtil.convertToInt(request.getParameter("chunks"),0);//总分片数
String name = MyUtil.convertToString(request.getParameter("name"));//文件名字
System.out.println("请求:"+chunk);
System.out.println("总分片:"+chunks);
try {
String uploadDir = tifPath;
File fileDir = new File(uploadDir);
//创建目录
if (!fileDir.exists()) {
fileDir.mkdirs();
}
//获取上次失败位置
Integer hechengnum = MyUtil.convertToInt(redisTemplate.opsForValue().get("uploadfile"+name),0);
System.out.println("上次合成到:"+hechengnum);
File fileCache = new File(uploadDir,name);
if(fileCache.exists()){//文件存在
if (hechengnum>0&&hechengnum>chunk){
System.out.println("已经合成过");
return new AjaxResult("","文件已上传");
}
}
//分片文件名称 以 0_xxxx.xxx 1_xxxx.xxx的形式
String tempName = name;
if(chunks>0){
tempName = chunk+"_"+name;
}
File tempFile = new File(uploadDir,tempName);
//存储文件路径
String uploadFilePath = uploadDir+tempName;
if (!tempFile.exists()){//文件不存在(分片没上传),则上传分片
File uploadFile = new File(uploadFilePath);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(uploadFile));
out.write(file.getBytes());
out.flush();
}
//开启线程去合成
//riskmap.get(name) 手动简单锁一下,保证只有一个线程在合成
if(riskmap.get(name)==null){
riskmap.put(name,1);
fixedThreadPool.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
try{
File addFile = new File(uploadDir,name);
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(addFile));
List<File> flist = new ArrayList<>();
//循环每一个分片
for (int i = 0; i < chunks; i++) {
//上次合成的位置 已合成的则不再合成
if (hechengnum >0&& hechengnum >i){
continue;
}
File file1 = new File(uploadDir,i+"_"+name);
System.out.println(i);
System.out.println(file1.exists());
Integer returnnum = 0;
while (!file1.exists()){//分片未下载完,等他下载完,每秒查一次,300秒都没有则失败
System.out.println("等待");
if(returnnum>300){//考虑文件比较大,所以等的时间比较长
//记录失败位置
redisTemplate.opsForValue().set("uploadfile"+name,i+"");
}
returnnum++;
Thread.sleep(1000);
}
//将字节写入目标文件
byte[] bytes = FileUtils.readFileToByteArray(file1);
os.write(bytes);
os.flush();
flist.add(file1);
}
os.flush();
System.gc();//gc,回收掉堆内存的垃圾file对象,不然删不掉
Thread.sleep(1000);
//合成完删除分片文件
for (File f:flist) {
f.delete();
}
}finally {
riskmap.put(name,null);
}
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
return new AjaxResult("","上传成功");
}`
开始我是在最后一个分片上传完后,再进行合成,但是因为文件本身比较大,非常的耗时,前端就timeout了,所以就改成开始就合成
前端代码:
这里用的是webupload组件,只是做了一个简单的演示测试功能。不了解的兄弟可以去官网查看文档http://fex.baidu.com/webuploader/
需要下载他的依赖引入项目,下载他官网那个压缩版本即可。
测试目录如上图,他是需要jq的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<!--引入JS-->
<script type="text/javascript" src="./jq.js"></script>
<!--引入CSS-->
<link rel="stylesheet" type="text/css" href="./webuploader-0.1.5/webuploader.css">
<!--引入JS-->
<script type="text/javascript" src="./webuploader-0.1.5/webuploader.js"></script>
<div id="uploader" class="wu-example">
<!--用来存放文件信息-->
<div id="thelist" class="uploader-list"></div>
<div class="btns">
<div id="upload-container"><span>点击选择文件</span></div>
<button id="picker" class="btn btn-default">开始上传</button>
</div>
</div>
<!-- <div id="upload-container"><span>点击选择文件</span></div>
<div id="upload-list"></div>
<button id="picker">点击上传</button> -->
<script type="text/javascript">
$("#upload-container").click(function(event){
$("#picker").find("input").click();
});
// 初始化Web Uploader
var uploader = WebUploader.create({
// 选完文件后,是否自动上传。
auto: true,
// swf文件路径
swf: './webuploader-0.1.5/webuploader-0.1.5/Uploader.swf',
// 文件接收服务端。
server: 'http://localhost:8088/examine/geoServer/uploadTiff',
dnd: "#upload-container",
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick: '#picker',
multiple: true,
chunked: true,
chunkSize:100 * 1024 * 1024, //每片100M
threads: 10, //开启十个线程上传
method: "POST",
fileVal: "file"//文件参数名称 与@RequestParam("file")一致
/* // 只允许选择图片文件。
accept: {
title: 'Images',
extensions: 'tif',
mimeTypes: 'image/*'
} */
});
// 当有文件被添加进队列的时候
uploader.on( 'fileQueued', function( file ) {
$("#thelist").append( '<div id="' + file.id + '" class="item">' +
'<h4 class="info">' + file.name + '</h4>' +
'<p class="state">等待上传...</p>' +
'</div>' );
uploader.md5File( file )
// 及时显示进度
.progress(function(percentage) {
console.log('Percentage:', percentage);
})
// 完成
.then(function(val) {
console.log('md5 result:', val);
});
});
// 文件上传过程中创建进度条实时显示。
uploader.on( 'uploadProgress', function( file, percentage ) {
var $li = $( '#'+file.id ),
$percent = $li.find('.progress .progress-bar');
// 避免重复创建
if ( !$percent.length ) {
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'</div>' +
'</div>').appendTo( $li ).find('.progress-bar');
}
$li.find('p.state').text('上传中');
$percent.css( 'width', percentage * 100 + '%' );
});
uploader.on( 'uploadSuccess', function( file ) {
$( '#'+file.id ).find('p.state').text('已上传');
});
uploader.on( 'uploadError', function( file ) {
$( '#'+file.id ).find('p.state').text('上传出错');
});
uploader.on( 'uploadComplete', function( file ) {
$( '#'+file.id ).find('.progress').fadeOut();
});
</script>
</body>
</html>
秒传:
我这里没有集成秒传功能,其实也很简单,就是上传之前拿文件的md5值去服务端查一下有没有,展示的html代码里也有一个打印了md5值的,在上传分片之前带上md5值调一下接口查一下。然后后端上传完文件可以将md5值和文件路径存起来,redis、mysql都可以,找到有一样的md5值直接算上传完成
结尾:
到这我的分享就结束了,可能代码有些纰漏,有什么问题欢迎大家留言。谢谢大家的阅读。