您當前的位置:首頁 > 繪畫

Java後臺生成pdf檔案

作者:由 夢想實現家 發表于 繪畫時間:2021-06-08

前段時間因為相關業務需求需要後臺生成pdf檔案,對於一直crud的程式設計師來說,這無疑是需要一定時間來做技術預研的。下面根據我的實踐經驗總結一下我是如何使用java生成pdf檔案的。

根據spring mvc的設計模式,理論上來說,我們可以把pdf檔案視作一個View檢視,那麼整個mvc模型如下圖:

Java後臺生成pdf檔案

如果按照上圖所示,那麼我們要編寫一個pdf檢視解析器,這無疑是一個有難度的事情。但是把思路轉換一下,我們可以先把model轉換成html,再透過html轉換成pdf是不是會更容易一點?

Java後臺生成pdf檔案

1。如何把model轉換成html?

這個問題spring mvc已經替我們解決了,thymeleaf的實現無非就是一個活生生的model轉換成html的例子。

2。html如何轉換成pdf?

IText

FlyingSaucer

WKHtmlToPdf

pd4ml

跨平臺性

跨平臺

跨平臺

跨平臺

跨平臺

是否安裝軟體

需安裝WKHtmlToPdf

是否收費

免費

免費

免費

收費

轉換Html效率

速度快

未測

速度慢。相比URL來說,效率較慢。能忽略一些html語法或資源是否存在問題。

速度快。部分CSS樣式不支援。

轉換Html效果

存在樣式失真問題。對html語法有一定要求

存在樣式失真問題。對html語法有較高要求。

失真情況較小,大部分網頁能按Chome瀏覽器顯示的頁面轉換

部分CSS樣式有問題。

轉換URL效率

未測

未測

效率不是特別高

未測

轉換URL效果

未測

未測

部分網頁由於其限制,或將出現html網頁不完整。

未測

優點

不需安裝軟體、轉換速度快

不需安裝軟體、轉換速度快

生成PDF質量高

不需要安裝軟體、轉換速度快

缺點

對html標籤嚴格,少一個結束標籤就會報錯;伺服器需要安裝字型

對html標籤嚴格,少一個結束標籤就會報錯;伺服器需要安裝字型

需要安裝軟體、時間效率不高

對部分CSS樣式不支援。

圖片

表格

連結

中文

特殊字元

整體樣式

速度

IText

支援

支援

支援

支援

支援

失真問題

FlyingSaucer

未知

未知

未知

未知

未知

未知

WKHtmlToPdf

支援

支援

支援

支援

支援

很好

pd4ml

支援

支援

支援

支援

支援

失真問題

對比以上各類實現:

1。WKHtmlToPdf因為轉換速度慢、需要安裝軟體的缺點被暫時排除在外;pd4ml因為是收費的,並且同樣存在一些常見的樣式失真問題,直接排除;

2。剩下的就是在IText和FlyingSaucer的實現方案中做選擇,對比之下,選擇IText作為我們的最終實現方案

【相關依賴】

com。itextpdf

itextpdf

5。5。13。2

com。itextpdf

itext-asian

5。2。0

com。itextpdf。tool

xmlworker

5。5。13。2

org。xhtmlrenderer

flying-saucer-pdf-itext5

9。1。22

【程式碼實現】

import

com。itextpdf。text。pdf。BaseFont

import

com。zx。silverfox。common。exception。GlobalException

import

lombok。extern。slf4j。Slf4j

import

org。apache。commons。lang3。StringUtils

import

org。xhtmlrenderer。pdf。ITextFontResolver

import

org。xhtmlrenderer。pdf。ITextRenderer

import

java。io。File

import

java。io。FileOutputStream

import

java。io。OutputStream

@Slf4j

public

final

class

HtmlUtil

{

private

HtmlUtil

()

{

}

// 字型路徑,放在資源目錄下

private

static

final

String

FONT_PATH

=

“classpath:simsun。ttc”

public

static

void

file2Pdf

File

htmlFile

String

pdfFile

throws

GlobalException

{

try

OutputStream

os

=

new

FileOutputStream

pdfFile

))

{

String

url

=

htmlFile

toURI

()。

toURL

()。

toString

();

ITextRenderer

renderer

=

new

ITextRenderer

();

renderer

setDocument

url

);

// 解決中文支援

ITextFontResolver

fontResolver

=

renderer

getFontResolver

();

// 獲取字型絕對路徑,ApplicationContextUtil是我自己寫的類

String

fontPath

=

ApplicationContextUtil

classpath

FONT_PATH

);

fontResolver

addFont

fontPath

BaseFont

IDENTITY_H

BaseFont

EMBEDDED

);

renderer

layout

();

renderer

createPDF

os

);

}

catch

Exception

e

{

// 丟擲自定義異常

throw

GlobalException

newInstance

e

);

}

}

public

static

void

html2Pdf

String

html

String

pdfFile

throws

GlobalException

{

String

pdfDir

=

StringUtils

substringBeforeLast

pdfFile

“/”

);

File

file

=

new

File

pdfDir

);

if

(!

file

exists

())

{

file

mkdirs

();

}

try

OutputStream

os

=

new

FileOutputStream

pdfFile

))

{

ITextRenderer

renderer

=

new

ITextRenderer

();

renderer

setDocumentFromString

html

);

// 解決中文支援

ITextFontResolver

fontResolver

=

renderer

getFontResolver

();

// 獲取字型絕對路徑,ApplicationContextUtil是我自己寫的類

String

fontPath

=

ApplicationContextUtil

classpath

FONT_PATH

);

fontResolver

addFont

fontPath

BaseFont

IDENTITY_H

BaseFont

EMBEDDED

);

renderer

layout

();

renderer

createPDF

os

);

}

catch

Exception

e

{

// 丟擲自定義異常

throw

GlobalException

newInstance

e

);

}

}

}

【字型檔案】

simsun.tcc 密碼:rzw4

以上實現就完成了html轉換成pdf的功能,後續就是model轉html:

因為我使用的是springboot,所以直接使用以下依賴。小夥伴可以根據自身專案具體情況使用對應的依賴

org。springframework。boot

spring-boot-starter-thymeleaf

【程式碼實現】

import

com。google。common。collect。Maps

import

com。zx。silverfox。common。exception。GlobalException

import

com。zx。silverfox。common。util。HtmlUtil

import

org。thymeleaf。TemplateEngine

import

org。thymeleaf。context。Context

import

java。util。Map

public

abstract

class

AbstractTemplate

{

// 使用thymeleaf模版引擎

private

TemplateEngine

engine

// 模版名稱

private

String

templateName

private

AbstractTemplate

()

{}

public

AbstractTemplate

TemplateEngine

engine

String

templateName

{

this

engine

=

engine

this

templateName

=

templateName

}

/**

* 模版名稱

*

* @return

*/

protected

String

templateName

(){

return

this

templateName

}

/**

* 所有的引數資料

*

* @return

*/

private

Map

<

String

Object

>

variables

(){

Map

<

String

Object

>

variables

=

Maps

newHashMap

();

// 對應html模版中的template變數,取值的時候就按照“${template。欄位名}”格式,可自行修改

variables

put

“template”

this

);

return

variables

};

/**

* 解析模版,生成html

*

* @return

*/

public

String

process

()

{

Context

ctx

=

new

Context

();

// 設定model

ctx

setVariables

variables

());

// 根據model解析成html字串

return

engine

process

templateName

(),

ctx

);

}

public

void

parse2Pdf

String

targetPdfFilePath

throws

GlobalException

{

String

html

=

process

();

// 透過html轉換成pdf

HtmlUtil

html2Pdf

html

targetPdfFilePath

);

}

}

建立模版引擎

@Configuration

public

class

TemplateEngineConfig

{

// 注入TemplateEngine模版引擎

@Bean

public

TemplateEngine

templateEngine

(){

ClassLoaderTemplateResolver

resolver

=

new

ClassLoaderTemplateResolver

();

// 設定模版字首,相當於需要在資原始檔夾中建立一個html2pdfTemplate資料夾,所有的模版都放在這個資料夾中

resolver

setPrefix

“/html2pdfTemplate/”

);

// 設定模版字尾

resolver

setSuffix

“。html”

);

resolver

setCharacterEncoding

“UTF-8”

);

// 設定模版模型為HTML

resolver

setTemplateMode

“HTML”

);

TemplateEngine

engine

=

new

TemplateEngine

();

engine

setTemplateResolver

resolver

);

return

engine

}

}

因為我們的依賴是基於springboot的,所以為了不讓spring-boot-starter-thymeleaf自動配置,我們需要排除相關的配置類。不想這樣做的小夥伴可使用thymeleaf其他依賴,原理上都一樣。

@SpringBootApplication

exclude

=

ThymeleafAutoConfiguration

class

至此,所有的技術準備都做好了,如何使用我們編寫好的程式碼實現model轉換pdf檔案呢?

【示例】

import

lombok。Data

import

org。thymeleaf。TemplateEngine

import

java。util。List

@Data

public

class

Model

extends

AbstractTemplate

{

// 建構函式

public

Model

TemplateEngine

engine

String

templateName

{

super

engine

templateName

);

}

// 名稱

private

String

name

// 保險記錄

private

List

<

InsuranceInfo

>

insuranceInfos

}

@Data

public

class

InsuranceInfo

{

/** 出險日期 */

private

String

expirationDate

/** 描述 */

private

String

description

}

【報告模版.html】

<!DOCTYPE html

PUBLIC “-//W3C//DTD XHTML 1。0 Transitional//EN” “http://www。w3。org/TR/xhtml1/DTD/xhtml1-transitional。dtd”>

<

html

lang

=

“en”

xmlns

=

“http://www。w3。org/1999/xhtml”

xmlns:th

=

“http://www。thymeleaf。org”

>

<

head

>

<

meta

charset

=

“UTF-8”

/>

<

meta

http-equiv

=

“X-UA-Compatible”

content

=

“IE=edge”

/>

<

meta

name

=

“viewport”

content

=

“width=device-width, initial-scale=1。0”

/>

<

title

>

報告模版

title

>

<

style

>

<!

——

編寫css

——

>

style

>

head

>

<!—— 引入字型 ——>

<

body

style

=

“font-family: SimSun;”

>

<

div

class

=

“main”

>

報告模版

div

>

<

div

class

=

“main2”

>

<

span

class

=

“heng”

th:text

=

“${template。name}”

>

template。name

span

>

<

table

class

=

“tabletype”

>

<

thead

>

<

tr

class

=

“recordhead”

>

<

th

class

=

“leaf”

style

=

“width: 80px;”

>

出險日期

th

>

<

th

class

=

“leaf”

style

=

“width: 80px;”

>

描述

th

>

tr

>

thead

>

<

tbody

th:if

=

“${template。insuranceInfos}”

>

<

tr

th:each

=

“m,var : ${template。insuranceInfos}”

>

<

th

class

=

“leaf”

th:text

=

“${m。expirationDate}”

>

th

>

<

th

class

=

“leaf”

th:text

=

“${m。description}”

>

th

>

tr

>

tbody

>

table

>

div

>

body

>

html

>

【測試程式碼】

@Autowired

private

TemplateEngine

engine

public

void

test

()

throws

Exception

{

// 建立model,需要指定模版引擎和具體的模版,“報告模版”指的是資源目錄下/html2pdfTemplate/報告模版。html檔案。如果是springboot專案,那麼就是在resources資料夾下面

Model

model

=

new

Model

engine

“報告模版”

);

model

setName

“名稱”

);

List

<

InsuranceInfo

>

insuranceInfos

=

new

ArrayList

<>();

InsuranceInfo

record1

=

new

InsuranceInfo

();

record1

setExpirationDate

“2021-01-19”

);

record1

setDescription

“剎車失靈”

);

insuranceInfos

add

record1

);

InsuranceInfo

record2

=

new

InsuranceInfo

();

record2

setExpirationDate

“2021-03-06”

);

record2

setDescription

“擋風玻璃破裂”

);

insuranceInfos

add

record2

);

model

setInsuranceInfos

insuranceInfos

);

//生成pdf,指定目標檔案路徑

model

parse2Pdf

“/home/dev/桌面/test。pdf”

);

}

根據以上理論和實踐,我們已經達到了我們的目標,最終完成了資料轉換成PDF檔案的需求。

標簽: HTML  模版  PDF  Model  thymeleaf