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******
{{/}}
{{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-TLTOCRenderPolicy插件还不太好使(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(可以跨平台的)

浏览 43
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报