PDF.js实现按需加载pdf文件
- 说明
- 前言
- 前端项目
- 分片加载的效果
- 前端项目结构
- 项目运行与访问
- 后端项目
- 项目结构
- 核心代码实现
- 注意事项
- 项目源码
说明
本文主要是介绍pdf.js的前后端项目的实现,包含可直接运行的源码。由于本人偏向于后端开发,因此前端的vue方面的demo介绍可能略有不足之处,敬请谅解。可运行源码放在文章末尾处
前言
本文主要是解决大体积pdf在线浏览加载缓慢,影响用户体验的问题。以及实现了分片加载后的,首次加载时自动加载了全部的pdf分片,导致浏览器报出内存不足的问题
技术栈为:SpringBoot、Vue、pdfjs
主要核心思路:前端请求时请求头附带请求范围range及读取大小,后端根据请求头返回相应的pdf文件流
前端项目
分片加载的效果
前端项目结构
项目运行与访问
首先确保vue需要的运行环境已经安装,然后使用vscode打开项目,在终端输入命令:
npm install
执行完成后,输入运行命令
npm run serve
后端项目
项目结构
后端项目是我在学习别的项目时创建,因此在上面临时写了一个分片加载的接口,直接运行即可
核心代码实现
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();
}
}
}
注意事项
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/1nlGWhsB40UB_c25NglbP-g?pwd=jrqj
提取码:jrqj
后端:
链接:https://pan.baidu.com/s/1m8IFVfOiDbMXKUdx1D_NoQ?pwd=otzy
提取码:otzy