5

Java根据Freemarker模板生成Word文件 - 废物大师兄

 1 year ago
source link: https://www.cnblogs.com/cjsblog/p/16715294.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java根据Freemarker模板生成Word文件

1.  准备模板

模板 + 数据 = 模型

1、将准备好的Word模板文件另存为.xml文件(PS:建议使用WPS来创建Word文件,不建议用Office)

874963-20220921133652444-331064643.png
874963-20220921142738744-1022232608.png

2、将.xml文件重命名为.ftl文件

874963-20220921134206730-1818129377.png

3、用文本编辑器打开.ftl文件,将内容复制出来,格式化一下,再覆盖原来的内容

(PS:格式化一下是为了方便查找并设置变量/占位符,当然设置好模板参数变量以后可以再压缩后再写会.ftl文件)

另外,强烈不建议在word文件中去编辑设置模板变量,因为.docx文件在另存为.xml文件后,原先好好的一个变量可能就被拆开了,建议另存为之后再用文本编辑器打开去编辑。

874963-20220921134052901-2033120364.png

4、设置模板参数(变量/占位符)

874963-20220921134406129-315918679.png

2.  代码实现

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo920</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo920</name>
    <description>demo920</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.7</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13.3</version>
        </dependency>
        <!--<dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-words</artifactId>
            <version>22.9</version>
            <classifier>jdk17</classifier>
        </dependency>-->

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

写个类测试一下

package com.example.demo920;

import com.example.demo920.domain.LoanReceipt;

import freemarker.template.Configuration;
import freemarker.template.Template;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

@SpringBootTest
class Demo920ApplicationTests {

    private DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMddHHmmss", Locale.CHINA);

    @Test
    void contextLoads() {
    }

    @Test
    void testGenerateWord() throws Exception {
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
        configuration.setDefaultEncoding("UTF-8");
        configuration.setClassForTemplateLoading(this.getClass(), "/templates");

        Template template = configuration.getTemplate("借条.ftl");

        Path path = Paths.get("tmp","contract");
        File fileDir = path.toFile();
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }

        String filename = "借条" + "_" + LocalDateTime.now().format(DTF) + ".docx";
        filename = path.toFile() + File.separator + filename;

        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename)));

//        template.process(getDataMap(), writer);
        template.process(getData(), writer);

        writer.flush();
        writer.close();
    }

    Map<String, Object> getDataMap() {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("borrowerName", "李白");
        dataMap.put("borrowerIdCard", "421302199001012426");
        dataMap.put("lenderName", "杜甫");
        dataMap.put("amount", 100);
        dataMap.put("amountInWords", "壹佰");
        dataMap.put("startDate", "2022年8月15日");
        dataMap.put("endDate", "2022年11月11日");
        dataMap.put("borrowingMonths", 3);
        dataMap.put("interestRate", "1.23");
        dataMap.put("guarantorName", "白居易");
        dataMap.put("guarantorIdCard", "421302199203152412");
        return dataMap;
    }

    LoanReceipt getData() {
        LoanReceipt receipt = new LoanReceipt();
        receipt.setBorrowerName("狄仁杰");
        receipt.setBorrowerIdCard("421302198710121234");
        receipt.setBorrowingMonths(6);
        receipt.setLenderName("李元芳");
        receipt.setAmount(new BigDecimal("101"));
        receipt.setAmountInWords("壹佰零壹");
        receipt.setInterestRate(new BigDecimal("0.6"));
        receipt.setStartDate("2022年1月1日");
        receipt.setEndDate("2022年6月30日");
        receipt.setGuarantorName("武则天");
        receipt.setGuarantorIdCard("421302199101014567");
        return receipt;
    }

}

最主要的是下面两行

//	加载模板
Template template = configuration.getTemplate("借条.ftl");
//	填充数据
template.process(getData(), writer);

数据可以是Map也可以是一个对象

改进一下,将生成文件的操作单独写成一个工具方法

package com.example.demo920.util;

import cn.hutool.core.io.IoUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.*;

public class FreemarkerUtils {

    /**
     * 生成Word
     * @param templateDir   模板所在的目录
     * @param templateName  模板文件名称
     * @param filename      生成的文件(含路径)
     * @param dataModel     模板参数数据
     */
    public static void generateWord(File templateDir, String templateName, String filename, Object dataModel) {
        BufferedWriter writer = null;
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
        configuration.setDefaultEncoding("UTF-8");
        try {
            configuration.setDirectoryForTemplateLoading(templateDir);
            Template template = configuration.getTemplate(templateName);
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename)));
            template.process(dataModel, writer);
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (TemplateException e) {
            throw new RuntimeException(e);
        } finally {
            IoUtil.close(writer);
        }
    }

}

再测试一下

package com.example.demo920;

import cn.hutool.core.io.IoUtil;
import com.example.demo920.util.FreemarkerUtils;
import com.example.demo920.util.PdfUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

public class WordTest {

    /**
     * 1、从文件服务器下载模板文件
     * 2、根据业务类型获取需要填充模板的数据
     * 3、模板+数据  再经过处理生成新的文件
     * 4、将生成后的文件上传到文件服务器,并返回一个文件ID
     * 5、业务可以保存这个文件ID或者文件的路径
     */
    @Test
    void testGenerateWordV1() throws Exception {
        Path tempPath = Paths.get("tmp", "contract2");
        File path = tempPath.toFile();
        if (!path.exists()) {
            path.mkdirs();
        }
        File tempFile = Files.createTempFile(tempPath, "qiantiao", ".docx").toFile();
        System.out.println(tempFile.getParent());
        System.out.println(tempFile.getName());
        FileOutputStream fos = new FileOutputStream(tempFile);


        File templateFile = ResourceUtils.getFile("classpath:templates/借条.ftl");
        FileInputStream fis = new FileInputStream(templateFile);

        IoUtil.copy(fis, fos);

        String filename = "借条" + "_" + System.currentTimeMillis() + ".docx";
        filename = "tmp/contract" + File.separator + filename;

        FreemarkerUtils.generateWord(new File(tempFile.getParent()), tempFile.getName(), filename, getDataMap());
    }

 	/**
     * 获取数据
     */
    Map<String, Object> getDataMap() {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("borrowerName", "李白2");
        dataMap.put("borrowerIdCard", "421302199001012426");
        dataMap.put("lenderName", "杜甫");
        dataMap.put("amount", 100);
        dataMap.put("amountInWords", "壹佰");
        dataMap.put("startDate", "2022年8月15日");
        dataMap.put("endDate", "2022年11月11日");
        dataMap.put("borrowingMonths", 3);
        dataMap.put("interestRate", "1.23");
        dataMap.put("guarantorName", "白居易");
        dataMap.put("guarantorIdCard", "421302199203152412");
        return dataMap;
    }

    @Test
    void testGenerateWord2() throws Exception {
        File templateDir = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "templates");
        String templateName = "借条.ftl";
        String destFilename = "借条" + System.currentTimeMillis() + ".docx";
        Map<String, Object> data = getDataMap();
        FreemarkerUtils.generateWord(templateDir, templateName, destFilename, data);
    }

}
874963-20220921142428750-419621071.png

3. PDF文件加水印

有时候,生成或者从服务器下载的文件是需要加水印的,比如标识这个文件是谁下载的之类的

pdf加水印还是比较方便的,用itext组件可以轻松实现

另外,如果最终需要pdf文件,建议直接生成pdf文件,跳过word转pdf的步骤

package com.example.demo920.util;

import cn.hutool.core.io.IoUtil;
import com.aspose.words.Document;
import com.aspose.words.SaveFormat;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;

/**
 * @author chengjiansheng
 * @date 2022/09/21
 */
public class PdfUtils {

    /**
     * Word转PDF
     * https://www.aspose.com/
     * 注意:Aspose.Words 这个组件是收费的,如果购买的话生成的PDF会有水印。
     * 可以去找相应的破解版本,但是我感觉完全可以跳过Word直接生成PDF。
     * 比如,可以通过Freemarker直接生成PDF,或者利用iText通过模板生成PDF
     * @param src
     * @param dest
     */
    public static void wordToPdf(String src, String dest) {
        File file = new File(src);
        if (!file.exists()) {
            throw new RuntimeException("文件不存在");
        }
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            Document wpd = new Document(fis);
            wpd.save(dest, SaveFormat.PDF);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            IoUtil.close(fis);
        }
    }

    /**
     * 加水印
     * @param src   源文件
     * @param dest  目标文件
     * @param text  文字
     * @param imagePath 图片地址
     */
    public static void addWatermark(String src, String dest, String text, String imagePath) {
        try {
            //  待加水印的文件
            PdfReader reader = new PdfReader(src);
            //  加完水印的文件
            PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
            //  字体
            BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            //  透明度
            PdfGState gs = new PdfGState();
            gs.setFillOpacity(0.4f);
            //  PDF文件总页数
            int total = reader.getNumberOfPages() + 1;
            //  循环对每一页都加水印
            PdfContentByte content;
            for (int i = 1; i < total; i++) {
                //  水印在文本之上
                content = stamper.getOverContent(i);
                content.setGState(gs);

                if (null != imagePath) {
                    Image image = Image.getInstance(imagePath);
//                    image.setAbsolutePosition(150, 150);
//                    image.scaleToFit(300,300);
//                    content.addImage(image);

                    for (int x = 0; x < 700; x = x + 300) {
                        for (int y = 0; y < 900; y = y + 200) {
                            image.setAbsolutePosition(x+50, y+50);
                            image.scaleToFit(100,100);
                            content.addImage(image);
                        }
                    }
                }
                if (null != text) {
                    content.beginText();
                    content.setColorFill(BaseColor.RED);
                    content.setFontAndSize(baseFont, 20);
//                    content.showTextAligned(Element.ALIGN_CENTER, text, 50, 50, 45);

                    for (int x = 0; x < 700; x = x + 300) {
                        for (int y = 0; y < 900; y = y + 200) {
                            //水印内容和水印位置
                            content.showTextAligned(Element.ALIGN_CENTER, "哈哈哈哈哈", x - 20, y + 10, 30);
                            content.showTextAligned(Element.ALIGN_CENTER, LocalDateTime.now().toString(), x, y, 30);
                        }
                    }

                    content.endText();
                }
            }
            stamper.close();
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }
}
@Test
void testWatermark() {
    String src2 = "D:\\借条_2.pdf";
    String dest2 = "D:\\借条_3.pdf";
    String imagePath = "D:\\1.jpg";
    PdfUtils.addWatermark(src2, dest2, "哈哈哈哈哈", imagePath);
}

加完水印后效果如图

874963-20220921141550577-562659677.png

最后,示例项目结构如图

874963-20220921141643506-543020480.png

参考:

https://blog.csdn.net/weixin_45103378/article/details/118395284

感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如需远程调代码或讲解或需要源码,请打赏!
欢迎各位转载,但必须在文章页面中给出作者和原文链接!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK