1 大文件上传(支持断点续传)
1.1 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
<link rel="stylesheet" type="text/css" href="webuploader.css">
<script src="jquery.js"></script>
<script src="webuploader.js"></script>
<style>
#upload-container {
width: 500px;
height: 500px;
background: lightskyblue;
padding-bottom: 10px;
}
</style>
</head>
<body>
<div id="upload-container">
<span>文件拖拽上传</span>
</div>
<div id="upload-list"></div>
<button id="picker" style="margin-top: 50px">点击上传</button>
</body>
<script>
$('#upload-container').click(function (event) {
$("#picker").find('input').click();
});
const uploader = WebUploader.create({
auto: true,
swf: 'Uploader.swf',
server: 'http://localhost:9000/upload',
dnd: '#upload-container',
pick: '#picker',
multiple: true,
chunked: true,
threads: 20,
method: 'POST',
fileSizeLimit: 1024 * 1024 * 1024 * 100,
fileSingleSizeLimit: 1024 * 1024 * 1024 * 5,
fileVal: 'upload'
});
uploader.on("beforeFileQueued", function (file) {
console.log(file);
});
uploader.on('fileQueued', function (file) {
console.log(file.ext);
console.log(file.size);
console.log(file.name);
const html = '<div class="upload-item"><span>文件名:' + file.name + '</span><span data-file_id="' + file.id + '"class="btn-delete">删除</span><span data-file_id="' + file.id + '"class="btn-retry">重试</span><div class="percentage ' + file.id + '" style="width: 0%;"></div></div>';
$('#upload-list').append(html);
uploader.md5File(file)
.progress(function (percentage) {
console.log('Percentage:', percentage);
})
.then(function (val) {
console.log('md5 result', val);
});
});
</script>
</html>
1.2 后端
public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletFileUpload servletFileUpload = getServletFileUpload();
List<FileItem> items = servletFileUpload.parseRequest(request);
UploadFileInfoDto uploadFileInfoDto = getFileDataDto(items);
writeTempFile(items, uploadFileInfoDto);
mergeFile(uploadFileInfoDto);
response.setCharacterEncoding(UTF_8);
response.getWriter().write("上传成功");
}
private void mergeFile(UploadFileInfoDto uploadFileInfoDto) throws IOException, InterruptedException {
Integer currentChunk = uploadFileInfoDto.getCurrentChunk();
Integer chunks = uploadFileInfoDto.getChunks();
if (currentChunk != null && chunks != null && currentChunk.equals(chunks - 1)) {
File tempFile = new File(uploadPath, uploadFileInfoDto.getFileName());
try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(tempFile))) {
for (int i = 0; i < chunks; i++) {
File file = new File(uploadPath, i + "_" + uploadFileInfoDto.getFileName());
while (!file.exists()) {
Thread.sleep(100);
}
byte[] bytes = FileUtils.readFileToByteArray(file);
os.write(bytes);
os.flush();
file.delete();
}
os.flush();
}
}
}
private void writeTempFile(List<FileItem> items, UploadFileInfoDto uploadFileInfoDto) throws Exception {
for (FileItem item : items) {
if (!item.isFormField()) {
String tempFileName = uploadFileInfoDto.getFileName();
if (StringUtils.isNotBlank(tempFileName)) {
if (uploadFileInfoDto.getCurrentChunk() != null) {
tempFileName = uploadFileInfoDto.getCurrentChunk() + "_" + uploadFileInfoDto.getFileName();
}
File tempFile = new File(uploadPath, tempFileName);
if (!tempFile.exists()) {
item.write(tempFile);
}
}
}
}
}
private UploadFileInfoDto getFileDataDto(List<FileItem> items) throws UnsupportedEncodingException {
UploadFileInfoDto rs = new UploadFileInfoDto();
for (FileItem item : items) {
if (item.isFormField()) {
if ("chunk".equals(item.getFieldName())) {
rs.setCurrentChunk(Integer.parseInt(item.getString(UTF_8)));
}
if ("chunks".equals(item.getFieldName())) {
rs.setChunks(Integer.parseInt(item.getString(UTF_8)));
}
if ("name".equals(item.getFieldName())) {
rs.setFileName(item.getString(UTF_8));
}
}
}
return rs;
}
private ServletFileUpload getServletFileUpload() {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024);
factory.setRepository(new File(uploadPath));
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(5 * 1024 * 1024 * 1024L);
upload.setSizeMax(100 * 1024 * 1024 * 1024L);
return upload;
}
1.3 测试
2 文件下载
2.1 HTTP Range 信息
Range 请求头: Range: bytes=start-end
Range: bytes=10- //:从第10个字节开始到最后一个字节的数据
Range:bytes=20-39 //:从第20个字节到第39个字节之间的数据
Content-Range 响应头 :
#表示服务器返回了前(0-10)个字节的数据,总共3000字节的数据。
Content-Range:bytes 0-10/3000
Content-Length 资源的长度:
#表示服务器响应了11个字节的数据
Content-Length:11
2.2 代码
public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
File file = new File(downloadFile);
DownloadFileInfoDto downloadFileInfoDto = getDownloadFileInfoDto(file.length(), request, response);
setResponse(response, file.getName(), downloadFileInfoDto);
try (InputStream is = new BufferedInputStream(new FileInputStream(file));
OutputStream os = new BufferedOutputStream(response.getOutputStream())) {
is.skip(downloadFileInfoDto.getPos());
byte[] buffer = new byte[1024];
long sum = 0;
while (sum < downloadFileInfoDto.getRangeLength()) {
int length = is.read(buffer, 0, (downloadFileInfoDto.getRangeLength() - sum) <= buffer.length ? (int) (downloadFileInfoDto.getRangeLength() - sum) : buffer.length);
sum = sum + length;
os.write(buffer, 0, length);
}
}
}
private DownloadFileInfoDto getDownloadFileInfoDto(long fSize, HttpServletRequest request, HttpServletResponse response) {
long pos = 0;
long last = fSize - 1;
if (request.getHeader("Range") != null) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
String numRange = request.getHeader("Range").replace("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());
}
}
long rangeLength = last - pos + 1;
String contentRange = "bytes " + pos + "-" + last + "/" + fSize;
return new DownloadFileInfoDto(fSize, pos, last, rangeLength, contentRange);
}
private void setResponse(HttpServletResponse response, String fileName, DownloadFileInfoDto downloadFileInfoDto) throws UnsupportedEncodingException {
response.setCharacterEncoding(UTF_8);
response.setContentType("application/x-download");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, UTF_8));
response.setHeader("Accept-Range", "bytes");
response.setHeader("fSize", String.valueOf(downloadFileInfoDto.getFSize()));
response.setHeader("fName", fileName);
response.setHeader("Content-Range", downloadFileInfoDto.getContentRange());
response.setHeader("Content-Length", String.valueOf(downloadFileInfoDto.getRangeLength()));
}
2.3 测试
3 分片下载
3.1 CODE
@Value("${file.download-path}")
private String downloadPath;
private static final Long PER_SLICE = 1024 * 1024 * 5L;
private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static final String RANGE = "Range";
private FileInfoDto sliceDownload(long start, long end, long page, String fName) throws IOException {
File file = new File(downloadPath, page + "-" + fName);
if (file.exists() && page != -1 && file.length() == PER_SLICE) {
return null;
}
HttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://localhost:9000/download");
httpGet.setHeader(RANGE, "bytes=" + start + "-" + end);
HttpResponse httpResponse = client.execute(httpGet);
String fSize = httpResponse.getFirstHeader("fSize").getValue();
fName = URLDecoder.decode(httpResponse.getFirstHeader("fName").getValue(), UTF_8);
HttpEntity entity = httpResponse.getEntity();
try (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);
}
fos.flush();
}
if (end - Long.parseLong(fSize) > 0) {
mergeFile(fName, page);
}
return new FileInfoDto(Long.parseLong(fSize), fName);
}
private void mergeFile(String fName, long page) throws IOException {
File file = new File(downloadPath, fName);
try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
for (int i = 0; i <= page; i++) {
File tempFile = new File(downloadPath, i + "-" + fName);
while (!tempFile.exists() || (i != page && tempFile.length() < PER_SLICE)) {
Thread.sleep(100);
}
byte[] bytes = FileUtils.readFileToByteArray(tempFile);
os.write(bytes);
os.flush();
tempFile.delete();
}
File f = new File(downloadPath, "-1" + "-null");
if (f.exists()) {
f.delete();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sliceDownload() throws IOException {
FileInfoDto fileInfoDto = sliceDownload(1, 10, -1, null);
if (fileInfoDto != null) {
long pages = fileInfoDto.getFileSize() / PER_SLICE;
for (long i = 0; i <= pages; i++) {
long start = i * PER_SLICE;
long end = (i + 1) * PER_SLICE - 1;
executorService.execute(new SliceDownloadRunnable(start, end, i, fileInfoDto.getFileName()));
}
}
}
private class SliceDownloadRunnable implements Runnable {
private final long start;
private final long end;
private final long page;
private final String fName;
private SliceDownloadRunnable(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 {
sliceDownload(start, end, page, fName);
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2 测试
PS: Gitee 地址
https://gitee.com/zhurongsheng/spring-file
|