学习链接
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请求头中没有结束位置, 取文件大小-1if (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));// 响应状态码206response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);// Content-Range响应头格式为:Content-Range: bytes 0-8055/9000response.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'// 分片下载大小 5MBconst 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 * chunkSizelet 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 {++chunkIdxdownloadChunkFile(chunkIdx)}})}downloadChunkFile(0)})}}
}
</script><style>
.gap {padding: 10px;
}
</style>