Java後臺生成pdf檔案
前段時間因為相關業務需求需要後臺生成pdf檔案,對於一直crud的程式設計師來說,這無疑是需要一定時間來做技術預研的。下面根據我的實踐經驗總結一下我是如何使用java生成pdf檔案的。
根據spring mvc的設計模式,理論上來說,我們可以把pdf檔案視作一個View檢視,那麼整個mvc模型如下圖:
如果按照上圖所示,那麼我們要編寫一個pdf檢視解析器,這無疑是一個有難度的事情。但是把思路轉換一下,我們可以先把model轉換成html,再透過html轉換成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檔案的需求。