Word简历文档自动化生成新方案
共 12204字,需浏览 25分钟
·
2024-05-11 18:13
来源:juejin.cn/post/7101493026899853325
推荐:https://t.zsxq.com/Kpajy
好看的简历并不是千篇一律的,而是各有特色。前几天我看了雷军的简历,简直亮瞎了我的双眼。
又看了某网红的简历,佩服的五体投地。
刚好最近公司又要裁员了,老板需要我整理一批简历。老系统中的人员信息都存储在数据库中,我该如何给老板拉简历呢?
百思不得其解呀,如果是画一个前端页面,那得一个一个的另存为。这时候 RPA 到是可以用上,但是前端页面得有人画。但是,前端最近人数本来就不够,还要砍一半。
不得已,我给团队的小伙伴说,后端生成 word 简历吧,转成 pdf 压缩后扔给我。我转给老板。顺便给大家支了招,使用 poi + FreeMarker 来搞定。当然,如果没有 FreeMarker 也是可以的,有 FreeMarker 是可以更灵活的。
下面就是本文要讲解的一些核心要点,分享给大家!
前言
工作中经常会遇到生成word这样的需求, 以前都是使用FreeMarker
来生成的 我们先了解一下FreeMarker
是如何生成word的
-
制作好word模板 -
转换为xml文档并格式化, 文件后缀改为ftl(这一步生成的xml格式可能会错乱...) -
在模板代码(上万行)中加各种逻辑代码来实现需求 -
FreeMarker模板引擎渲染生成word(doc格式的) -
doc转docx
ps: word doc格式和docx格式的区别 doc为二进制文件, 非常大, 打开速度非常慢; docx为压缩文件, 非常小, 打开速度很快
上万行的xml
代码简直是维护地狱, 要是这时候老板觉得这个word模板不太好看, 改改字体, 换下颜色, 调整表格样式等等, 除非你对word的xml结构特比特别熟悉, 否则你只能重复上面的步骤再来一次,心态崩了!!!
有没有一种更容易维护的方式来生成word呢, 答案就是: POI-TL
。
poi-tl简介
poi-tl(poi template language)
是基于Apache POI
的Word模板引擎,纯Java组件,跨平台,代码短小精悍,通过插件机制使其具有高度扩展性。
注意: POI-TL可能会和项目中使用到xml相关操作的包产生冲突 建议将POI-TL单独写成一个服务, 包装需要使用到的相关特性以避免冲突
poi-tl使用
使用步骤十分简单:
-
准备好word模板 -
准备好数据 下面我们来试一下
❝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
的以下特性。
/**
* 表格内设置超链接
*/
@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 {
// 满足条件, 设置超链接
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();
// 设置值
ctText.setStringValue(text);
CTR ctr = CTR.Factory.newInstance();
CTRPr rpr = ctr.addNewRPr();
CTColor color = CTColor.Factory.newInstance();
// 设置颜色
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});
}
}
-
柱状图/饼图(柱状图可以在模板中调整柱间距来改变柱的粗细)
-
嵌套 语法: {{+nested}}
sub.docx
这里是{{province}}
{{city}} - {{district}}
-
循环
{{?sections}}集合第[{{_index}}]个元素: {{name}}{{/sections}}
-
区块对
{{?isEmpty}}
******数据不为null******
{{/}}
{{?isEmpty == null}}
******数据为null******
{{/}}
-
SpingEL表达式
{{new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(time)}}
❝配置并生成文件
Configure config = Configure.builder()
// isStrict = false , 关闭严格模式, 严格模式下key不存在会报错
// 关闭后key不存在会被忽略
.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实验功能:目录,打开文档时会提示更新域) 这里我使用的是spire.doc
来更新目录, 为了避免更新目录造成内容位置错乱, 我在模板文件的目录后加了一个分页符
/**
* 更新目录
*
* @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免费版
的最多支持500个段落
, 25个表格
, 超过部分会被截断
(一般情况下这个已经能够满足了)
也可以使用docx4j
来更新目录(文档很多页会特别慢, 并且DTM ID
可能被耗尽, 导致无法使用)
如果真的是文档特别大的话可以使用jacob(不能跨平台, 只能在windows系统下使用)
或者使用付费版的spire.doc(可以跨平台的)