SpringBoot+vue 大文件分片下载

学习链接

SpringBoot+vue文件上传&下载&预览&大文件分片上传&文件上传进度

Blob & File & FileReader & ArrayBuffer

Vue+SpringBoot实现文件的分片下载

video标签学习 & xgplayer视频播放器分段播放mp4(Range请求交互过程可以参考这个里面的截图)

【java】java实现大文件的分片上传与下载(springboot+vue3)(代码已fork至本地)

代码

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.mp4el-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>

测试

SpringBoot+vue 大文件分片下载_第1张图片

你可能感兴趣的:(前端学习,spring,boot,vue.js,后端)