我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
1 故事
故事是这样的,我们是一个商城系统,在输出快递单的时候,第三方给我们的一直是单个的面单。我们的仓库人员只能一张一张的打印,一张一张去贴。
效率低呀,还累。点一下打印出一张,点一下打印出一张。俺们就问第三方,你们有批量申请面单的接口吗?给到的回答是No。俺们就只能自己做了。其实这个不难,到也有不少坑。在就在这记个笔记,踩个脚印。
2 实现合并
集体的需求是这样的:
- 合并接口请求到的PDF
- 需要和传入参数顺序一样
- 输出到页面下载
2.1 代码示范
我先把代码留到这,然后一一解释
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
private static volatile Map<String, Object> readersMap = new HashMap<>();
@GetMapping("/pdf")
public void packingPrint(HttpServletResponse response) {
//给定两个pdf的链接
Map<String,String> map = new HashMap<>();
map.put("1","file:///C:/Users/Administrator/Downloads/test1.pdf");
map.put("2","file:///C:/Users/Administrator/Downloads/test2.pdf");
mergePdfFiles(map,response);
}
private void mergePdfFiles(Map<String, String> map, HttpServletResponse response) {
readersMap = new LinkedHashMap<>();
readersMap.putAll(map);
String now = LocalDateTime.now(ZoneId.of("Asia/Shanghai")).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String filename = "合并后_" + now + ".pdf";
try {
//读取pdf对象
CountDownLatch cdlatch = new CountDownLatch(map.entrySet().size());
ExecutorService executorPool = ThreadPoolUtils.getExecutorPool(4);
for (Map.Entry<String, String> stringEntry : map.entrySet()) {
executorPool.execute(() -> {
PdfReader reader = null;
try {
reader = new PdfReader(stringEntry.getValue());
readersMap.put(stringEntry.getKey(),reader);
cdlatch.countDown();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
cdlatch.await();
executorPool.shutdown();
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; fileName=" + new String(filename.getBytes("gb2312"), "ISO8859-1"));
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream());
document.open();
document.setPageSize(PageSize.A4.rotate());
PdfContentByte cb = writer.getDirectContent();
int pageOfCurrentReaderPDF = 0;
List<PdfReader> reader = new ArrayList<>();
for (Map.Entry<String, Object> stringObjectEntry : readersMap.entrySet()) {
reader.add((PdfReader) stringObjectEntry.getValue());
}
Iterator<PdfReader> iteratorPDFReader = reader.iterator();
while (iteratorPDFReader.hasNext()) {
PdfReader pdfReader = iteratorPDFReader.next();
while (pageOfCurrentReaderPDF < pdfReader.getNumberOfPages()) {
document.newPage();//创建新的一页
pageOfCurrentReaderPDF++;
PdfImportedPage page = writer.getImportedPage(pdfReader, pageOfCurrentReaderPDF);
cb.addTemplate(page, 0, 0);
}
pageOfCurrentReaderPDF = 0;
}
document.close();
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (DocumentException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
3 解释
我挑重点内容解释一下。当然也包含我遇到的坑
3.1 性能
测试的时候发现,这一步是特别慢的。当30个面单合并的时候,竟然要15秒,这也太久了。
reader = new PdfReader(stringEntry.getValue());
这是在读取需要合并的pdf,是整个代码块最慢的地方,要提高效率咋办,只能多线程了呗。又必须等到所有的pdf读取完成了,才能执行合并的操作,这样的话就用CountDownLatch实现如下:大概意思就是CountDownLatch会有一个初始的值,这里我设置的是需要合并的pdf的数量,当有一个线程读取完成之后,则这个数量减一,当都读取完成之后,这个值为0的时候,主线程才会继续执行。
CountDownLatch cdlatch = new CountDownLatch(map.entrySet().size());
ExecutorService executorPool = ThreadPoolUtils.getExecutorPool(4);
for (Map.Entry<String, String> stringEntry : map.entrySet()) {
executorPool.execute(() -> {
PdfReader reader = null;
try {
reader = new PdfReader(stringEntry.getValue());
readersMap.put(stringEntry.getKey(),reader);
cdlatch.countDown();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
cdlatch.await();
executorPool.shutdown();
readersMap是个加了volatile的全局变量,保证了主线程可以访问到各个子线程读取之后的pdf数据。readersMap是个LinkedHashMap,保证了顺序。
3.2 输出文件的格式
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; fileName=" + new String(filename.getBytes("gb2312"), "ISO8859-1"));
这两行里setContentType则规定了输出文件的格式为pdf,attachment表示为页面下载。fileName则为文件名,后边的参数则为中文显示提供了支持。
3.3 合并PDF
接下来就是紧张刺激的合并的过程了,整个过程没啥注意的,读取然后写入,如有需要照抄即可。
document.setPageSize(PageSize.A4.rotate());
值得注意的是setPageSize的时候,rotate()方法会改变纸张的方向,不需要转换方向,去掉即可。
4 效果
之前为两个文件
合并后为: