1.引子
当我们写一个web程序时,经常会遇到将某个特定页面的内容导出成报告的功能。本文将实现利用Java 8,Spring Boot, Wkhtmltopdf, Thymeleaf将HTML页面导出成PDF文本。
2.总纲
在我们实现具体功能前,我们先看看创建一个PDF文档的过程大约分三步走:
a) 浏览器(服务端)发送一个HTTP请求到我们的后台程序,并说明HTML页面的url和所创建的PDF文档的文件名;
b) 写Java后台代码调用并 wkhtmltopdf 命令行工具以读取HTML文档并将其转为PDF文档;
c) Java后台程序读取转好的PDF文档,并将其返回到到浏览器端。
在开始前我们之先,先安装wkhtmltopdf
3.技术实现
1. 安装Wkhtmltopdf
首先我们需要安装wkhtmltopdf命令行工具。我们可以去其官网选择对应的操作系统版本下载并安装(本文作者安装的是windows-64bit版本)
官网下载地址:https://wkhtmltopdf.org/downloads.html
如果你用的是 macOS 可以利用Homebrew进行wkhtmltopdf的安装。只要输入如下命令行即可完成安装:
brew install Caskroom/cask/wkhtmltopdf
2. 配置环境变量
我的Wkhtmltopdf是默认安装路径’C:\ProgramFiles\wkhtmltopdf’
计算机-属性-高级系统设置-环境变量-系统变量-Path添加wkhtmltopdf的路径,如下图所示:
配置完Path后我们就可以去写Java代码啦。
开发环境与工具:
a) Spring Boot 1.4.3 REALEASE
b) Thymeleaf
c) Maven 3.3
d) Eclipse oxygen
3. 项目最终结构
4. 项目依赖 pom.xml
4.0.0
org.thinkingingis
spring-boot-htmltopdf
0.0.1-SNAPSHOT
jar
spring-boot-htmltopdf
http://maven.apache.org
org.springframework.boot
spring-boot-starter-parent
1.4.3.RELEASE
UTF-8
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-devtools
true
org.webjars
bootstrap
3.3.7
org.springframework.boot
spring-boot-maven-plugin
5. 从HTML页面创建PDF文档
5.1 Model层
在我们具体实现HTML转PDF功能之前,我们需要创建一个类用于存放wkhtmltopdf所需要的参数信息。
我们创建PdfFileRequest .java 类,包含两个属性
filename 属性是我们所创建PDF文档的文件名
sourceHtmlUrl 属性是HTML文档的URL地址
PdfFileRequest .java
package org.thinkingingis.model;
public class PdfFileRequest {
private String fileName;
private String sourceHtmlUrl;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getSourceHtmlUrl() {
return sourceHtmlUrl;
}
public void setSourceHtmlUrl(String sourceHtmlUrl) {
this.sourceHtmlUrl = sourceHtmlUrl;
}
}
5.2 Service层
PdfFileCreator.java
package org.thinkingingis.service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.thinkingingis.model.PdfFileRequest;
@Service
public class PdfFileCreator {
private static final Logger LOGGER = LoggerFactory.getLogger(PdfFileCreator.class);
public void writePdfToResponse(PdfFileRequest fileRequest, HttpServletResponse response) {
String pdfFileName = fileRequest.getFileName();
requireNotNull(pdfFileName, "The file name of the created PDF must be set");
requireNotEmpty(pdfFileName, "File name of the created PDF cannot be empty");
String sourceHtmlUrl = fileRequest.getSourceHtmlUrl();
requireNotNull(sourceHtmlUrl, "Source HTML url must be set");
requireNotEmpty(sourceHtmlUrl, "Source HTML url cannot be empty");
List pdfCommand = Arrays.asList(
"wkhtmltopdf",
sourceHtmlUrl,
"-"
);
ProcessBuilder pb = new ProcessBuilder(pdfCommand);
Process pdfProcess;
try {
pdfProcess = pb.start();
try(InputStream in = pdfProcess.getInputStream()) {
writeCreatedPdfFileToResponse(in, response);
waitForProcessBeforeContinueCurrentThread(pdfProcess);
requireSuccessfulExitStatus(pdfProcess);
setResponseHeaders(response, fileRequest);
}
catch (Exception ex) {
writeErrorMessageToLog(ex, pdfProcess);
throw new RuntimeException("PDF generation failed");
}
finally {
pdfProcess.destroy();
}
}
catch (IOException ex) {
throw new RuntimeException("PDF generation failed");
}
}
private void requireNotNull(String value, String message) {
if (value == null) {
throw new IllegalArgumentException(message);
}
}
private void requireNotEmpty(String value, String message) {
if (value.isEmpty()) {
throw new IllegalArgumentException(message);
}
}
private void writeCreatedPdfFileToResponse(InputStream in, HttpServletResponse response) throws IOException {
OutputStream out = response.getOutputStream();
IOUtils.copy(in, out);
out.flush();
}
private void waitForProcessBeforeContinueCurrentThread(Process process) {
try {
process.waitFor(2, TimeUnit.SECONDS);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
private void requireSuccessfulExitStatus(Process process) {
if (process.exitValue() != 0) {
throw new RuntimeException("PDF generation failed");
}
}
private void setResponseHeaders(HttpServletResponse response, PdfFileRequest fileRequest) {
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileRequest.getFileName() + "\"");
}
private void writeErrorMessageToLog(Exception ex, Process pdfProcess) throws IOException {
LOGGER.error("Could not create PDF because an exception was thrown: ", ex);
LOGGER.error("The exit value of PDF process is: {}", pdfProcess.exitValue());
String errorMessage = getErrorMessageFromProcess(pdfProcess);
LOGGER.error("PDF process ended with error message: {}", errorMessage);
}
private String getErrorMessageFromProcess(Process pdfProcess) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(pdfProcess.getErrorStream()));
StringWriter writer = new StringWriter();
String line;
while ((line = reader.readLine()) != null) {
writer.append(line);
}
return writer.toString();
}
catch (IOException ex) {
LOGGER.error("Could not extract error message from process because an exception was thrown", ex);
return "";
}
}
}
5.3 REST API实现
PdfController.java
package org.thinkingingis.controller;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.thinkingingis.model.PdfFileRequest;
import org.thinkingingis.service.PdfFileCreator;
@RestController
public class PdfController {
private final PdfFileCreator pdfFileCreator;
@Autowired
public PdfController(PdfFileCreator pdfFileCreator) {
this.pdfFileCreator = pdfFileCreator;
}
@RequestMapping(value = "/api/pdf", method = RequestMethod.POST)
public void createPdf(@RequestBody PdfFileRequest fileRequest, HttpServletResponse response) {
pdfFileCreator.writePdfToResponse(fileRequest, response);
}
}
PrintPdfController.java
package org.thinkingingis.controller;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import org.thinkingingis.model.PdfFileRequest;
@Controller
@RequestMapping("/print")
public class PrintPdfController {
private final RestTemplate restTemplate;
public PrintPdfController(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@RequestMapping(value = "/pdf", method = RequestMethod.GET)
public void createPdfFromUrl(HttpServletResponse response) {
PdfFileRequest fileRequest = new PdfFileRequest();
fileRequest.setFileName("index.pdf");
fileRequest.setSourceHtmlUrl("http://blog.csdn.net/gisboygogogo/article/");
byte[] pdfFile = restTemplate.postForObject("http://localhost:8080/api/pdf",
fileRequest,
byte[].class
);
writePdfFileToResponse(pdfFile, "index.pdf", response);
}
private void writePdfFileToResponse(byte[] pdfFile, String fileName, HttpServletResponse response) {
try (InputStream in = new ByteArrayInputStream(pdfFile)) {
OutputStream out = response.getOutputStream();
IOUtils.copy(in, out);
out.flush();
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
}
catch (IOException ex) {
throw new RuntimeException("Error occurred when creating PDF file", ex);
}
}
}
输入 http://localhost:8080/index
点击 ‘打印’ 就会将 ‘http://blog.csdn.net/gisboygogogo/article/’ 页面转成PDF文档,保存即可。
下载源码
如果你觉得本文对你有帮助,是可以赞赏一下的:)
如遇到问题,欢迎通过公众号留言给作者,以便共同探讨。
微信公众号: