大家好,我是一安,今天聊一下springboot如何实现大文件上传下载、分片、断点续传
实现思路
-
分片:按照自定义缓冲区大小,将大文件分成多个小文件片段。
-
断点续传:根据分片数量,给每个小文件通过循环起对应名称,当文件下载中断在续传时,判断小文件名称若存在则不存了,此时还需要判断文件若不是最后一个分片则大小为缓冲区固定大小,若没达到则证明小文件没传完需要重新传输。
-
合并:下载时通过线程池创建任务进行下载或上传、当判断最后一个分片传完时,调用合并方法,根据之前定义的文件名称顺序进行合并,肯能出现最后一个分片传完,之前分片未传完的情况,需要使用while循环进行判断,多文件未传输完,则等待一会继续判断。
-
大文件秒传:实际上是根据文件名称区一个唯一的md5值存储,传文件时进行判断,若存在则不传。
创建springboot项目,添加依赖
@RestController
public class DownloadClient {
private final static long per_page = 1024l*1024l*50l;
//分片存储临时目录 当分片下载完后在目录中找到文件合并
private final static String down_path="D:\File";
//多线程下载
ExecutorService pool = Executors.newFixedThreadPool(10);
//文件大小 分片数量 文件名称
//使用探测 获取变量
//使用多线程分片下载
//最后一个分片下载完 开始合并
@RequestMapping("/downloadFile")
public String downloadFile() throws IOException {
FileInfo fileInfo = download(0,10,-1,null);
if (fileInfo!= null){
long pages = fileInfo.fSize/per_page;
for (int i = 0; i <= pages; i++) {
pool.submit(new Download(i*per_page,(i+1)*per_page-1,i,fileInfo.fName));
}
}
return "成功";
}
class Download implements Runnable{
long start;
long end;
long page;
String fName;
public Download(long start, long end, long page, String fName) {
this.start = start;
this.end = end;
this.page = page;
this.fName = fName;
}
@Override
public void run() {
try {
FileInfo fileInfo = download(start,end,page,fName);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//返回文件名 跟大小
private FileInfo download(long start,long end,long page,String fName) throws IOException {
//断点下载 文件存在不需要下载
File file = new File(down_path, page + "-" + fName);
//探测必须放行 若下载分片只下载一半就锻炼需要重新下载所以需要判断文件是否完整
if (file.exists()&&page != -1&&file.length()==per_page){
return null;
}
//需要知道 开始-结束 = 分片大小
HttpClient client = HttpClients.createDefault();
//httpclient进行请求
HttpGet httpGet = new HttpGet("http://127.0.0.1:8080/down");
//告诉服务端做分片下载
httpGet.setHeader("Range","bytes="+start+"-"+end);
HttpResponse response = client.execute(httpGet);
String fSize = response.getFirstHeader("fSize").getValue();
fName= URLDecoder.decode(response.getFirstHeader("fName").getValue(),"utf-8");
HttpEntity entity = response.getEntity();//获取文件流对象
InputStream is = entity.getContent();
//临时存储分片文件
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];//定义缓冲区
int ch;
while ((ch = is.read(buffer)) != -1){
fos.write(buffer,0,ch);
}
is.close();
fos.flush();
fos.close();
//判断是不是最后一个分片
if (end-Long.valueOf(fSize)>0){
//合并
try {
mergeFile(fName,page);
} catch (Exception e) {
e.printStackTrace();
}
}
return new FileInfo(Long.valueOf(fSize),fName);
}
private void mergeFile(String fName, long page) throws Exception {
//归并文件位置
File file = new File(down_path, fName);
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
for (int i = 0; i <= page; i++) {
File tempFile = new File(down_path, i + "-" + fName);
//分片没下载或者没下载完需要等待
while (!file.exists()||(i!=page&&tempFile.length()<per_page)){
Thread.sleep(100);
}
byte[] bytes = FileUtils.readFileToByteArray(tempFile);
os.write(bytes);
os.flush();
tempFile.delete();
}
File file1 = new File(down_path, -1 + "-null");
file1.delete();
os.flush();
os.close();
}
//使用内部类实现
class FileInfo{
long fSize;
String fName;
public FileInfo(long fSize, String fName) {
this.fSize = fSize;
this.fName = fName;
}
}
}
作者:远走与梦游
https://blog.csdn.net/weixin_5221055
最后说一句(别白嫖,求关注)
如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!
本篇文章来源于微信公众号: 一安未来
微信扫描下方的二维码阅读本文
Comments NOTHING