Word簡(jiǎn)歷文檔自動(dòng)化生成新方案
共 12204字,需瀏覽 25分鐘
·
2024-05-11 18:13
來源:juejin.cn/post/7101493026899853325
推薦:https://t.zsxq.com/Kpajy
好看的簡(jiǎn)歷并不是千篇一律的,而是各有特色。前幾天我看了雷軍的簡(jiǎn)歷,簡(jiǎn)直亮瞎了我的雙眼。
又看了某網(wǎng)紅的簡(jiǎn)歷,佩服的五體投地。
剛好最近公司又要裁員了,老板需要我整理一批簡(jiǎn)歷。老系統(tǒng)中的人員信息都存儲(chǔ)在數(shù)據(jù)庫(kù)中,我該如何給老板拉簡(jiǎn)歷呢?
百思不得其解呀,如果是畫一個(gè)前端頁(yè)面,那得一個(gè)一個(gè)的另存為。這時(shí)候 RPA 到是可以用上,但是前端頁(yè)面得有人畫。但是,前端最近人數(shù)本來就不夠,還要砍一半。
不得已,我給團(tuán)隊(duì)的小伙伴說,后端生成 word 簡(jiǎn)歷吧,轉(zhuǎn)成 pdf 壓縮后扔給我。我轉(zhuǎn)給老板。順便給大家支了招,使用 poi + FreeMarker 來搞定。當(dāng)然,如果沒有 FreeMarker 也是可以的,有 FreeMarker 是可以更靈活的。
下面就是本文要講解的一些核心要點(diǎn),分享給大家!
前言
工作中經(jīng)常會(huì)遇到生成word這樣的需求, 以前都是使用FreeMarker來生成的 我們先了解一下FreeMarker是如何生成word的
-
制作好word模板 -
轉(zhuǎn)換為xml文檔并格式化, 文件后綴改為ftl(這一步生成的xml格式可能會(huì)錯(cuò)亂...) -
在模板代碼(上萬行)中加各種邏輯代碼來實(shí)現(xiàn)需求 -
FreeMarker模板引擎渲染生成word(doc格式的) -
doc轉(zhuǎn)docx
ps: word doc格式和docx格式的區(qū)別 doc為二進(jìn)制文件, 非常大, 打開速度非常慢; docx為壓縮文件, 非常小, 打開速度很快
上萬行的xml代碼簡(jiǎn)直是維護(hù)地獄, 要是這時(shí)候老板覺得這個(gè)word模板不太好看, 改改字體, 換下顏色, 調(diào)整表格樣式等等, 除非你對(duì)word的xml結(jié)構(gòu)特比特別熟悉, 否則你只能重復(fù)上面的步驟再來一次,心態(tài)崩了!!!
有沒有一種更容易維護(hù)的方式來生成word呢, 答案就是: POI-TL。
poi-tl簡(jiǎn)介
poi-tl(poi template language)是基于Apache POI的Word模板引擎,純Java組件,跨平臺(tái),代碼短小精悍,通過插件機(jī)制使其具有高度擴(kuò)展性。
注意: POI-TL可能會(huì)和項(xiàng)目中使用到xml相關(guān)操作的包產(chǎn)生沖突 建議將POI-TL單獨(dú)寫成一個(gè)服務(wù), 包裝需要使用到的相關(guān)特性以避免沖突
poi-tl使用
使用步驟十分簡(jiǎn)單:
-
準(zhǔn)備好word模板 -
準(zhǔn)備好數(shù)據(jù) 下面我們來試一下
?maven
<!-- poi-tl -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
<!-- spire.doc -->
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.doc.free</artifactId>
<version>3.9.0</version>
</dependency>
<!-- spring el -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
</dependency>
?先看下我的模板
?這是生成后的word效果
我在模板中嘗試了POI-TL的以下特性。
/**
* 表格內(nèi)設(shè)置超鏈接
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
public class LoopRowTableHyperlinkRenderPolicy extends LoopRowTableRenderPolicy {
/**
* 列
*/
private int hyperlinkIndex;
/**
* 超鏈接url key
*/
private String hyperlinkName = "url";
/**
* 超鏈接text key
*/
private String hyperlinkValue;
public LoopRowTableHyperlinkRenderPolicy(int hyperlinkIndex, String hyperlinkValue) {
this.hyperlinkIndex = hyperlinkIndex;
this.hyperlinkValue = hyperlinkValue;
}
@Override
protected void afterloop(XWPFTable table, Object data) {
try {
List<JSONObject> jsonObjectList = JSONArray.parseArray(JSON.toJSONString(data), JSONObject.class);
List<XWPFTableRow> rows = table.getRows();
// index從1開始, 跳過表頭
for (int i = 1; i < rows.size(); i++) {
XWPFTableCell cell = rows.get(i).getCell(hyperlinkIndex);
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
JSONObject js = jsonObjectList.get(i - 1);
String url = String.valueOf(js.get(hyperlinkName));
String value = String.valueOf(js.get(hyperlinkValue));
if (StrUtil.isBlank(url) || "null".equals(url)) {
// 不滿足條件, 正常填充值
cell.setText(value);
} else {
// 滿足條件, 設(shè)置超鏈接
setLink(url, value, paragraph);
}
// 居中
paragraph.setAlignment(ParagraphAlignment.CENTER);
paragraph.setVerticalAlignment(TextAlignment.CENTER);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void setLink(String url, String text, XWPFParagraph e) {
String id = e.getDocument().getPackagePart().addExternalRelationship(url,
XWPFRelation.HYPERLINK.getRelation()).getId();
CTHyperlink ctHyperlink = e.getCTP().addNewHyperlink();
ctHyperlink.setId(id);
CTText ctText = CTText.Factory.newInstance();
// 設(shè)置值
ctText.setStringValue(text);
CTR ctr = CTR.Factory.newInstance();
CTRPr rpr = ctr.addNewRPr();
CTColor color = CTColor.Factory.newInstance();
// 設(shè)置顏色
color.setVal("0000FF");
rpr.setColorArray(new CTColor[]{color});
// 單下劃線
// rpr.addNewU().setVal(STUnderline.SINGLE);
// 無下劃線
rpr.addNewU().setVal(STUnderline.NONE);
ctr.setTArray(new CTText[]{ctText});
ctHyperlink.setRArray(new CTR[]{ctr});
}
}
-
柱狀圖/餅圖(柱狀圖可以在模板中調(diào)整柱間距來改變柱的粗細(xì))
-
嵌套 語(yǔ)法: {{+nested}}
sub.docx
這里是{{province}}
{{city}} - {{district}}
-
循環(huán)
{{?sections}}集合第[{{_index}}]個(gè)元素: {{name}}{{/sections}}
-
區(qū)塊對(duì)
{{?isEmpty}}
******數(shù)據(jù)不為null******
{{/}}
{{?isEmpty == null}}
******數(shù)據(jù)為null******
{{/}}
-
SpingEL表達(dá)式
{{new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(time)}}
?配置并生成文件
Configure config = Configure.builder()
// isStrict = false , 關(guān)閉嚴(yán)格模式, 嚴(yán)格模式下key不存在會(huì)報(bào)錯(cuò)
// 關(guān)閉后key不存在會(huì)被忽略
.useSpringEL(false)
// 表格綁定policy
.bind("books", policy)
.build();
XWPFTemplate template = XWPFTemplate.
compile(getFile("", "template/template.docx"), config).render(data);
String path = OUT_PATH + IdUtil.fastSimpleUUID() + ".docx";
template.writeToFile(path);
private File getFile(String path, String name) throws IOException {
InputStream input = this.getClass().getClassLoader().getResourceAsStream(path + name);
File file = new File(name);
FileUtils.copyInputStreamToFile(input, file);
return file;
}
POI-TL的TOCRenderPolicy插件還不太好使(Beta實(shí)驗(yàn)功能:目錄,打開文檔時(shí)會(huì)提示更新域) 這里我使用的是spire.doc來更新目錄, 為了避免更新目錄造成內(nèi)容位置錯(cuò)亂, 我在模板文件的目錄后加了一個(gè)分頁(yè)符
/**
* 更新目錄
*
* @param source 源文件
* @param dir 目錄
* @return 更新后文件
*/
public static File updateTOCBySpire(String source, String dir) {
Document document = new Document(source);
String path = newPath(dir);
File file = new File(path);
try {
document.updateTableOfContents();
document.saveToFile(path, FileFormat.Docx_2013);
} finally {
document.close();
}
return file;
}
注意spire.doc免費(fèi)版的最多支持500個(gè)段落, 25個(gè)表格, 超過部分會(huì)被截?cái)?/code>(一般情況下這個(gè)已經(jīng)能夠滿足了)
也可以使用docx4j來更新目錄(文檔很多頁(yè)會(huì)特別慢, 并且DTM ID可能被耗盡, 導(dǎo)致無法使用)
如果真的是文檔特別大的話可以使用jacob(不能跨平臺(tái), 只能在windows系統(tǒng)下使用)或者使用付費(fèi)版的spire.doc(可以跨平臺(tái)的)
