Descarga segmentada de archivos grandes de SpringBoot + vue

Enlace de estudio

Carga y descarga de archivos SpringBoot+vue y vista previa y carga segmentada de archivos grandes y progreso de carga de archivos

Blob y archivo y lector de archivos y ArrayBuffer

Vue + SpringBoot implementa la descarga segmentada de archivos

El aprendizaje de etiquetas de video y el reproductor de video xgplayer reproducen mp4 en segmentos (el proceso de interacción de solicitud de rango puede consultar las capturas de pantalla en este archivo)

[java] Java implementa la carga y descarga fragmentada de archivos grandes (springboot+vue3) ( el código se ha bifurcado a local )

el código

Controlador de archivos

Puede consultar la implementación del código aquí.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

  • Primero envíe una solicitud principal para obtener el tamaño del archivo.
  • Envíe una solicitud de publicación nuevamente para obtener cada fragmento (para facilitar la comprensión, no se introducirá el uso de async-await)
  • Combine cada fragmento obtenido en un solo archivo
  • (De hecho, la siguiente implementación también se puede procesar usando Promise.all() para iniciar varias descargas al mismo tiempo y luego esperar a que se completen todas las tareas de descarga antes de combinarlas)
<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>

prueba

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_16992475/article/details/132118802
Recomendado
Clasificación