PDF.js实现按需分片加载pdf文件-包含前后端开发源码和详细开发教程

news2024/10/5 14:32:09

PDF.js实现按需加载pdf文件

  • 说明
  • 前言
  • 前端项目
    • 分片加载的效果
    • 前端项目结构
    • 前端核心代码
    • 项目运行与访问
  • 后端项目
    • 项目结构
    • 核心代码实现
    • 注意事项
  • 项目源码

说明

本文主要是介绍pdf.js的前后端项目的实现,包含可直接运行的源码。由于本人偏向于后端开发,因此前端的vue方面的demo介绍可能略有不足之处,敬请谅解。可运行源码放在文章末尾处,如果项目运行问题可私信

前言

本文主要是解决大体积pdf在线浏览加载缓慢,影响用户体验的问题。以及实现了分片加载后的,首次加载时自动加载了全部的pdf分片,导致浏览器报出内存不足的问题
技术栈为:SpringBoot、Vue、pdfjs
主要核心思路:前端请求时请求头附带请求范围range及读取大小,后端根据请求头返回相应的pdf文件流

前端项目

分片加载的效果

在这里插入图片描述

前端项目结构

在这里插入图片描述
在这里插入图片描述

前端核心代码

Home index.vue
<el-button type="primary" @click="toLoad">预览文件</el-button>按钮触发跳转到Pdf文件夹index.vue页面

<template>
  <div class="container">
    <el-button type="primary" @click="toLoad">预览文件</el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  mounted() {},
  methods: {
    toLoad() {
      this.$router.push("/pdf");
    },
  },
};
</script>

<style lang="scss" scoped>
.container {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>

Pdf index.vue

获取后端返回pdf文件流的接口,this.baseUrl为本地的后端项目接口:http://localhost:8181
this.src = ${this.baseUrl}/v1/pdf/load;

<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;
      // 数据文件流 转成 pdf
      this.src = `${this.baseUrl}/v1/pdf/load`;
    },
    // 禁用鼠标右击、F12 来禁止打印和打开调试工具
    prohibit() {
      document.oncontextmenu = function (ev) {
        return false; //屏蔽右键菜单
      };
      document.onkeydown = function (e) {
        if (
          e.ctrlKey &&
          (e.keyCode === 65 ||
            e.keyCode === 67 ||
            e.keyCode === 73 ||
            e.keyCode === 74 ||
            e.keyCode === 80 ||
            e.keyCode === 83 ||
            e.keyCode === 85 ||
            e.keyCode === 86 ||
            e.keyCode === 117)
        ) {
          return false;
        }
        if (e.keyCode === 18 || e.keyCode === 123) {
          return false;
        }
      };
    },
  },
  mounted() {
    this.$nextTick(() => {
      this.getPdfCode();
    });
  },
};
</script>

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

项目运行与访问

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

npm install

执行完成后,输入运行命令

npm run serve

在这里插入图片描述

后端项目

项目结构

后端项目是我在学习别的项目时创建,因此在上面临时写了一个分片加载的接口,直接运行即可
在这里插入图片描述

核心代码实现

pdf分片加载的后端实现类 PDFController.java

 package com.zhouquan.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 zhouquan
 * @description todo
 * @date 2022-07-29 10:29
 **/
@RestController
@RequestMapping("/v1/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("classpath:泛函分析教程(第2版).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();
        }
    }
}

跨域配置类 CORSFilter.java

package com.zhouquan.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 {
    }
}

注意事项

1.首次加载返回状态码200,注意以下属性服务器端在响应头中务必要加上
在这里插入图片描述

 //表明服务器支持分片加载
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");

2.之后每次请求都会返回206,即已经实现分片加载。Content-Range: bytes 0-65535/408244,表明此次返回的文件范围
在这里插入图片描述
在这里插入图片描述

项目源码

前端:
链接:https://pan.baidu.com/s/1xUfVaBSkr4T-PPAw2nEzfg?pwd=c3pf
提取码:c3pf

后端:

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

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

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

相关文章

Redis设计逻辑及生产部署问题整理

数据结构 redis数据结构包括&#xff1a;简单动态字符串SDS、链表、字典、跳跃表、整数组合、压缩列表。 SDS&#xff1a;在增加/减少字符串时不会频繁进行内存充分配&#xff0c;采用了空间预分配和惰性空间释放两种优化策略。 链表&#xff1a;链表节点使用void*保存节点值&a…

Stable Diffusion Web-UI 安装指南

Stable DIffusion 是 Stability.AI 开源的 text-to-image 模型&#xff0c;目前类似产品有 Midjourney 以及 OpenAI 的 DELL-2 &#xff1b;从AI绘画效果上来说&#xff0c;Midjourney 目前公认是最好的&#xff1b;但从模型的可玩性和发展潜力来看&#xff0c;个人观点来看&am…

【009】C++数据类型之转义字符和类型转换

C数据类型之转义字符和类型转换 引言一、转义字符1.1、概念1.2、八进制转义1.3、十六进制转义 二、类型转换2.1、自动类型转换原则2.2、强制类型转换 三、C新特性中类型转换的扩展3.1、隐式类型转换3.2、显式类型转换 总结 引言 &#x1f4a1; 作者简介&#xff1a;专注于C/C高…

Packet Tracer – 配置单臂路由器 VLAN 间路由

Packet Tracer – 配置单臂路由器 VLAN 间路由 地址分配表 设备 接口 IPv4 地址 子网掩码 默认网关 R1 G0/0.10 172.17.10.1 255.255.255.0 不适用 G0/0.30 172.17.30.1 255.255.255.0 不适用 PC1 NIC 172.17.10.10 255.255.255.0 172.17.10.1 PC2 NIC 1…

游乐园里,一边带小孩,一边写代码,分享一些有趣好玩儿的嵌入式软硬件资讯...

作者&#xff1a;晓宇&#xff0c;排版&#xff1a;晓宇 微信公众号&#xff1a;芯片之家&#xff08;ID&#xff1a;chiphome-dy&#xff09; 01 边带小孩边写代码 以前觉得&#xff0c;自己下班后都还有大把时间&#xff0c;下班了回到家还能再干个两三个小时&#xff0c;学…

定义运营系统架构

介绍 供应商提供的信息系统随着新功能和实施策略不断发展。可用选项的复杂性和多样性使许多公司难以充分讨论和比较可能满足或不满足其要求的替代方案。 供应商通常会推广由公司或个人工具箱中的产品或解决方案支持的架构。如果公司对其运营系统的架构没有清晰的愿景&#xf…

第九章:C语言的简单结构体

作为一个人有什么关于人的属性呢&#xff1f;简单的梳理一下&#xff0c;人的属性有自己的名字&#xff0c;年龄&#xff0c;身高&#xff0c;体重...。当然关于人的属性还有很多&#xff0c;当我们C语言来描述一下人的属性&#xff0c;就需要定义多个变量&#xff0c;那我们这…

21天学会C++:Day4----函数重载

CSDN的uu们&#xff0c;大家好。这里是C入门的第四讲。 座右铭&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;C专题 目录 1. 知识引入 2. 函数重载的知识点 2. 为什么C语言不支持函数重载而C支持呢&…

springboot贫困生勤工助学评定管理系统

本系统尝试使用springboot在网上架构一个动态的贫困生管理系统&#xff0c;以使每一用户在家就能通过系统来进行贫困生管理。 Spring Boot 是 Spring 家族中的一个全新的框架&#xff0c;它用来简化Spring应用程序的创建和开发过程。也可以说 Spring Boot 能简化我们之前采用S…

数据结构学习记录——图应用实例-拯救007(问题描述、解题思路、伪代码解读、C语言算法实现)

目录 问题描述 解题思路 伪代码 总体算法 DFS算法 伪代码解读 总体算法 DFS算法 具体实现&#xff08;C语言&#xff09; 问题描述 在老电影“007之生死关头”&#xff08;Live and Let Die&#xff09;中有一个情节&#xff0c;007被毒贩抓到一个鳄鱼池中心的小岛…

Matlab - Plot in plot(图中画图)

Matlab - Plot in plot&#xff08;图中画图&#xff09; 这是在MATLAB中创建一个嵌入式图形的示例&#xff0c;可以在另一个图形中显示。 与MATLAB中的“axes”函数相关。 Coding % Create data t linspace(0,2*pi); t(1) eps; y sin(t);% Place axes at (0.1,0.1) with w…

学系统集成项目管理工程师(中项)系列24a_信息系统集成专业技术知识(上)

1. 信息系统的生命周期 1.1. 【19下选10】 1.2. 立项 1.2.1. 形成《需求规格说明书》并确定立项 1.2.1.1. 【21上选11】 1.3. 开发 1.3.1. 【22下选10】 1.3.2. 以立项阶段所做的需求分析为基础&#xff0c;进行总体规划。之后&#xff0c;通过系统分析、系统设计、系统…

ChatGPT4 镜像网站推荐

文章目录 1. TomChat2. Ai Doge3. 二狗问答4. 小莓用AI5. Ora6. ChatGPT镜像7. ChatGPT镜像8. VIVI-AI9. 小杰AI10. ChatGPT Web11. AIchatOS 什么是ChatGPT? ChatGPT&#xff0c;全称&#xff1a;聊天生成预训练转换器&#xff08;英语&#xff1a;Chat Generative Pre-train…

辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-HMI人机交互

书接上回 2.3.7HMI人机交互 2.3.7.1显示 (1)图标 序号 图标状态 (图形、颜色供参考) 含义说明 备注 1 辅助驾驶功能READY

Winform窗体利用WebApi接口实现ModbusTCP数据服务

在上位机开发过程中&#xff0c;有时候会遇到需要提供数据接口给MES或者其他系统&#xff0c;今天跟大家分享一下&#xff0c;如何在Winform等桌面应用程序中&#xff0c;开发WebApi接口&#xff0c;提供对外modbus设备的数据服务。通讯模型是&#xff1a; 为了更好地演示应用场…

华为OD机试真题 Java 实现【简单的解压缩算法】【2023Q1 200分】

一、题目描述 现需要实现一种算法&#xff0c;能将一组压缩字符串还原成原始字符串&#xff0c;还原规则如下&#xff1a; 1、字符后面加数字N&#xff0c;表示重复字符N次。例如&#xff1a;压缩内容为A3&#xff0c;表示原始字符串为AAA。 2、花括号中的字符串加数字N&…

MyBatis环境搭建+第一个MyBatis程序

目录 1.MyBatis是什么&#xff1f; 2.MyBatis开发环境搭建 3.我的第一个MyBatis程序 1.MyBatis是什么&#xff1f; MyBatis是一款数据库框架&#xff0c;是一款优秀的持久层框架&#xff0c;它不仅支持用户自定义SQL和存储过程&#xff0c;而且还具有高级映射功能。简单来说…

【重新定义matlab强大系列九】函数isoutlier查找数据中的离群值

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

【TikZ 简单学习(上):基础绘制】Latex下的绘图宏包

【TikZ 简单学习[上]基础绘制】Latex下的绘图宏包 前置简单图形绘制基本架构路径绘制添加样式/风格弧线绘制剪切抛物线和正弦曲线绘制填充和绘制渲染绘制箭头循环添加文本信息绘制一个角度 前置 Latex 可以解决绘制这些东西&#xff1a; ∫ a b 1 x d x \int_a^b\frac{1}{x}dx…

DoFE:Domain-oriented Feature Embedding

key : 通过利用多源领域的知识来提高CNN在未见目标领域上的泛化能力。 我们的DoFE框架通过动态丰富图像特征与来自多源领域学习的附加领域先验知识相结合&#xff0c;使语义特征更具辨别性。引入了一个领域知识池来学习和记忆从多源领域提取的先验信息。 然后&#xff0c;原始…