Java实现Html转PDF

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.   项目最终结构

Java实现Html转PDF_第1张图片

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);
	}
	

}

5.4   Controller层

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);
        }
    }

}

6. 启动项目

输入 http://localhost:8080/index

Java实现Html转PDF_第2张图片

点击 ‘打印’ 就会将 ‘http://blog.csdn.net/gisboygogogo/article/’ 页面转成PDF文档,保存即可。

Java实现Html转PDF_第3张图片

至此,HTML转PDF就实现啦。

下载源码


如果你觉得本文对你有帮助,是可以赞赏一下的:)

Java实现Html转PDF_第4张图片


如遇到问题,欢迎通过公众号留言给作者,以便共同探讨。

邮箱:[email protected]

微信公众号:

Java实现Html转PDF_第5张图片

你可能感兴趣的:(spring,boot)