SpringBoot+PDF.js实现按需分片加载预览(包含可运行示例源码)

SpringBoot+PDF.js实现按需分片加载预览

  • 前言
  • 分片加载的效果
  • 前端项目
    • 前端项目结构
    • 前端核心代码
    • 前端项目运行
  • 后端项目
    • 后端项目结构
    • 后端核心代码
    • 后端项目运行
  • 项目运行效果
    • 首次访问
    • 分片加载
  • 项目源码

前言

本文的解决方案旨在解决大体积PDF在线浏览加载缓慢、影响用户体验的难题。通过利用分片加载技术,前端请求时附带range及读取大小信息,后端据此返回相应的PDF文件流。这种方式有效地减轻了服务器和浏览器的负担,提升了加载速度和用户体验。同时解决了首次加载全部分片导致浏览器内存不足的问题。

技术栈:Spring Boot、Vue和pdf.js。

分片加载的效果

在这里插入图片描述

前端项目

前端项目结构

在这里插入图片描述

image-20240223172511041

前端核心代码

index.vue

<template>
  <div class="pdf">
    <iframe
      :src="`/static/pdf/web/viewer.html?file=${encodeURIComponent(src)}`"
      frameborder="0"
      style="width: 100%; height: calc(100vh)"
    ></iframe>
  </div>
</template>

<script>
import baseUrl from "@/api/baseurl.js";
export default {
  data() {
    return {
      baseUrl: baseUrl.baseUrl,
      src: "",
      loading: false,
    };
  },
  created() {},
  methods: {
    getPdfCode: function () {
      this.loading = true;
      // 这里是请求分片的接口,看情况修改
      this.src = `http://localhost:8181/v1/pdf/load`;
    },
  },
  mounted() {
    this.$nextTick(() => {
      this.getPdfCode();
    });
  },
};
</script>

<style lang="scss" scoped></style>

image-20240224132317559

前端项目运行

首先确保vue需要的运行环境已经安装(nodejs),我使用的版本:12.18.2,然后使用vscode打开项目,在终端输入命令:

npm install
npm run serve

image-20240223173450002

后端项目

后端项目结构

本示例只是一个简单的springboot项目,核心文件PDFController.java用于分片加载接口,CORSFilter.java为跨域配置

image-20240224132650175

后端核心代码

这段代码实现了使用 PDF.js 进行分片加载 PDF 文件的功能。下面是代码的主要实现思路:

  1. 首先,通过 ResourceUtils.getFile 方法获取类路径下的 PDF 文件,并将其读取为字节数组 pdfData
  2. 然后,判断文件大小是否小于指定的阈值(1MB),如果小于阈值,则直接将整个文件作为响应返回。修改了小体积pdf小于分片大小时无法访问的bug
  3. 如果文件大小超过阈值,就根据请求头中的 Range 字段判断是否为断点续传请求。
  4. 如果是首次请求或者没有 Range 字段,则返回整个文件的字节范围,并设置响应状态为 SC_OK(响应码200)。
  5. 如果是断点续传请求,则解析 Range 字段获取请求的起始位置和结束位置,并根据这些位置从文件中读取相应的字节进行响应。
  6. 在响应头中设置 Accept-RangesContent-Range 属性,告知客户端服务器支持分片加载,并指定本次返回的文件范围。
  7. 最后,设置响应的内容类型为 application/octet-stream,内容长度为本次返回的字节数,然后刷新输出流,将数据返回给客户端。

这样,客户端就可以使用 PDF.js 来分片加载显示 PDF 文件了。

PDFController.java

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

    // 获取pdf文件,建议pdf大小超过20mb以上
    File pdf = ResourceUtils.getFile("classpath:需要分片加载的pdf.pdf");
    byte[] pdfData = new byte[0];
    try {
        pdfData = FileUtils.readFileToByteArray(pdf);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    // 以下为pdf分片的代码
    try (InputStream is = new ByteArrayInputStream(pdfData);
         BufferedInputStream bis = new BufferedInputStream(is);
         OutputStream os = response.getOutputStream();
         BufferedOutputStream bos = new BufferedOutputStream(os)) {

        // 下载的字节范围
        int startByte, endByte, totalByte;

        // 获取文件总大小
        int fileSize = pdfData.length;

        int minSize = 1024 * 1024;
        // 如果文件小于1 MB,直接返回数据,不需要进行分片
        if (fileSize < minSize) {
            // 直接返回整个文件
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("application/octet-stream");
            response.setContentLength(fileSize);
            bos.write(pdfData);
            return;
        }

        // 根据HTTP请求头的Range字段判断是否为断点续传
        if (request == null || request.getHeader("range") == null) {
            // 如果是首次请求,返回全部字节范围 bytes 0-7285040/7285041
            totalByte = is.available();
            startByte = 0;
            endByte = totalByte - 1;
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            // 断点续传逻辑
            String[] range = request.getHeader("range").replaceAll("[^0-9\\-]", "").split("-");
            // 文件总大小
            totalByte = is.available();
            // 下载起始位置
            startByte = Integer.parseInt(range[0]);
            // 下载结束位置
            endByte = range.length > 1 ? Integer.parseInt(range[1]) : totalByte - 1;

            // 跳过输入流中指定的起始位置
            bis.skip(startByte);

            // 表示服务器成功处理了部分 GET 请求,返回了客户端请求的部分数据。
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

            int bytesRead, length = endByte - startByte + 1;
            byte[] buffer = new byte[1024 * 64];
            while ((bytesRead = bis.read(buffer, 0, Math.min(buffer.length, length))) != -1 && length > 0) {
                bos.write(buffer, 0, bytesRead);
                length -= bytesRead;
            }
        }

        // 表明服务器支持分片加载
        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(endByte - startByte + 1);
        // 需要设置此属性,否则浏览器默认不会读取到响应头中的Accept-Ranges属性,
        // 因此会认为服务器端不支持分片,所以会直接全文下载
        response.setHeader("Access-Control-Expose-Headers", "Accept-Ranges,Content-Range");
        // 第一次请求直接刷新输出流,返回响应
        response.flushBuffer();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

CORSFilter.java 通用的跨域配置

package com.example.pdfload.filter;

import org.springframework.stereotype.Component;

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

@Component
public class CORSFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response1 = (HttpServletResponse) response;
        response1.addHeader("Access-Control-Allow-Credentials", "true");
        response1.addHeader("Access-Control-Allow-Origin", "*");
        response1.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        response1.addHeader("Access-Control-Allow-Headers",
                "range,Accept-Ranges,Content-Range,Content-Type," +
                "X-CAF-Authorization-Token,sessionToken,X-TOKEN,Cache-Control,If-Modified-Since");
        if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
            response.getWriter().println("ok");
            return;
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

后端项目运行

image-20240224133652301

项目运行效果

image-20240224134625354

首次访问

首次访问返回状态码200,返回响应信息如下:

image-20240224135153313

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

分片加载

分片加载返回状态码206,返回响应信息如下:

image-20240224135805018

image-20240224140040627

项目源码

image-20240224141259487

链接:https://pan.baidu.com/s/1oD9bUvGfFmfEimXfaKGpXg?pwd=zhou
提取码:zhou

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/408331.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从docx提取文本的Python实战代码

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

代码随想录day33-动态规划的应用1

LeetCode62.不同路径 题目描述&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 …

Linux的进程

在Linux中&#xff0c;可以使用多种方式来结束进程。以下是8种常见的方式&#xff1a; 终端中断&#xff08;Ctrl C&#xff09;&#xff1a;在终端中运行的程序可以通过按下Ctrl C组合键来发送SIGINT信号&#xff0c;终止该进程的执行。 kill命令&#xff1a;使用kill命令可…

软考39-上午题-【数据库】-关系代数运算1-传统的集合运算

一、笛卡尔积 二、关系代数 关系代数是施加于关系之上的集合代数运算。 关系代数包含&#xff1a; 传统的集合运算专门的关系运算 2-1、传统的集合运算 1、关系的并 示例&#xff1a; 2、关系的差 示例&#xff1a; 3、关系的交 示例&#xff1a; 关系的并、差、交&#xf…

【C语言】linux内核ipoib模块 - ipoib_ib_handle_rx_wc

一、中文注释 // 定义一个处理InfiniBand接收完成工作请求的函数 static void ipoib_ib_handle_rx_wc(struct net_device *dev, struct ib_wc *wc) {// 通过网络设备获取私有数据结构struct ipoib_dev_priv *priv ipoib_priv(dev);// 获取工作请求ID&#xff0c;并屏蔽掉接收…

【Flink精讲】Flink 内存管理

面临的问题 目前&#xff0c; 大数据计算引擎主要用 Java 或是基于 JVM 的编程语言实现的&#xff0c;例如 Apache Hadoop、 Apache Spark、 Apache Drill、 Apache Flink 等。 Java 语言的好处在于程序员不需要太关注底层内存资源的管理&#xff0c;但同样会面临一个问题&…

纳斯达克大屏-投放需要知道的几个条件-大舍传媒

引言 随着移动互联网的快速发展&#xff0c;数字广告媒体广告越来越受到企业的关注。纳斯达克大屏作为全球最大的数字媒体广告投放平台之一&#xff0c;拥有广泛的受众和优质的媒体资源&#xff0c;吸引了众多企业的眼球。要想在纳斯达克大屏上投放广告&#xff0c;企业需要了…

java数据结构与算法刷题-----LeetCode617. 合并二叉树

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路 此题如果使用广度优先遍历&#xff0c;一定需要创建很多队列&…

21.scala泛型结合隐式转换使用

目录 概述实践代码执行 结束 概述 scala泛型结合隐式转换使用 实践 代码 package com.fun.scala/*** 视图界定*/ object Genericity04 {def main(args: Array[String]): Unit {val s1 new Stu("test", 33)val s2 new Stu("test2", 32)println(new M…

WSL2配置Linux、Docker、VS Code、zsh、oh my zsh(附Docker开机自启设置)

0. 写在前面 本篇笔记来自于UP主麦兜搞IT的合集视频Windows10开发环境搭建中的部分内容 1. 安装WSL2 按照微软官方文档进行操作&#xff0c;当然也可以直接wsl --install 也可以按照 旧版手动安装的步骤 来进行操作 选择安装的是Ubuntu 20.04 LTS 注&#xff1a;WSL默认安装…

HDL FPGA 学习 - Quartus II 工程搭建,ModelSim 仿真,时序分析,IP 核使用,Nios II 软核使用,更多技巧和规范总结

目录 工程搭建、仿真与时钟约束 一点技巧 ModelSim 仿真 Timing Analyzer 时钟信号约束 SignalTap II 使用 In-System Memory Content Editor 使用 记录 QII 的 IP 核使用 记录 Qsys/Nios II 相关 记录 Qsys 的 IP 核使用 封装 Avalon IP 更多小技巧教程文章 更多好…

buuctf_N1BOOK_粗心的小李

题目&#xff1a; 看完题目&#xff0c;git下载文件&#xff1f;然后将.git文件传到线上环境&#xff1f;&#xff08;which 会造成git泄露的安全威胁&#xff09;<这个背景抱歉我不太了解哈&#xff0c;可能后续有补充> 这里主要记录做法过程&#xff1a; 工具&#xf…

vue3获取环境变量import.meta.env

vitevue的时候环境变量的获取方式变成如下&#xff1a; console.log(import.meta.env)

手机单目相机内参标定

使用软件&#xff1a; 参考我之前的文章&#xff1a; 软件地址:https://github.com/DavidGillsjo/VideoIMUCapture-Android/releases 棋盘标定板下载 链接: https://pan.baidu.com/s/1wiPJsEf87Vc0D7KwJnt3GA?pwd1234 提取码: 1234 过程 1.使用下载的软件录制一段视频&am…

第6.4章:StarRocks查询加速——Colocation Join

目录 一、StarRocks数据划分 1.1 分区 1.2 分桶 二、Colocation Join实现原理 2.1 Colocate Join概述 2.2 Colocate Join实现原理 三、应用案例 注&#xff1a;本篇文章阐述的是StarRocks-3.2版本的Colocation Join 官网文章地址&#xff1a; Colocate Join | StarRoc…

【国密算法】理解国密算法的基础概念

目录 1.1 什么是国密算法&#xff1f; 1.1.1 国密算法的定义 1.1.2 国密算法的作用与重要性 1.2 国密算法的工作原理 1.2.1 对称加密与非对称加密 1.2.2 国密算法的特点与优势 1.2.3 国密算法的基本流程 1.3 国密算法与数据传输安全 1.3.1 数据传输中的风险与威胁 1.…

element table数据量太大,造成浏览器崩溃。解决方案

这是渲染出来的数据 其实解决思路大致就是&#xff1a;把后台返回的上万条数据&#xff0c;进行分割&#xff08;前端分页&#xff09;&#xff0c;这样先加载几十条&#xff0c;然后再用懒加载的方式去concat&#xff0c;完美解决 上代码 <template><div class&quo…

七分钟交友匿名聊天室源码

应用介绍 本文来自&#xff1a;七分钟交友匿名聊天室源码 - 源码1688 简介&#xff1a; 多人在线聊天交友工具&#xff0c;无需注册即可畅所欲言&#xff01;你也可以放心讲述自己的故事&#xff0c;说出自己的秘密&#xff0c;因为谁也不知道对方是谁。 运行说明&#xff…

Nginx----高性能的WEB服务端(一)

目录 一、Nginx介绍 1、什么是Nginx 2、Nginx并发连接 3、Nginx应用场景 4、nginx的HTTP七层代理和四层代理 5、反向代理 1.反向代理定义 2.反向代理优点 6、yum安装 7、官网文件安装 二、Nginx和Apache的优缺点比较 1、nginx相对于apache的优点&#xff1a; 2、a…

Java 学习和实践笔记(20):static的含义和使用

static的本义是静止的。在计算机里就表示静态变量。 在Java中&#xff0c;从内存分析图上可以看到&#xff0c;它与类、常量池放在一个区里&#xff1a; 从图可以看到&#xff0c;普通的方法和对象属性&#xff0c;都在heep里&#xff0c;而static则在方法区里。 static声明的…