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

news2024/10/6 5:54:17

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路飞项目--06

redis介绍和安装 # 数据库&#xff1a; 关系型数据库&#xff1a;mysql、oracle、postgrasql、sqlserver、sqlite IBM&#xff1a;服务器 Oracle&#xff1a;数据库 达梦 EMC&#xff1a;存储 非关系型数据库: redis、mongodb、es…

一文读懂:AWS 网络对等互连(VPC peering)实用操作指南

VPC peering connection-网络对等互连在您的 Atlas VPC 和云提供商的 VPC 之间建立私有连接。该连接将流量与公共网络隔离以提高安全性。本篇文章有VPC peering的操作指南以及价格等信息。如还有疑问请联系我们MongoDB的销售&#xff0c;客户成功经理或解决方案架构师。 1 使用…

CPU漏洞之Meltdown

1.前言 计算机系统的安全性从根本上依赖于内存隔离&#xff0c;例如内核(Kernel)地址范围被标记为不可访问&#xff0c;并对用户访问加以限制和保护&#xff0c;因此操作系统确保了用户程序不能访问彼此的内存或内核内存。这种内存隔离是我们计算机环境的基石&#xff0c;它允…

【mysql】1000w数据量的分页查询SQL,如何优化提升性能?

文章目录 优化场景特别注意&#xff01;&#xff01;&#xff01;有前提&#xff0c;谨慎使用 优化场景 当表数据量非常大时&#xff0c;需要进行分页查询如果慢的时候&#xff0c;可以考虑优化下。 假设一页展示10条&#xff0c;查询第10w条后面的数据时候变慢了… 优化思路&…

抖音短视频:表情包账号的魅力与运营之道以及制作与工具

在短视频的浪潮中&#xff0c;抖音以其独特的创意和趣味性成为了年轻人的最爱。其中&#xff0c;表情包账号更是凭借其生动、形象的表现方式&#xff0c;赢得了众多用户的青睐。本文将深入探讨抖音短视频表情包账号的魅力所在以及如何有效运营。 一、表情包账号的独特魅力 情…

C# 1.消息队列MQ使用场景--图文解析

为什么使用消息队列MQ&#xff08;Message Queue&#xff09;&#xff1f; 消息队列有什么优点和缺点&#xff1f; Kafka(大数据日志采集)、ActiveMQ(最早的MQ--目前使用较少)、RabbitMQ(开源&#xff0c;中小型企业使用足够)、RocketMQ(阿里开发&#xff0c;大型企业适用) 都…

windows Server下Let‘s Encrypt的SSL证书续期

一、手动续期方法&#xff1a; 暂停IIS服务器 --> 暂时关闭防火墙 --> 执行certbot renew --> 打开防火墙 --> 用OpenSSL将证书转换为PFX格式-->pfx文件导入到IIS --> IIS对应网站中绑定新证书 --> 重新启动IIS -->完成 1、暂停IIS服务器 2、暂时关闭…

冲击大厂算法面试=>链表专题【链表删除】

冲击大厂算法面试>链表专题【链表删除】 本文学习目标或者巩固的知识点 学习如何删除链表中的某个节点 如何删除valk的节点如何删除倒数第n个节点 学习如何删除链表中的某些节点 涉及头节点问题如何解决 提前说明&#xff1a;算法题目来自力扣、牛客等等途径 &#x1f7e…

每日一学—由面试题“Redis 是否为单线程”引发的思考

文章目录 &#x1f4cb; 前言&#x1f330; 举个例子&#x1f3af; 什么是 Redis&#xff08;知识点补充&#xff09;&#x1f3af; Redis 中的多线程&#x1f3af; I/O 多线程&#x1f3af; Redis 中的多进程&#x1f4dd; 结论&#x1f3af;书籍推荐&#x1f525;参与方式 &a…

[更新]ARCGIS之土地耕地占补平衡、进出平衡系统报备坐标txt格式批量导出工具(定制开发版)

序言 之前开发的耕地占补平衡报备格式&#xff0c;现在之前的基础上集成了耕地进出平衡报备格式导出。 之前版本软件详见&#xff1a;软件介绍 一、软件简介 本软件是基于arcgis二次开发的工具&#xff08;插件&#xff09;&#xff0c;需要授权后才能使用&#xff1b; 本软件…

【成像光谱遥感技术】如何使用ChatGPT应用自如

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本课程重点介绍ChatGPT在遥感中的应用&#xff0c;人工智…

TongWEB(东方通),部署WEB前后端项目步骤

我的系统: 银河麒麟桌面系统V10(SP1)(兆芯) 环境需要搭建好,什么redis,数据库等 1.准备项目前端war包 (我后端项目本就是war部署,jar转war自行百度一下吧) 进入前端打包好的dist文件夹,创建一个文件夹 WEB-INF ,再在 WEB-INF 里创建一个 web.xml 文件,文件内容: <web-…

大数据-数据可视化-环境部署vue+echarts+显示案例

文章目录 一、安装node.js1 打开火狐浏览器,下载Node.js2 进行解压3 配置环境变量4 配置生效二、安装vue脚手架1 下载vue脚手架,耐心等待。三、创建vue项目并启动1 创建2 启动四、下载echarts.js与axios.js到本地。五、图表显示demo【以下所有操作均在centos上进行】 一、安…

【Python笔记-设计模式】外观模式

一、说明 外观模式是一种结构型设计模式&#xff0c;能为程序库、框架或其他复杂类提供一个统一的接口。 (一) 解决问题 简化复杂系统的接口调用 (二) 使用场景 简化复杂系统&#xff1a;需要一个指向复杂子系统的直接接口&#xff0c; 且该接口的功能有限时重构复杂的代码…

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

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

Windows中的Git Bash运行conda命令:未找到命令的错误(已解决)

在windows中的Gitbash中 打开激活conda环境&#xff0c;并运行&#xff08;前提是你先安装好git&#xff08;自己去官网下载&#xff09;&#xff09;。 要能够在Gitbash上运行Conda&#xff0c; 临时配置 如果你只是临时用一下&#xff0c;就是临时爽一把&#xff0c;那就按…

【Python】OpenCV-图像轮廓检测初学

图像轮廓检测初学 在图像处理领域中&#xff0c;轮廓检测是一项重要的任务&#xff0c;用于寻找并标定图像中的物体边缘。本文将介绍如何使用OpenCV库进行图像轮廓检测&#xff0c;并展示一个简单的示例代码。代码中的注释将详细解释每一步的操作。 1. 引言 图像轮廓检测是图…

可视化 RAG 数据 - 用于检索增强生成的 EDA

每日推荐一篇专注于解决实际问题的外文,精准翻译并深入解读其要点,助力读者培养实际问题解决和代码动手的能力。 欢迎关注公众号(NLP Research),及时查看最新内容 原文标题:Visualize your RAG Data — EDA for Retrieval-Augmented Generation 原文地址:https://medi…

网络原理 - HTTP/HTTPS(5)

HTTPS HTTPS也是一个应用层协议.在HTTP协议的基础上引入了一个加密层. HTTP协议内容都是按照文本的方式明文传输的. 这就导致了在传输过程中出现了一些被篡改的情况. 臭名昭著的"运营商劫持" 下载一个天天动听. 未被劫持的效果,点击下载按钮,就会弹出天天动听的…

yolov5-tracking-xxxsort yolov5融合六种跟踪算法(三)--目标跟踪

本次开源计划主要针对大学生无人机相关竞赛的视觉算法开发。 开源代码仓库链接&#xff1a;https://github.com/zzhmx/yolov5-tracking-xxxsort.git 先按照之前的博客配置好环境&#xff1a; yolov5-tracking-xxxsort yolov5融合六种跟踪算法&#xff08;一&#xff09;–环境配…