Aspose-words结合Freemarker实现word邮件合并功能,批量处理word模板文件

Aspose-words结合Freemarker实现word邮件合并功能,批量处理word模板文件,第1张

Aspose-words结合Freemarker实现word邮件合并功能,批量处理word模板文件

最近的工作中有一个需求,需要处理word文档,有一些内容需要根据不同用户进行替换修改,使用的是word文档,替换后的内容还需要转换为pdf进行签章确认,并进行防篡改处理。

所以记录一下处理步骤,首先可以从百度文库上下载一份用于测试使用的询证函

 

比如这篇文章,我已经下载下来了,然后打开文档,对需要进行替换的部分使用变量占位符处理

然后将word另存为xml格式

另存的时候,最好把文件名改为英文名,防止freemark读取模板文件路径时,中文路径乱码问题

然后再将xml后缀改为ftl格式,因为freemarker模板文件的格式是ftl格式,并将模板文件放在本地目录下:

  可以看到改了后缀为ftl,打开文件仍然是xml格式

接下来创建实体类,实体类属性名要与刚才在word文档中设置的占位符变量名相同,用于替换占位符为具体的数据,至此,准备工作基本上已经完成

接下来开始处理模板文件:

所需要的jar包依赖

   
        
            com.aspose
            aspose-words
            15.8.0
        
        
            org.freemarker
            freemarker
            2.3.30
        
        
        
            org.projectlombok
            lombok
            1.18.4
        
          
            org.apache.commons
            commons-lang3
            3。3.9
          
        
                cn.hutool
                hutool-all
                5.7.10
         

创建实体类:

import lombok.Data;


@Data
public class AssetsReconciliation {

   private String enterpriseName;
   private String agencyName;
   private String address;
   private String amount;
    
   private String postcode;
   private String phone;
   private String fax;
   private String linkman;
   private String year;
   private String month;
   private String day;
   private String date;
    
   private String assets;
    
   private String liabilities;
    
   private String content;
    
   private String sign;
    
   private String operator;


}

 创建处理工具类:

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.setting.dialect.Props;
import cn.hutool.system.OsInfo;
import com.aspose.cells.License;
import com.aspose.words.document;
import com.aspose.words.FontSettings;
import com.aspose.words.SaveFormat;
import com.xiaomifeng1010.tmc.newenergy.word.bo.AssetsReconciliation;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;


@Slf4j
public class ConvertWordToPDFUtil {
    private static Configuration cfg;

    
    private static Props props=new Props("pdf.properties");
    private static String templateFileDirPath=props.getProperty("template.path");
    private static String docFilePath=props.getProperty("word.path");
    private static boolean initSuccess = true;

    static{
        //        初始化freemarker配置
        try {
            cfg=new Configuration(Configuration.VERSION_2_3_22);
//            加载模板所在目录(从pdf.properties文件中可以看出,模板文件是在本地存放的)
//          当然也可以把ftl模板文件放在resources目录下,这样的话,就需要获取classpath下的目录
            cfg.setDirectoryForTemplateLoading(new File(templateFileDirPath));
            cfg.setDefaultEncoding(CharsetUtil.UTF_8);
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        } catch (IOException e) {
            log.error("freemarker初始化配置出错",e);
            initSuccess=false;
        }
    }

    
    public static boolean getLicense() {
        boolean result = false;
        try {
            InputStream is = ConvertWordToPDFUtil.class.getClassLoader().getResourceAsStream("\license.xml");
            License aposeLic = new License();
            aposeLic.setLicense(is);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    
    public static String handleWordTemplate(AssetsReconciliation assetsReconciliation){
        String docpath=docFilePath+System.currentTimeMillis()+".docx";

        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(docpath), StandardCharsets.UTF_8))) {
            String fileName="template_test.ftl";
//            获取模板
            Template template = cfg.getTemplate(fileName, CharsetUtil.UTF_8);
//            往模板中填充内容,并保存为docx文件
            template.process(assetsReconciliation,out);
        } catch (Exception e) {
            log.error("模板填充内容失败!", e);
            return StringUtils.EMPTY;
        }

        return docpath;

    }


    
    public static Boolean word2pdf(String inputPath, String outPath) {
        OsInfo osInfo = new OsInfo();
        // linux平台下需要安装字体样式,从window下拷贝过去安装即可
        if (osInfo.isLinux()) {
            FontSettings.setFontsFolder(File.separator + "usr" + File.separator + "share" + File.separator + "fonts", true);
        } else if (osInfo.isMac()) {
            FontSettings.setFontsFolder("/Library/Fonts/Microsoft", true);
        }

        try (FileOutputStream os = new FileOutputStream(outPath)) {
            if (getLicense()) {
                long start = System.currentTimeMillis();
                document doc = new document(inputPath);
                doc.save(os, SaveFormat.PDF);
                long end = System.currentTimeMillis();
                log.info("转换成功, 花费 " + (end - start) / 1000.0 + " seconds!");
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("转换报错!", e);
            return false;
        }
    }
}

 pdf.properties放在项目的resources目录下:

 以及license.xml文件



    
        
            Aspose.Total for Java
            Aspose.Words for Java
        
        Enterprise
        20991231
        20991231
        8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7
    
    sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=

然后写一个controller类请求方法测试一下

import cn.hutool.core.date.DateUtil;
import com.xiaomifeng1010.tmc.newenergy.word.bo.AssetsReconciliation;
import com.xiaomifeng1010.tmc.newenergy.word.util.ConvertWordToPDFUtil;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@Api(value = "处理word文档",tags = {"处理word文档"})
@ApiSort(26)
@RestController
@RequestMapping("/word")
public class WordTestController {

    @ApiOperation("测试填充word模板并转换为pdf")
    @GetMapping("/convertTest")
    public String testConvertExcelToPDF(){
        AssetsReconciliation assetsReconciliation = new AssetsReconciliation();
        assetsReconciliation.setAddress("广东省广州市白云区人和镇999路233号");
        assetsReconciliation.setAmount("¥2,456,987.05");
        assetsReconciliation.setAssets("¥1,345,675.45");
        assetsReconciliation.setAgencyName("非凡科技有限公司");
        assetsReconciliation.setContent("无异议");
        assetsReconciliation.setDate(DateUtil.now());
        assetsReconciliation.setEnterpriseName("天涯科技有限公司");
        assetsReconciliation.setFax("010-3458-450");
        assetsReconciliation.setLiabilities("¥45,769.56");
        assetsReconciliation.setYear("2021");
        assetsReconciliation.setMonth("11");
        assetsReconciliation.setDay("27");
        assetsReconciliation.setlinkman("小明");
        assetsReconciliation.setOperator("ablert");
        assetsReconciliation.setPhone("13576890543");
        assetsReconciliation.setPostcode("443900");
        assetsReconciliation.setSign("假装有印章");

        String docPath = ConvertWordToPDFUtil.handleWordTemplate(assetsReconciliation);
        if (StringUtils.isNotEmpty(docPath)) {
            String pdfFileName="C:\Users\MSI\Desktop\template\"+ RandomStringUtils.randomAlphanumeric(6) + ".pdf";
            ConvertWordToPDFUtil.word2pdf(docPath,pdfFileName);
            return "填充word模板成功,并转为pdf成功";
        }
        return "处理失败!";


    }
}

然后再knife4j接口文档界面发起请求

填充内容后的word文档:

注意:使用freemarker处理之后的word文档用microsoft word是打不开的,不知道为什么,但是可以用金山WPS 文字软件打开,效果如上

然后再看一下转换为pdf后的效果:

 样式和word文档样式是一样的,这就是为什么不建议使用apache poi去处理word文档,再用itext生成pdf的原因。poi可以读取到word文档的内容,但是读取出来后,会读成为字符串了,样式都丢失了,再用itext去保存为pdf的时候,因为样式丢失,整体布局是错乱的。

注意事项:我在用freemark处理word转换之后的ftl模板时遇到了一些麻烦问题,现在也总结一下

doc格式的文档,在一些需要填充数据的地方,写上了EL表达式${变量名},但是在另存为xml格式的时候,一些EL表达式变量,会被拆分开,这时候,freemarker在解析模板的时候就会报错:

Encountered "<", but was expecting one of: 
 
 
"false" 
"true" 
 
 
"." 
"+" 
"-" 
"!" 
"[" 
"(" 
"{" 
 

 此时需要打开保存的xml文件

可以看到原本设置的${enterpriseName}被拆分开成了好几部分

而正常的应该是这样的

 应该是整体在一起的,所以呢需要进行手动修改xml文件,是正哥EL表达式是整体在一起的

这还没完,注意下边还有一个 标签,包括刚才删除的那一部分,也有注意都有一个proofErr的属性,说明这个标签是提示错误的,这部分里边的内容有错误

所以,列信息那几个字也得处理一下,把列信息三个字移到下边,然后删除这个标签

下边的agencyName出错与enterpriseName出错一致,处理一样

 这样的话,语法问题就解决了,freemark就可以正常解析EL表达式了。还有一个问题就是在创建实体类的时候,最开始的时候,实体类创建的时候少了一个属性amount,但是模板文件中是有这个EL表达式的

但是创建实体类时候,没加上这个属性,填充模板内容的时候也报错了

Caused by: freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:  ==> amount in 

 所以在测试时候,设置AssetsReconciliation时候,就没有设置amount的值,在填充模板时候,${amount}就为null,就报上边这个异常,所以后来在实体类上加上了这个属性,并赋值,就正常了

或者修改模板文件,添加判空的判断,给个默认值

在ftl文件中修改 ${(amount)!' '} 或者${amount!" "} 如果为空,就以默认值(“!”后的字符)显示;

还有就是如果同时使用apose-words处理word,有用apose-cells处理excel的话,需要同时分开用两个工具类或配置文件去分别获取对应的license,不能共用一个工具类去获取license,不然总会有一个工具处理后带水印(处理word转pdf之后,或者excel转pdf后带水印)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5611753.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存