SpringBoot+Vue+PdfJS实现大文件分片加载 后端实现

SpringBoot+Vue+PdfJS实现大文件分片加载 后端实现

在企业开发中,我们经常会用到大文件显示的问题,比如一个100M的PDF文件,我们想要把它加载到浏览器的页面或者APP上,如果我们用全加载的话,就有可能出现加载慢,甚至报内存溢出的错误,所以这时候我们就需要使用分片加载的技术了

分片加载需要浏览器的支持,像谷歌和火狐都默认支持分片加载,当我们使用PdfJS的时候,我们可以通过PdfJS的配置项去开启浏览器分片加载功能

当然,服务器端也需要开启支持分片的功能,具体是通过几个响应头参数进行配置的,这样,当前端浏览器需要加载分片时,就会从服务器端拉取分片信息,这些操作可以看做为网络协议为我们封装的,自然不用我们来实现,我们需要做的只是按照规则进行配置即可,但是后端需要对文件流进行切片,下面就来看看具体是如何实现

技术栈:SpringBoot,Vue,PdfJS

后端实现

一般来讲,后端会获取一个PDF文件流,然后我们会根据Request请求头里的Range参数去截取文件流的分片,然后将分片写入Response的输出流里,返回给前端加载展示,做到按需展示

注意,如果使用pdfjs工具的话,则不需要我们指定range,pdfjs会帮我们封装好每次请求的range范围,一般情况下是不需要我们手动指定分片range范围

项目结构

因为测试,所以只需要新建一个Controller即可,来指定分片加载的后端接口

image-20230706230105501

代码实现

PDFController的具体实现,这里我们只使用本地的一个Pdf文件作为测试,将它转成文件流的形式,然后分片流写入Http的Response流,返回给客户端,注意响应头的设置是为了开启服务端分片加载的功能,因此不能省去,具体配置可以查看代码中的说明信息

package com.example.pdfjs.controller;

import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

/**
 * @author zj
 * @description pdf分片加载
 * @date 2023-07-06 10:29
 **/
@RestController
@RequestMapping("/pdf")
public class PDFController {

    /**
     * pdf分片加载的后端实现
     *
     * @param response
     * @param request
     * @throws FileNotFoundException
     */
    @GetMapping("/load")
    public void loadPDFByPage(HttpServletResponse response, HttpServletRequest request) throws FileNotFoundException {

        File pdf = ResourceUtils.getFile("C:\\Users\\ZJ\\Desktop\\Doc\\Java.pdf");

        try (
                InputStream is = new FileInputStream(pdf);
                BufferedInputStream bis = new BufferedInputStream(is);
                OutputStream os = response.getOutputStream();
                BufferedOutputStream bos = new BufferedOutputStream(os)) {

            // 下载的字节范围
            int startByte, endByte, totalByte;
            if (request != null && request.getHeader("range") != null) {
                // 断点续传
                String[] range = request.getHeader("range").replaceAll("[^0-9\\-]", "").split("-");
                // 文件总大小
                totalByte = is.available();
                // 下载起始位置
                startByte = Integer.parseInt(range[0]);
                // 下载结束位置
                if (range.length > 1) {
                    endByte = Integer.parseInt(range[1]);
                } else {
                    endByte = totalByte - 1;
                }
                // 返回http状态
                response.setStatus(206);
            } else {
                // 正常下载
                // 文件总大小
                totalByte = is.available();
                // 下载起始位置
                startByte = 0;
                // 下载结束位置
                endByte = totalByte - 1;
                // 返回http状态
                response.setHeader("Accept-Ranges", "bytes");
                response.setStatus(200);
            }
            // 需要下载字节数
            int length = endByte - startByte + 1;

            //表明服务器支持分片加载
            response.setHeader("Accept-Ranges", "bytes");

            //Content-Range: bytes 0-65535/408244,表明此次返回的文件范围
            response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + totalByte);

            //告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
			response.setContentType("application/octet-stream");

            //表明该文件的所有字节大小
            response.setContentLength(length);

            //需要设置此属性,否则浏览器默认不会读取到响应头中的Accept-Ranges属性,因此会认为服务器端不支持分片,所以会直接全文下载
            response.setHeader("Access-Control-Expose-Headers", "Accept-Ranges,Content-Range");

            // 响应内容
            bis.skip(startByte);
            int len = 0;
            byte[] buff = new byte[1024 * 64];
            while ((len = bis.read(buff, 0, buff.length)) != -1) {
                if (length <= len) {
                    bos.write(buff, 0, length);
                    break;
                } else {
                    length -= len;
                    bos.write(buff, 0, len);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

接口请求

我们使用ApiFox请求,查看接口返回的数据

注意:可以通过响应头配置流的类型

//文件流
response.setContentType("application/octet-stream");

//Pdf文件
response.setContentType("application/pdf");

这样,我们就可以在ApiFox中查看返回的字节流信息了,如下:

注意:这里我们只是测试所以在请求头里加了range参数,实际在项目中当我们使用PdfJS时,是不需要手动添加range参数,PdfJS会根据需要自动计算range值传给后台,如果我们手动添加range请求头参数,我们会发现下载的数据是损坏的,这是因为,PdfJS分片加载文件流时,会先发送一个200的请求用以获取文件的元数据和目录等信息,然后再发送206请求获取分片再去元数据和目录中查找对应的分片信息,所以能正常显示文件内容,而如果我们直接指定请求字节范围,请求就不会去发送200请求获取Pdf文件的元数据和目录等先驱数据,这时虽然正常获取了分片数据流,但是浏览器无法解析就会报文件格式损坏的错误

image-20230706231352946

<think>嗯,用户今天问了三个问题,都是关于VueSpringBoot实现文件在线预览的。首先,我需要理清每个问题的核心点,然后组织回答。 第一个问题是Vue如何实现PDF在线预览。用户可能是在前端开发中遇到了需要展示PDF的需求。这时候我应该想到常用的库,比如pdf.js或者vue-pdf。需要说明两种方法,一种是直接使用Mozilla的pdf.js,另一种是用现成的Vue组件,比如vue-pdf。还要提到后端是否需要转换,或者前端直接处理。可能用户对是否需要服务端处理不太清楚,所以需要解释两种方式的区别,比如直接通过URL加载和需要后端转码的情况。 第二个问题是SpringBoot后端传输给前端的数据格式。这里要分情况讨论,可能返回的是文件流或者URL。如果是文件流的话,通常用字节数组或者Base64编码,需要设置正确的Content-Type。而如果是URL,可能后端已经存储了文件,前端直接通过链接访问。需要明确不同的传输方式对应的处理方法,比如ResponseEntity<byte[]>或者返回JSON包含URL。用户可能想知道如何组织响应,以及前后端如何协作。 第三个问题关于其他文件在线预览的可行性,比如Word、Excel。用户可能想扩展功能,不仅限于PDF。这时候需要提到不同的方法,比如使用微软的Office Online服务,或者开源库如Mammoth.js、SheetJS,以及转换PDF后用PDF预览的方法。可能需要比较各种方案的优缺点,比如依赖外部服务或自建服务的区别。还要提到浏览器兼容性和格式问题,比如较新的Office格式可能需要特定处理。 用户可能的真实需求是在项目中实现多种文件的在线预览,而不仅仅是PDF。他们可能希望了解整个流程,包括前后端如何配合,以及有哪些现成的工具可用。需要确保回答覆盖了前后端的协作,不同文件类型的处理方案,以及可能的注意事项,比如性能、安全性或第三方服务的依赖。 在组织回答时,需要分点清晰,使用示例代码或步骤来帮助理解。同时要指出不同方法的适用场景,比如小项目可能倾向于使用现成库,而大企业可能考虑自建服务。另外,需要注意用户可能对某些技术细节不熟悉,比如Base64编码或HTTP头的设置,所以需要简明扼要地解释这些概念,或者提供参考链接。 最后,总结部分要强调根据项目需求选择方案,并考虑维护成本。确保用户能够根据回答中的信息,选择适合自己项目的方法,并顺利实施。</think>以下是对三个问题的分点解答,供参考: --- ### **1. Vue如何实现PDF在线预览** **常见方案:** - **使用 `pdf.js`(Mozilla官方库)** ① 安装依赖:`npm install pdfjs-dist` ② 在Vue组件中加载PDF: ```javascript import * as pdfjsLib from 'pdfjs-dist'; pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.js'; const loadingTask = pdfjsLib.getDocument(pdfUrl); loadingTask.promise.then(pdf => { // 渲染PDF页面 }); ``` - **使用 `vue-pdf` 插件** ① 安装:`npm install vue-pdf` ② 直接嵌入组件: ```vue <template> <pdf :src="pdfUrl"></pdf> </template> <script> import pdf from 'vue-pdf' export default { components: { pdf } } </script> ``` **后端协作**:需返回PDF的URL或二进制流(如SpringBoot通过`byte[]`或Base64传输)。 --- ### **2. SpringBoot后端传输数据的形式** **常见方式:** - **返回文件流** ```java @GetMapping("/pdf") public ResponseEntity<byte[]> getPdf() { byte[] pdfBytes = // 读取文件或生成内容 return ResponseEntity.ok() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PDF_VALUE) .body(pdfBytes); } ``` - **返回文件URL** 前端通过URL直接访问静态资源(需配置SpringBoot静态资源路径): ```java @GetMapping("/pdf-url") public Map<String, String> getPdfUrl() { return Map.of("url", "/static/files/document.pdf"); } ``` --- ### **3. 其他文件在线预览的借鉴方案** **通用思路:** - **Office文件(Word/Excel)** - **微软Office Online**:通过嵌入URL(需公网可访问) ```html <iframe src="https://view.officeapps.live.com/op/embed.aspx?src=文件URL"></iframe> ``` - **开源方案**: - **Mammoth.js**(Word转HTML) - **SheetJS(xlsx.js)**(Excel转HTML表格) - **图片/文本**:直接通过浏览器渲染(如返回`image/png`或`text/plain`)。 - **通用方案**: 将文件统一转为PDF(如使用`LibreOffice`无头模式转换),再用PDF预览方案。 **注意事项**: - 浏览器兼容性(如旧版Office格式需转换) - 大文件分片加载或缓存优化 - 敏感文件需鉴权(如通过Token验证URL) --- ### **总结** - **前端**:根据文件类型选择渲染库(如`pdf.js`、`Mammoth.js`)。 - **后端**:提供文件流/URL,注意设置正确的`Content-Type`。 - **扩展性**:复杂场景可结合文件转换服务(如`Apache POI`处理Office文件)。 可根据项目规模选择轻量级嵌入方案或自建文件转换服务。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BirdMan98

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值