前段时间,老板接到一个需求,说是某个小程序打开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的依赖

org.apache.pdfbox pdfbox 2.0.24 com.itextpdf itextpdf 5.5.13.2

编写一个接收PDF文件并且进行拆分的方法

/** * 将PDF文档拆分成图像列表 * * @param pdf PDF文件 */ private static List getImgList(File pdf) throws IOException { PDDocument pdfDoc = PDDocument.load(pdf); log.info("加载PDF文件成功"); List 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列表之后,接下来的操作就是将这个列表转换成对应的图片进行存储。

/** * PDF分解图片文件 * * @param pdf pdf文件 * @param outDir 输出文件夹 */ public static List> cutPNG(File pdf, File outDir) throws IOException { log.info("PDF分解图片工作开始"); String pdfName = pdf.getName(); List> fileNameList = new ArrayList<>(); List 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 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文件过大而导致的加载缓慢的问题。