分片下载大文件
package com.jero.modules.system.controller;
import com.jero.common.aspect.annotation.AutoLog;
import com.jero.modules.oss.service.IOSSFileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
@Slf4j(topic = "DownloadController")
@RestController
@Api("多线程分片下载大文件")
@RequestMapping("/file/download")
public class DownloadController {
@Resource
private IOSSFileService ossFileService;
public static final String HEADER_RANGE = "Range";
@GetMapping(value = "/range/{fileId}",produces ="application/octet-stream" )
@AutoLog(value = "文件-分片下载大文件-浏览器直接下载")
@ApiOperation(value="文件-分片下载大文件-浏览器直接下载", notes="文件-分片上传-分片下载大文件-浏览器直接下载")
public String downloadFileByRange(@PathVariable("fileId")String fileId,
@RequestParam(value = "delay",defaultValue = "100") Long delayFactor,
HttpServletRequest request, HttpServletResponse response) {
response.addHeader("Access-Control-Expose-Headers", "Accept-Range");
response.addHeader("Access-Control-Expose-Headers", "fSize");
response.addHeader("Access-Control-Expose-Headers", "fName");
response.addHeader("Access-Control-Expose-Headers", "Content-Length");
File file = getDownloadFile(fileId);
if(file == null) {
return "Download fail, file is not exists";
}
log.info("准备下载文件,文件名为 {}", file.getName());
InputStream is = null;
OutputStream os = null;
try {
long fSize = file.length();
log.info("文件总大小为 {}", fSize);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/x-download");
String fileName = URLEncoder.encode(file.getName(), "UTF-8");
response.addHeader("Content-Disposition", "attachement;fileName=" + fileName);
response.addHeader("Accept-Range", "bytes");
response.addHeader("fSize", String.valueOf(fSize));
response.addHeader("fName", fileName);
long pos = 0, last = fSize - 1, sum = 0;
if (null != request.getHeader(HEADER_RANGE)) {
log.info("分片下载中,range信息为 {}", request.getHeader(HEADER_RANGE));
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
String numRange = request.getHeader(HEADER_RANGE).replaceAll("bytes=", "");
String[] strRange = numRange.split("-");
if (strRange.length == 2) {
pos = Long.parseLong(strRange[0].trim());
last = Long.parseLong(strRange[1].trim());
if (last > fSize - 1) {
last = fSize - 1;
}
} else {
pos = Long.parseLong(numRange.replace("-", "").trim());
}
} else {
log.warn("客户端本次进行非分片下载");
}
long rangeLength = last - pos + 1;
String contentRange = new StringBuffer("bytes ").append(pos).append("-").append(last)
.append("/").append(fSize).toString();
response.setHeader("Content-Range", contentRange);
response.setHeader("Content-Length", String.valueOf(rangeLength));
os = new BufferedOutputStream(response.getOutputStream());
is = new BufferedInputStream(new FileInputStream(file));
is.skip(pos);
byte[] buffer = new byte[1024*1024];
int length = 0;
int j = 0;
while (sum < rangeLength) {
long readLength = rangeLength - sum;
if (readLength > buffer.length) {
readLength = buffer.length;
}
length = is.read(buffer, 0, (int) readLength);
sum = sum + length;
os.write(buffer, 0, length);
j++;
}
log.info("文件下载完毕");
return "Download file success";
} catch (IOException e) {
log.warn("下载异常终止:{}", e.getMessage());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
log.warn("关闭文件流异常:{}", e.getMessage());
}
}
return "Download fail";
}
private File getDownloadFile(String fileId) {
String url = ossFileService.getById(fileId).getUrl();
return new File(url);
}
}
DownloadController.java
多线程下载器客户端
package com.jero.modules.system.controller;
import com.jero.common.util.ServletUtil;
import com.jero.modules.system.dto.FileInfoDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j(topic = "DownloadClient")
@RestController
@RequestMapping("/file/client")
public class DownloadClient {
private static String HTTP = "http://localhost:7001/file/download/range/";
private final static long PER_PAGE = 1024 * 1024 * 10L;
private final static String DOWN_PATH = "D:\\fileItem";
ExecutorService pool = Executors.newFixedThreadPool(4);
@GetMapping(value = "/download",produces ="application/octet-stream")
public void downloadFile(@RequestParam("fileId") String fileId,HttpServletRequest request, HttpServletResponse response) {
FileInfoDTO downloadFileInfo = new FileInfoDTO();
downloadFileInfo.setDownloadUrl(HTTP+fileId);
FileInfoDTO fileInfo = download(downloadFileInfo.getDownloadUrl(), 0, 10, -1, null);
if (fileInfo != null) {
long pages = fileInfo.getfSize() / PER_PAGE;
for (long i = 0; i <= pages; i++) {
long start = i * PER_PAGE;
long end = (i + 1) * PER_PAGE - 1;
pool.submit(new DownLoadTask(downloadFileInfo.getDownloadUrl(), start, end, i, fileInfo.getfName()));
}
}
}
class DownLoadTask implements Runnable {
private String downloadUrl;
private long start;
private long end;
private long page;
private String fName;
@Override
public void run() {
FileInfoDTO download = download(downloadUrl, start, end, page, fName);
log.info("文件名 {} 分片码 {} 对应的分片内容已经下载完毕", download.getfName(), page);
}
public DownLoadTask(String downloadUrl, long start, long end, long page, String fName) {
this.start = start;
this.end = end;
this.page = page;
this.fName = fName;
this.downloadUrl = downloadUrl;
}
}
public FileInfoDTO download(String downloadUrl, long start, long end, long page, String fName) {
if (page == -1) {
log.info("下载探测文件:{}", fName);
} else {
log.info("下载分片文件:{},分片序号 {}", fName, page);
}
File file = new File(DOWN_PATH, page + "-" + fName);
if (file.exists() && page != -1 && file.length() == PER_PAGE) {
log.info("此分片文件 {} 已存在,免下载", page);
return null;
}
long fSize = 0L;
try {
HttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.setHeader("Range", "bytes=" + start + "-" + end);
httpGet.setHeader("X-Access-Token",ServletUtil.getRequest().getHeader("X-Access-Token"));
HttpResponse response = client.execute(httpGet);
if (response.getFirstHeader("fSize") == null) {
URL url = new URL(downloadUrl);
URLConnection conn = url.openConnection();
int fileSize = conn.getContentLength();
fSize = fileSize;
} else {
fSize = Long.valueOf(response.getFirstHeader("fSize").getValue());
}
log.info("将要下载的文件大小为:{} bytes, headerFields {}", fSize);
if (response.getFirstHeader("fName") == null) {
fName = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1);
} else {
fName = URLDecoder.decode(response.getFirstHeader("fName").getValue(), "UTF-8");
}
HttpEntity entity = response.getEntity();
InputStream is = null;
FileOutputStream fos = null;
try {
is = entity.getContent();
fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int ch;
while ((ch = is.read(buffer)) != -1) {
fos.write(buffer, 0, ch);
}
} catch (UnsupportedOperationException e) {
e.printStackTrace();
} finally {
if (is != null) {
is.close();
}
if (fos != null) {
fos.flush();
}
if (fos != null) {
fos.close();
}
}
if (end - fSize >= 0) {
log.info("最后一个分片文件已下载完毕,准备合并文件");
mergeFile(fName, page, ServletUtil.getRequest(),ServletUtil.getResponse());
}
} catch (IOException e) {
e.printStackTrace();
}
return new FileInfoDTO(fSize, fName);
}
private void mergeFile(String fName, long page,HttpServletRequest request, HttpServletResponse response) {
File file = new File(DOWN_PATH, fName);
try {
FileOutputStream out = null;
BufferedOutputStream os = null;
try {
out = new FileOutputStream(file);
try {
os = new BufferedOutputStream(out);
for (int i = 0; i <= page; i++) {
File tempFile = new File(DOWN_PATH, i + "-" + fName);
while (!tempFile.exists() || (i != page && tempFile.length() < PER_PAGE)) {
try {
Thread.sleep(100);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
Thread.currentThread().interrupt();
}
}
log.info("所有分片文件已下载完毕,合并文件中,合并分片 {}", i);
byte[] bytes = FileUtils.readFileToByteArray(tempFile);
os.write(bytes);
os.flush();
tempFile.delete();
log.info("分片 {} 已合并完成,进行清理", i);
}
log.info("文件合并结束,最后删除探测文件");
File tanceFile = new File(DOWN_PATH, "-1-null");
tanceFile.delete();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
os.close();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
if (os != null) {
os.flush();
}
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
log.info("恭喜您,文件{}下载完成!", fName);
}
}
DownloadClient.java