Téléchargement de fragments de fichiers volumineux SpringBoot+vue

lien d'apprentissage

Téléchargement, téléchargement et aperçu de fichiers SpringBoot + vue et téléchargement segmenté de fichiers volumineux et progression du téléchargement de fichiers

Blob & Fichier & FileReader & ArrayBuffer

Vue+SpringBoot implémente le téléchargement de fragments de fichiers

Apprentissage des balises vidéo et lecteur vidéo xgplayer lit le mp4 par segments (le processus d'interaction de demande de plage peut se référer aux captures d'écran contenues dans ce document)

[java] Java implémente le téléchargement et le téléchargement fragmentés de fichiers volumineux (springboot+vue3) ( le code a été transféré en local )

le code

Contrôleur de fichiers

L'implémentation du code ici peut être complètement référencéeResourceHttpRequestHandler#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

  • Envoyez d’abord une requête principale pour obtenir la taille du fichier
  • Envoyez à nouveau une demande de publication pour obtenir chaque fragment (par souci de simplicité, l'utilisation de l'attente asynchrone ne sera pas introduite)
  • Combinez chaque fragment récupéré en un seul fichier
  • (En fait, l'implémentation suivante peut également être traitée en utilisant Promise.all() pour démarrer plusieurs téléchargements en même temps, puis les combiner une fois toutes les tâches de téléchargement terminées)
<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>

test

Insérer la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/qq_16992475/article/details/132118802
conseillé
Classement