wkhtmltopdf+itext實現html生成pdf文件的打印下載(適用於linux及windows)


目中遇到個根據html轉Java的功能,在java中我們itext可以快速的實現pdf打印下載的功能,在itext中我們一般有以下三中方式實現

  1. 配置pdf模板,通過Adobe Acrobat 來設置域最后通過代碼將數據填充進去
  2. 通過FreeMarker或thymeleaf配置html模板填充數據
  3. Jsoup+XMLWorkerHelper對於上述的三種方式,我簡述下我的體驗:第一種方式對於入門簡單,如果我們需求中的pdf文件是表格或者報表的樣式還是很好實現的,但如果遇到要求和html樣式一致的話就基本歇菜了。第二張方式比較理想,在項目基本完工的情況下再去改成模板不太現實,而且我只是個做后端的還沒那么大的能耐,不選。第三種方式我也嘗試了下,對於一些簡單網頁比如說博客,我們通過Jsoup可以獲取到文章的內容和html樣式,網上的demo也是通過博客來舉例的,效果不錯,但我看了下博客內容的基本樣式就是一些基本的div、p、li這些標簽,但在我實際的項目中樣式比較復雜,生成的pdf無法打開。而且上面的三種方式都有一個致命的缺點:那就是需要去針對模板。現實的項目中我們可能多個地方需要實現pdf打印的功能,樣式模板的配置將占用很大的開發工作量。第三種方式雖說不需要模板,但通過網頁的標簽去獲取數據也會變得多樣化,完全不能實現方法的復用。當然,itext也不是一無是處,在這個項目里面我們還是需要用到itext去生成水印、加密、不可編輯等一系列細致的活。

這里我想要的理想的pdf打印效果是:最少的改動,實現pdf的打印的效果與我請求的url的html樣式一致。那就要使用到我們今天的主角wkhtmltopdf,完美的解決了我的難題,windows下解壓即用,linux下需要安裝三個依賴和一個宋體文字,下面讓我們一起coding吧~

詳細步驟:

  1. windows下wkhtmltopdf的下載安裝測試     
  2. linux下的wkhtmltopdf的下載安裝測試
  3. 配置pdf生成的路徑解讀
  4. 自動調用java中Runtime.getRuntime()運行wkhtmltopdff插件的具體實現

 

windows下安裝測試

通過https://wkhtmltopdf.org/downloads.html下載並解壓縮wkhtmltox文件,通過cmd 命令找到 bin下面的wkhtmltopdf.exe 文件,執行wkhtmltopdf.exe +http url + pdf路徑及文件名

http://resource.tianluzhi.com/view/windows.mp4  windows演示地址

Linux下安裝測試

wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4.tar.bz2 用wget下載wkhtmltopdf安裝,這里我們采用的是0.12.4的版本,沒有和上面windows的0.12.5保持一致原因是沒有0.12.5的tar包,rpm的不習慣。安裝完之后我們參考上面windows的方式運行,運行過程中在centos 7中會遇到缺少依賴的問題。執行yum -y install libXrender* libfontconfig* libXext* 安裝后重新運行測試,還會遇到中文亂碼問題,這這里我們還需將windows下的simsun字體放置到 /usr/share/fonts/ ,windows字體的存放位置為C:\Windows\Fonts,win10部分用戶的simsun字體是沒效果的,這里給大家提供一個下載  http://resource.tianluzhi.com/dev/SIMSUN.TTC ,linux下一般就這些問題。這里就不做演示了,我們開始真正的coding吧。

 

配置pdf生成的路徑解讀


# linux path
#wkhtmltopdfPath 指向wkhtmltopdfPath安裝的bin目錄下wkhtmltopdf
wkhtmltopdfPath=/home/wkhtmltox/bin/wkhtmltopdf
#pdf
pdfLocationPathForLinux=/home/prd-gehcpp-01/otoResources/pdf/
waterPdfLocationPathForLinux=/home/prd-gehcpp-01/otoResources/waterPdf/

# windows path
#photoLocationPath=D://otoResources//photo//
#pdf文件路徑
pdfLocationPath= D://otoResources//pdf//
#添加水印后文件路徑
waterPdfLocationPath= D://otoResources//waterPdf//

上面配置中我們分別配置了linux和windows兩套路徑環境,在我們代碼中我們會自動判別當前服務器的操作系統,然后選擇相關的路徑。下面我們分別解讀下各個配置參數

wkhtmltopdfPath :指向wkhtmltopdfPath安裝的bin目錄下wkhtmltopdf,這段地址其實就是為了執行wkhtmltopdf插件而添加的,這里我們用全路徑,這樣在linux的環境下我們就不需要去配置環境了,另外細心的朋友會發現windows的沒有配置,這是因為我已經將插件放到了資源文件夾下,
然后通過Java程序去拼接了一個完整的路徑,具體我們往下看。
pdfLocationPathForLinux/photoLocationPath : 這兩個路徑分別指向了linux和Windows下的pdf生成的初始文件,如果我們不需要對pdf進行二次操作的話,在這里我們就可以把結果返回給用戶。
waterPdfLocationPathForLinux/waterPdfLocationPath :這個路徑是指經過加工后的路徑,比如水印、加密、不可編輯等,上面的路徑相當於是模子,這個才是最終返回客戶的結果。 

代碼實現

部分代碼參考 https://www.cnblogs.com/xionggeclub/p/6144241.html

controller

/**
*
* @param response
* @param httpUrl 需要生成pdf的請求地址
* @param downName 生成pdf后的文件名
* @return
*/
@RequestMapping("pdfConvert")
@ResponseBody
public Response htmlToPdf(HttpServletResponse response ,@RequestParam String httpUrl,String downName) {
return geFileService.htmlToPdf(response,httpUrl,downName);
}

service 

   Response htmlToPdf(HttpServletResponse response, String httpUrl,String downName);

impl

 
         
package com.ge.service.impl;

import com.common.exception.MyException;
import com.ge.service.GeFileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import pdf.HtmlToPdfUtil;
import utils.Response;
import utils.ResultHttpStatus;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

import static utils.PropertiesUtil.getProperty;

/**
* @Author: gaofeng_peng
* @Date: 2018/6/13 13:47
*/
@Service("GeFileService")
public class FileServiceImpl implements GeFileService {
private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

public static String pdfLocationPath;

public static String waterPdfLocationPath;

public static String systemOs;
/**
* 根據服務器系統設置pdf文件路徑
*/
static {
systemOs = System.getProperty("os.name");
if (systemOs.toLowerCase().startsWith("win")) {
pdfLocationPath = getProperty("pdfLocationPath");
waterPdfLocationPath = getProperty("waterPdfLocationPath");
} else {
pdfLocationPath = getProperty("pdfLocationPathForLinux");
waterPdfLocationPath = getProperty("waterPdfLocationPathForLinux");
}
}



@Override
public Response htmlToPdf(HttpServletResponse res, String httpUrl, String downName) {
Response response = new Response(ResultHttpStatus.OK.getValue(), ResultHttpStatus.OK.getName());
//判斷路徑是否存在,不存在則創建
File fileDir = new File(pdfLocationPath);
File waterFileDir = new File(waterPdfLocationPath);
if (!fileDir.exists()) {
fileDir.setWritable(true);
fileDir.mkdirs();
}
if (!waterFileDir.exists()) {
waterFileDir.setWritable(true);
waterFileDir.mkdirs();
}
String fileName = UUID.randomUUID().toString() + ".pdf";
String source = pdfLocationPath + fileName;
String outPath = waterPdfLocationPath + fileName;
try {
//調用HtmlToPdfUtilz中的convent的方法使html生成pdf
boolean isSuccess = HtmlToPdfUtil.convert(httpUrl, source,systemOs);
if (isSuccess) {
//水印加密
// HtmlToPdfUtil.setWaterMarkForPDF(source,outPath,"");
download(res, downName, fileName);
} else {
throw new MyException("pdf下載異常");
}

} catch (Exception e) {
response.setMsg(e.getMessage());
response.setStatus(ResultHttpStatus.INTERNAL_ERROR.getValue());
}
return response;
}

/**
* 響應下載
* @param resp
* @param downloadName 下載的pdf文件名
* @param fileName 系統中真正的文件名
*/
    public void download(HttpServletResponse resp, String downloadName, String fileName) {
try {
downloadName = new String(downloadName.getBytes("GBK"), "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String realPath = pdfLocationPath;
String path = realPath + fileName;
File file = new File(path);
resp.reset();
resp.setContentType("application/octet-stream");
resp.setCharacterEncoding("utf-8");
resp.setContentLength((int) file.length());
resp.setHeader("Content-Disposition", "attachment;filename=" + downloadName + ".pdf");
byte[] buff = new byte[1024];
BufferedInputStream bis = null;
OutputStream os = null;
try {
os = resp.getOutputStream();
bis = new BufferedInputStream(new FileInputStream(file));
int i = 0;
while ((i = bis.read(buff)) != -1) {
os.write(buff, 0, i);
os.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
//完成后刪除本地pdf文件
file.delete();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}
 

 

HtmlToPdfUtil類

package pdf;


import com.itextpdf.text.BaseColor;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.apache.commons.lang3.StringUtils;
import utils.PropertiesUtil;

import java.io.FileOutputStream;

/**
 * @Author: gaofeng_peng
 * @Date: 2018/7/5 13:13
 */
public class HtmlToPdfUtil {
    //wkhtmltopdf在系統中的路徑
    // private static final String toPdfTool = "D:\\wkhtmltox\\bin\\wkhtmltopdf.exe";


    /**
     * html轉pdf
     *
     * @param srcPath    html路徑
     * @param pdfLocationPath pdf保存路徑
     * @return 轉換成功返回true
     */
    public static boolean convert(String srcPath, String pdfLocationPath, String systemOs) throws Exception {
        String toPdfTool="";
        if (systemOs.toLowerCase().startsWith("win")) {
             toPdfTool = HtmlToPdfUtil.class.getClassLoader().getResource("wkhtmltox/bin/wkhtmltopdf.exe").getPath();
        }else {
            toPdfTool= PropertiesUtil.getProperty("wkhtmltopdfPath");
        }
        StringBuilder cmd = new StringBuilder();
        cmd.append(toPdfTool);
        cmd.append(" ");
        cmd.append("  --background");
        cmd.append(" --debug-javascript");
        cmd.append("  --header-line");//頁眉下面的線
        cmd.append(" --header-spacing 10 ");//    (設置頁眉和內容的距離,默認0)

        cmd.append(srcPath);
        cmd.append(" ");
        cmd.append(pdfLocationPath);

        boolean result = true;
        try {
            Process proc = Runtime.getRuntime().exec(cmd.toString());
            HtmlToPdfInterceptor error = new HtmlToPdfInterceptor(proc.getErrorStream());
            HtmlToPdfInterceptor output = new HtmlToPdfInterceptor(proc.getInputStream());
            error.start();
            output.start();
            proc.waitFor();
        } catch (Exception e) {
            result = false;
            e.printStackTrace();
        }
        return result;
    }

    /**
* 生成水印,加密在這里實現 * @param sourceFilePath 源文件路徑 * @param fileWaterMarkPath 水印生成文件路徑 * @throws Exception */ public static void setWaterMarkForPDF(String sourceFilePath, String fileWaterMarkPath, String waterMarkName) throws Exception { // String waterPath = Class.class.getClass().getResource("/1.png").getPath(); PdfReader reader = new PdfReader(sourceFilePath); PdfStamper stamp = new PdfStamper(reader, new FileOutputStream(fileWaterMarkPath)); int total = reader.getNumberOfPages() + 1; PdfContentByte under = null; //Image img = Image.getInstance(waterPath); //img.setAbsolutePosition(30, 100);//坐標 // img.setRotation(-20);//旋轉 弧度 //img.setRotationDegrees(-35);//旋轉 角度 //img.scaleAbsolute(200,100);//自定義大小 //img.scalePercent(100);//依照比例縮放 BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED); if (StringUtils.isBlank(waterMarkName)) { waterMarkName = "Tech Mahindra"; } int j = waterMarkName.length(); char c = 0; int rise = 0; for (int i = 1; i < total; i++) // 每一頁都加水印 { rise = 500; under = stamp.getUnderContent(i); // 添加圖片 // under.addImage(img); under.beginText(); under.setColorFill(BaseColor.BLACK); under.setFontAndSize(bf, 30); // 設置水印文字字體傾斜 開始 if (j >= 15) { under.setTextMatrix(200, 120); for (int k = 0; k < j; k++) { under.setTextRise(rise); c = waterMarkName.charAt(k); under.showText(c + ""); rise -= 20; } } else { under.setTextMatrix(180, 100); for (int k = 0; k < j; k++) { under.setTextRise(rise); c = waterMarkName.charAt(k); under.showText(c + ""); rise -= 18; } } // 字體設置結束 under.endText(); } stamp.close(); reader.close(); } }
HtmlToPdfInterceptor類
package pdf;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
 * @Author: gaofeng_peng
 * @Date: 2018/7/5 13:17
 */
public class HtmlToPdfInterceptor extends Thread {
    private InputStream is;

    public HtmlToPdfInterceptor(InputStream is){
        this.is = is;
    }

    public void run(){
        try{
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line.toString()); //輸出內容
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
水印部分的功能被我注釋了,實際效果是可行的,只是本人設計的太丑,沒臉見人所以注釋。后期改良再說,有興趣的小伙伴可以自己改良。至此pdf打印下載的功能就完成了。需要注意的是html最好用原生的,使用less預處理可能會導致樣式的丟失。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM