前段时间,老板接到一个需求,说是某个小程序打开PDF文件加载太慢了?开发团队的同学一脸闷逼,大家不都是这样做的?提升服务器的带宽不就可以了么。
结果运维同学在提升了服务器带宽,增加了服务器内存之后,老板到客户面前去装X的时候,还是翻车了。所以就要求开发同学一定要解决这个问题。
我们日常使用的PDF,有两种形式
- 文档形式的PDF:通过Word文档或者PPT等内容导出生成的PDF。
- 图片形式的PDF:通过图片生成的PDF
根据这个思路,我们可以将PDF的每一页切分成一个图片,这样按照图片加载内容,总比直接加载一个一两百兆的PDF文件要加载的快吧,说干就干。下面我们就来看看如何去实现一个PDF文件的切分。
创建一个文件上传的接口
在这个接口中主要完成PDF文件的上传工作。
/**
* 通用上传请求(单个)
*/
@PostMapping("/uploadPdf")
@ResponseBody
public AjaxResult uploadFilePdf(MultipartFile file) throws Exception
{
try
{
String prefix = RuoYiConfig.getImageProxyPath();
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.uploadPdf(filePath, file);
String url = prefix + fileName;
AjaxResult ajax = AjaxResult.success();
ajax.put("url", url);
ajax.put("fileName", fileName);
ajax.put("newFileName", FileUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
return ajax;
}
catch (Exception e)
{
return AjaxResult.error(e.getMessage());
}
}
PDF文件切分操作
首先需要引入两个POM的依赖
<!-- apache PDF转图片-->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.24</version>
</dependency>
<!--itext 图片合成PDF-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.2</version>
</dependency>
编写一个接收PDF文件并且进行拆分的方法
/**
* 将PDF文档拆分成图像列表
*
* @param pdf PDF文件
*/
private static List<BufferedImage> getImgList(File pdf) throws IOException {
PDDocument pdfDoc = PDDocument.load(pdf);
log.info("加载PDF文件成功");
List<BufferedImage> imgList = new ArrayList<>();
PDFRenderer pdfRenderer = new PDFRenderer(pdfDoc);
int numPages = pdfDoc.getNumberOfPages();
log.info("获取到PDF文件页码 {}",numPages);
Instant start = Instant.now();
for (int i = 0; i < numPages; i++) {
BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);
imgList.add(image);
}
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
log.info("分解PDF Time elapsed: " + duration.toMillis() + " milliseconds");
pdfDoc.close();
return imgList;
}
将图片切分成一个List<BufferedImage>列表之后,接下来的操作就是将这个列表转换成对应的图片进行存储。
/**
* PDF分解图片文件
*
* @param pdf pdf文件
* @param outDir 输出文件夹
*/
public static List<Map<String,Object>> cutPNG(File pdf, File outDir) throws IOException {
log.info("PDF分解图片工作开始");
String pdfName = pdf.getName();
List<Map<String,Object>> fileNameList = new ArrayList<>();
List<BufferedImage> list = getImgList(pdf);
log.info(pdf.getName() + " 一共发现了 " + list.size() + " 页");
FileUtils.cleanDir(outDir);
Instant start = Instant.now();
String outDirPath = outDir.getAbsolutePath();
String name = outDir.getCanonicalPath();
log.info("获取输出文件路径 {}",outDirPath);
for (int i = 0; i < list.size(); i++) {
Map<String,Object> fileMap = new HashMap<>();
String format = StringUtils.format("{}/{}/{}.png", DateUtils.datePath(),pdfName.substring(0,pdfName.lastIndexOf(".")).trim()+"_pngs",i+1);
IMGUtils.saveImageToFile(list.get(i), outDirPath + File.separator + (i + 1) + ".png");
String pathFileName = FileUploadUtils.getPathFileName(RuoYiConfig.getUploadPath(), format);
fileMap.put("pageNumber",i+1);
fileMap.put("filePath",RuoYiConfig.getImageProxyPath()+pathFileName);
fileNameList.add(fileMap);
}
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
log.info("保存图片 Time elapsed: " + duration.toMillis() + " milliseconds");
log.info("PDF分解图片工作结束,一共分解出" + list.size() + "个图片文件,保存至:" + outDir.getAbsolutePath());
return fileNameList;
}
保存图像到指定路径
/**
* 保存图像到指定文件
* @param image 图像
* @param filePath 文件路径
*/
public static void saveImageToFile(BufferedImage image, String filePath) throws IOException {
FileUtils.saveDataToFile(filePath,getBytes(image));
}
将数据保存到指定的路径
/**
* 将数据保存到指定文件路径
*/
public static void saveDataToFile(String filePath,byte[] data) throws IOException {
File file = new File(filePath);
if(!file.exists()){
file.createNewFile() ;
}
FileOutputStream outStream = new FileOutputStream(file);
outStream.write(data);
outStream.flush();
outStream.close();
}
最终通过这样的操作可以让PDF转换换成一张一张的图片。然后通过getList的方式去加载这些图片。就不会出现因为PDF文件过大而导致的加载缓慢的问题。