技术方案:

  • 一、使用Freemarker + doc xml 文件导出 .doc文档
  • 二、使用XDOCReport + Freemarker模板导出 .docx文档
    一、使用Freemarker + doc xml 文件导出.doc文档
    优缺点:使用简单,但是xml文档太长,模板.ftl文件维护困难,直接修改word xml文件语法困难

依赖:

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.30</version>
</dependency>

1、生成XML文件

新建.doc文档并另存为xml文档,打开文件格式化
2025-05-28T07:34:56.png
2025-05-28T07:35:15.png

2、修改文件后缀为.ftl并使用Freemarker 语法修改xml文件
2025-05-28T07:36:00.png

3、使用Freemarker 模板引擎填充值,导出doc文件
2025-05-28T07:36:25.png

<dependency>
    <groupId>com.meix</groupId>
    <artifactId>ia-commons-utils</artifactId>
</dependency>

二、使用XDOCReport + Freemarker模板导出 .docx文档

优缺点:模板文件为.docx文件,使用word域作为占位符,模板文件维护相对简单,支持复杂格式(插入图片...),但是由于word格式问题经常导致导出时报错,

依赖:

<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
        <artifactId>fr.opensagres.xdocreport.core</artifactId>
        <version>2.0.2</version>
    </dependency>
    <dependency>
        <groupId>fr.opensagres.xdocreport</groupId>
        <artifactId>fr.opensagres.xdocreport.document</artifactId>
        <version>2.0.2</version>
    </dependency>
    <dependency>
        <groupId>fr.opensagres.xdocreport</groupId>
        <artifactId>fr.opensagres.xdocreport.template</artifactId>
        <version>2.0.2</version>
    </dependency>
    <dependency>
        <groupId>fr.opensagres.xdocreport</groupId>
        <artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
        <version>2.0.2</version>
    </dependency>
    <dependency>
        <groupId>fr.opensagres.xdocreport</groupId>
        <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
    <version>2.0.2</version>
</dependency>

1、新建word docx文档,Ctrl + F9 新建域,编辑域,选择邮件合并,填写freemarker 语法

2、使用Freemarker 模板生成站位符

//生成待占位符的docx临时文档
String destFileName = XWPFDocumentUtils.processTemplate(classPath, template, tempFileName, root);

//docx文档处理富文本生成word文档
XWPFDocumentUtils.processRichTextArea(tempFileName, filePath, fileName, richTextMap, response);

public static String processTemplate(String templatePath, String template, String tempFileName, Map<String, Object> data) throws Exception{
        File file = ResourceUtils.getFile(templatePath + template);
        InputStream ins = new FileInputStream(file);
        IXDocReport docReport = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
        IContext context = docReport.createContext();
        context.putMap(data);

        File tempFile = new File(tempFileName);
        if (!tempFile.getParentFile().exists()) {
            tempFile.getParentFile().mkdirs();
        }
        tempFile.createNewFile();
        OutputStream out = new FileOutputStream(tempFile);
        docReport.process(context, out);
        ins.close();
        out.close();

        return tempFileName;
    }

public static void processRichTextArea(String tempFileName, String filePath, String destFileName, Map<String, List<Map<String, Object>>> richTextMap, HttpServletResponse response){
        try {
            File wordFile = ResourceUtils.getFile(tempFileName);
            InputStream insWord = new FileInputStream(wordFile);
            OPCPackage srcPackage = OPCPackage.open(insWord);
            XWPFDocument document = new XWPFDocument(srcPackage);
            XWPFDocumentUtils.insertRichTextAreaDocx(document, richTextMap);
            XWPFDocumentUtils.exportDocx(document, filePath, destFileName, response);
            insWord.close();
        } catch (Exception e){
            log.error("XWPFDocumentUtils.processRichTextArea error: {}", e.getMessage(), e);
        }
    }

    public static void insertRichTextAreaDocx(XWPFDocument document, Map<String, List<Map<String, Object>>> richTextMap) {
        try {
            //遍历多模块
            for (Map.Entry<String, List<Map<String, Object>>> e: richTextMap.entrySet()) {
                List<Map<String, Object>> richTextList = e.getValue();
                // 如果需要替换多份富文本,通过Map来操作,key:要替换的标记
                for (Map<String, Object> richText : richTextList) {
                    Map<String, String> styles = (Map<String, String>) richText.get("styles");
                    Iterator<Map.Entry<String, Object>> iterator = richText.entrySet().iterator();

                    while (iterator.hasNext()){
                        Map.Entry<String, Object> entry = iterator.next();
                        for (XWPFParagraph paragraph : document.getParagraphs()) {
                            if(paragraph.getText().trim().contains(entry.getKey())){
                                log.info("paragraph Text ==============================" + paragraph.getText());
                            }
                            if (entry.getKey().equals(paragraph.getText().trim())) {
                                HtmlResolver htmlResolver = new HtmlResolver();
                                if(entry.getValue() != null){
                                    htmlResolver.resolveHtml(entry.getValue().toString(), document, paragraph, styles);
                                }
                                //删除占位符
                                if (!iterator.hasNext()) {
                                    document.removeBodyElement(document.getPosOfParagraph(paragraph));
                                }
                                break;
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("XWPFDocumentUtils insertRichTextAreaDocx error: {}", e.getMessage(), e);
        }
    }

3、处理占位符替换为内容,处理富文本

4、富文本处理

<dependency>    

    <groupId>com.meix</groupId>    

    <artifactId>ia-commons-utils</artifactId>

</dependency>
最后修改:2025 年 05 月 28 日
如果觉得我的文章对你有用,请随意赞赏