前言
项目中都会遇到关于文件的导出,这里主要使用freemarker来根据ftl来生成word文档,这里使用的例子中我只考虑到一些常见的情况,如果你遇到问题或是其他特殊情况,希望留下你问题和见解。
word文件导出
引入依赖
- 首先是导入freemarker的jar包
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency>
模板填充
-
然后我们在word的模板上填充上对应的参数:
上面模板中个人荣誉和工作经历是存在多个,对应了一个list对象,在此我们先不在页面上进行参数填充,而头像和个人风采需要插入图片,所以我们先默认插入一些图片进行占位和设置其大小属性;值得注意的是如果你需要像上图个人风采部分动态的展示图片的话,其图片一定要设置为四周型环绕。 -
将word另存为xml格式:
在另存为时,提供了两种xml格式的转储,请用Word XML 文档格式进行转储。 -
将导出的xml文件的后缀更改为ftl,并放入项目的resources资源包的templates文件夹中
list数据的遍历填充
- 打开该ftl文件,上面我们提到了关于个人荣誉和工作经历都是存在多条的,所以需要用list对象来存储,在ftl文件中就需要对list数据进行遍历,以下是对工作经历数据的遍历
<#--这里的使用if标签来对list进行判空操作--> <#if workExperienceList?? && (workExperienceList?size > 0) > <#--这里是对list进行遍历循环,workExperience为每次被遍历到的对象--> <#list workExperienceList as workExperience> <#--在ftl模板中,一个w:p标签代表一个段落,这里不要直接将这份代码粘贴过去,因为你本身的格式与我不同,导出后可能会出现的格式不一的问题--> <w:p> <w:pPr> <w:bidi w:val="0"/> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="default" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> <w:sz w:val="24"/> <w:szCs w:val="24"/> <w:vertAlign w:val="baseline"/> <w:lang w:val="en-US" w:eastAsia="zh-CN"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia" w:ascii="仿宋_GB2312" w:hAnsi="仿宋_GB2312" w:eastAsia="仿宋_GB2312" w:cs="仿宋_GB2312"/> <w:sz w:val="24"/> <w:szCs w:val="24"/> <w:vertAlign w:val="baseline"/> <w:lang w:val="en-US" w:eastAsia="zh-CN"/> </w:rPr> <#--在w:t标签中填充值,通过time?string("yyyy-MM-dd")!,首先判断time是否为空,如果不为空则对时间进行格式转换,这里的?!其实类似于java的三目运算符 --> <w:t>${(workExperience.workStartTime?string("yyyy-MM-dd"))!}~${(workExperience.workEndTime?string("yyyy-MM-dd"))!'至今'}在${workExperience.unitName!}担任${workExperience.title!},${workExperience.jobDescription!}</w:t> </w:r> </w:p> </#list> </#if>
插入图片
-
首先插入图片前我们需要知道在ftl中图片分为三部分
- 第一部分:
在创建模板之前因为插入了几张图片,所以你能看到占位
<#--pkg:name中填充的是图片在ftl模板中的位置,pkg:contentTyped代表了当前的标签内容的类型--> <pkg:part pkg:name="/word/media/image1.jpeg" pkg:contentType="image/jpeg"> <pkg:binaryData>存放图片的base64位编码</pkg:binaryData> </pkg:part>
- 第二部分:
这一部分可以说是对图片的一个申明,我们看到使用Relationship标签对图片进行了申明,这里只有三张图片(其他的非图片为了方便截图我将其隐藏了)是因为我的模板中虽然展示了四张图片,但是因为有两张图片相同,所以这里就只有三个声明,通过Target属性指向图片所在的路径,而Id则必须唯一它是连接连接第三部分的重要参数。<pkg:part pkg:name="/word/_rels/document.xml.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml"> <pkg:xmlData> <Relatiaonships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="指定ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="对应第一部分中pkg:name,只是去掉了/word/"/> </Relationships> </Relationships> </pkg:xmlData> </pkg:part>
- 第三部分:
该部分的xml比较长,我就只截重要的部分说明
以上就是对图片插入的说明,如果说你只有一张图片插入,那直接在第一部分中填充入你图片的Base64位编码就能实现图片的插入,而当你需要动态的插入图片就必须要通过循环来指定图片
- 第一部分:
-
动态插入图片
- 首先我将图片都封装为一个实体类,然后多张图片对应一个list集合
import lombok.Data; /** * @Author HomeWellGo * @Date 2020/10/18 20:10 * @Description 图片信息类 */ @Data public class ImgInfo { //图片的base64位编码 private String imgBaseCode; //图片的路径 private String imgUrl; //序号 private Integer imgIndex; }
- 填充图片的base64位编码
<#if personalStyle?? && (personalStyle?size > 0) > <#list personalStyle as style> <pkg:part pkg:name="/word/media/image${style.imgIndex}.jpeg" pkg:contentType="image/jpeg"> <pkg:binaryData> ${(style.imgBaseCode)!''} </pkg:binaryData> </pkg:part> </#list> </#if>
- 声明图片
<#if personalStyle?? && (personalStyle?size > 0) > <#list personalStyle as style> <Relationship Id="rId${style.imgIndex}jpg" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image${style.imgIndex}.jpeg"/> <Relationship Id="rId${style.imgIndex}jpg" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image${style.imgIndex}.jpeg"/> <Relationship Id="rId${style.imgIndex}jpg" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image${style.imgIndex}.jpeg"/> </#list> </#if>
- 指定图片
- 首先我将图片都封装为一个实体类,然后多张图片对应一个list集合
java代码
- 代码部分
import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.Version; import sun.misc.BASE64Encoder; import java.io.*; /** * @author HomeWellGo * @createTime 2021-09-01 09:47 * @Description */ public class ExportWordUtils { /** * 根据ftl模板导出word文件 * @param t 实体类 * @param templateName 模板文件名 * @param path 导出的word文件路径 * */ public static <T> void exportWordForFreemarker(T t,String templateName,String path){ //Configuration 用于读取ftl文件 Configuration configuration = new Configuration(new Version("2.3.0")); configuration.setDefaultEncoding("utf-8"); configuration.setClassForTemplateLoading(ExportWordUtils.class, File.separator+"templates"); Writer out=null; try { //输出文档路径及名称 File outFile = new File(path); //以utf-8的编码读取ftl文件 Template template = configuration.getTemplate(File.separator+templateName, "utf-8"); out= new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"), 10240); template.process(t, out); } catch (IOException e) { e.printStackTrace(); }catch (TemplateException e1){ e1.printStackTrace(); }finally { try { if(out!=null) out.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 获取图片的Base64位编码 * */ public static String getImgBase64(String path){ InputStream in = null; byte[] data = null; try{ in = new FileInputStream(path); data = new byte[in.available()]; in.read(data); in.close(); }catch (IOException e){ e.printStackTrace(); }finally { try { if (in!=null) in.close(); } catch (IOException e) { e.printStackTrace(); } } //对字节数组Base64编码 BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(data); } }