学习链接
SpringBoot+vue文件上传&下载&预览&大文件分片上传&文件上传进度
Vue+SpringBoot实现文件的分片下载
video标签学习 & xgplayer视频播放器分段播放mp4(Range请求交互过程可以参考这个里面的截图)
代码
FileController
这里面的代码实现,完全可以参考ResourceHttpRequestHandler#handleRequest
@RestController
public class FileController {
private static final int BUFFER_SIZE = 4 * 1024;
@RequestMapping(path = "chunkdownload", method = {RequestMethod.HEAD, RequestMethod.POST})
public void chunkdownload(HttpServletRequest request, HttpServletResponse response) throws Exception {
File file = new File("D:/usr/test/demo.mp4");
// 文件总大小
long fileSize = file.length();
// 设置 Content-Type 和 相关响应头
// (这里分片下载响应头设置, 其实可以参考ResourceHttpRequestHandler#handleRequest,
// 和 video标签学习 & xgplayer视频播放器分段播放mp4 - https://blog.csdn.net/qq_16992475/article/details/130945997)
response.setContentType("application/octect-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
response.setHeader("Accept-Ranges", "bytes");
// 检查请求头中是否有Range请求头,
// (可参考:video标签学习 & xgplayer视频播放器分段播放mp4 - https://blog.csdn.net/qq_16992475/article/details/130945997)
String rangeHeader = request.getHeader("Range");
// 没有Range请求头, 则下载整个文件
if (rangeHeader == null) {
response.setHeader("Content-Length", String.valueOf(fileSize));
InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream();
// 字节缓冲数组
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
// 读取多少, 写多少, 直到读取完毕为止
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
in.close();
out.close();
} else {
// 分片下载
// (可参考: 参考ResourceHttpRequestHandler#handleRequest中的做法)
// 开始索引
long start = 0;
// 结束索引
long end = fileSize - 1;
// 获取Range请求头的范围, 格式为:Range: bytes=0-8055,
// (其中可能没有结束位置, 若没有位置, 取文件大小-1)
String[] range = rangeHeader.split("=")[1].split("-");
// 如果Range请求头中没有结束位置, 取文件大小-1
if (range.length == 1) {
start = Long.parseLong(range[0]);
end = fileSize - 1;
} else {
// 解析开始位置 和 结束位置
start = Long.parseLong(range[0]);
end = Long.parseLong(range[1]);
}
// 此次要写出的数据
long contentLength = end - start + 1;
// 返回头里存放每次读取的开始和结束字节
response.setHeader("Content-Length", String.valueOf(contentLength));
// 响应状态码206
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
// Content-Range响应头格式为:Content-Range: bytes 0-8055/9000
response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream();
// 跳到第start字节
in.skip(start);
// 字节缓冲数组
byte[] buffer = new byte[BUFFER_SIZE];
// 读取的字节数量
int bytesRead = -1;
// 写出的字节数量
long bytesWritten = 0;
while ((bytesRead = in.read(buffer)) != -1) {
// 如果 已写入的数据 + 当前已读到的数据 超过了 此次要写出的数据, 则只能写入请求范围内的数据
if (bytesWritten + bytesRead > contentLength) {
out.write(buffer, 0, (int) (contentLength - bytesWritten));
break;
} else {
out.write(buffer, 0, bytesRead);
bytesWritten += bytesRead;
}
}
in.close();
out.close();
}
}
}
ChunkDownload.vue
- 先发1个head请求,获取到文件的大小
- 再发post请求,获取每个分片(其中为了简单理解,就不引入async-await的使用了)
- 将获取的每个分片组合为单个文件
<template>
<div class="gap">
<el-button @click="downloadChunks">分片下载demo.mp4</el-button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'ChunkDownload',
components: {
},
methods: {
downloadChunks() {
const chunkdownloadUrl = 'http://localhost:8085/chunkdownload'
// 分片下载大小 5MB
const chunkSize = 1024 * 1024 * 5;
// 文件总大小(需要请求后端获得)
let fileSize = 0;
axios
.head(chunkdownloadUrl)
.then(res => {
// 定义 存储所有的分片的数组
let chunks = [];
// 获取文件总大小
fileSize = res.headers['content-length']
// 计算分片数量
const chunksNum = Math.ceil(fileSize / chunkSize)
// 定义下载文件分片的方法
function downloadChunkFile(chunkIdx) {
if (chunkIdx >= chunksNum) {
alert('分片索引不可超过分片数量')
return
}
let start = chunkIdx * chunkSize
let end = Math.min(start + chunkSize - 1, fileSize - 1)
const range = `bytes=${start}-${end}`;
axios({
url: chunkdownloadUrl,
method: 'post',
headers: {
Range: range
},
responseType: 'arraybuffer'
}).then(response => {
chunks.push(response.data)
if(chunkIdx == chunksNum - 1) {
// 下载好了
console.log(chunks, 'chunks');
// 组合chunks到单个文件
const blob = new Blob(chunks);
console.log(blob, 'blob');
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'demo.mp4';
link.click();
return
} else {
++chunkIdx
downloadChunkFile(chunkIdx)
}
})
}
downloadChunkFile(0)
})
}
}
}
</script>
<style>
.gap {
padding: 10px;
}
</style>