poi
java2word
freemarker
最后选用freemarker,为什么呢?业务需要。
生成doc格式,比较简单,这里就不列举了,网上有大量的例子
生成思路大概就是保存一个doc文件,改后缀位xml,占位符去渲染。其中图片转换成base64格式。
这里主要讲docx格式。测试环境office 2013 2016
特殊符号需要转义,否则报错
docx格式本质上是个zip压缩包。
- 新建docx文件,插入自己需要的格式,如图
- 修改后缀为zip
[Content_Types].xml
如果需要插图,需要修改此文件,这里以jpg图片为例,
插入图片word转为jpeg,建议图片修改jpg为jepg
如果插入其他格式在此文件夹添加对应的说明
3.打开word文件夹,主要改动也是在这里
注意: <pic:cNvPr id=“1” name=“图片 1”/> 中的id最好使用数字,前面使用的字符串出现打开文件弹错误的情况
=====================================================================
干货来了
目录结构
springboot maven引入freemarker
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
freemarker相关配置
application.yml
这里就不啰嗦了
spring:
freemarker:
charset: UTF-8
suffix: .html
template-loader-path: /templates
content-type: text/html; charset=utf-8
java 工具类
import java.io.*;
import java.util.Date;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* @Description: word工具类
* @Auther: simp
* @Date: 2020/11/27 11:18
* @Version: 1.0
*/
@Slf4j
@Component
public class WordUtils {
private static final String path = "/project/upload/doc/";
public static Configuration getConfiguration() {
Configuration config = new Configuration(Configuration.VERSION_2_3_28);
config.setDefaultEncoding("utf-8");
config.setClassForTemplateLoading(WordUtils.class, "/templates");
config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
return config;
}
/**
* FreeMarker生成Word
* @param dataMap 数据
* @param templateName 目标名
*/
public static ByteArrayInputStream createXml(JSONObject dataMap, String templateName) {
Template template = null;
ByteArrayInputStream in = null;
if(templateName.endsWith(".html")) {
templateName = templateName.substring(0, templateName.indexOf(".html"));
}
try {
template = getConfiguration().getTemplate(templateName + ".html");
} catch (TemplateNotFoundException e) {
e.printStackTrace();
} catch (MalformedTemplateNameException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
StringWriter out = new StringWriter();
try {
template.process(dataMap, out);
in = new ByteArrayInputStream(out.toString().getBytes("utf-8"));//这里一定要设置utf-8编码 否则导出的word中中文会是乱码
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
return in;
}
public static String exportWord(String report) {
JSONObject jsonObject = JSONObject.parseObject(report);
Date date = new Date(Long.parseLong(jsonObject.getString("time")));
String docName = new StringBuffer()
.append(DateUtil.format(date, "yyyyMMddHHmmss"))
.append("_")
.append(jsonObject.getString("taskName"))
.append(".docx").toString();
String saveFilePath = path + docName;
String time = DateUtil.format(date, "yyyy/MM/dd HH:mm:ss");
jsonObject.put("time", time);
JSONArray stepInfos = jsonObject.getJSONArray("stepInfos");
JSONArray images = new JSONArray();
int count = 10;
for (Object item : stepInfos) {
JSONObject step = (JSONObject) item;
if (step.containsKey("images")) {
JSONArray list = step.getJSONArray("images");
for (Object it : list) {
JSONObject image = (JSONObject) it;
if (image.containsKey("path")) {
image.put("id", count);
image.put("embed", "rId" + count);
image.put("name", "图片 " + count);
String path = image.getString("path");
String ext = path.substring(path.lastIndexOf("."));
ext = ".jpg".equals(ext) ? ".jpeg" : ext;
JSONObject imageJson = new JSONObject();
imageJson.put("embed", image.getString("embed"));
imageJson.put("id", image.getString("id"));
imageJson.put("ext", ext);
imageJson.put("name", "image" + count + ext);
try {
FileInputStream fileIn = new FileInputStream(image.getString("path"));
imageJson.put("code", fileIn);
} catch (Exception e) {
}
images.add(imageJson);
count++;
}
}
}
}
JSONObject imageObject = new JSONObject();
imageObject.put("images", images);
ZipOutputStream zipout = null;
OutputStream outputStream = null;
try {
ByteArrayInputStream documentInput = createXml(jsonObject, "report.html");
ByteArrayInputStream documentXmlRelsInput = createXml(imageObject, "report_images.html");
File docxFile = new File(WordUtils.class.getClassLoader().getResource("templates/report.zip").getPath());
if (!docxFile.exists()) {
docxFile.createNewFile();
}
ZipFile zipFile = new ZipFile(docxFile);
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
File outFile = new File(saveFilePath);
if(!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
outputStream = new FileOutputStream(outFile);
zipout = new ZipOutputStream(outputStream);
// 覆盖文档
int len = -1;
byte[] buffer = new byte[1024];
while (zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();
InputStream is = zipFile.getInputStream(next);
if (next.toString().indexOf("media") < 0) {
zipout.putNextEntry(new ZipEntry(next.getName()));
if (next.getName().indexOf("document.xml.rels") > 0) {
if (documentXmlRelsInput != null) {
while ((len = documentXmlRelsInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
documentXmlRelsInput.close();
}
} else if ("word/document.xml".equals(next.getName())) {
if (documentInput != null) {
while ((len = documentInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
documentInput.close();
}
} else {
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
is.close();
}
}
}
// 写入图片
for (Object it : images) {
JSONObject pic = (JSONObject) it;
ZipEntry next = new ZipEntry(new StringBuffer()
.append("word")
.append("/")
.append("media")
.append("/")
.append(pic.getString("name"))
.toString());
zipout.putNextEntry(new ZipEntry(next.toString()));
InputStream in = (FileInputStream) pic.get("code");
while ((len = in.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
in.close();
}
} catch (Exception e) {
log.error("word导出失败: {}", e.getStackTrace());
} finally {
if(zipout != null){
try {
zipout.close();
} catch (IOException e) {
log.error("io异常");
}
}
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
log.error("io异常");
}
}
}
return docName;
}
}
document.xml 文件替换
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document
xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 wp14">
<w:body>
<w:p w14:paraId="4CA2B435" w14:textId="39DCCBAF" w:rsidR="007E4DFA" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:b/>
<w:bCs/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:sz w:val="52"/>
<w:szCs w:val="52"/>
</w:rPr>
<w:t>${taskName?if_exists}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="667ECFCA" w14:textId="29F662B5" w:rsidR="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:b/>
<w:bCs/>
<w:sz w:val="36"/>
<w:szCs w:val="36"/>
</w:rPr>
</w:pPr>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:p w14:paraId="6C3589ED" w14:textId="5F39D09F" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>检查时间</w:t>
</w:r>
<w:r w:rsidR="00F11D3D">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>:</w:t>
</w:r>
<w:r w:rsidR="00F11D3D">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> ${time?if_exists}</w:t>
</w:r>
</w:p>
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="a5"/>
<w:tblW w:w="0" w:type="auto"/>
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="4148"/>
<w:gridCol w:w="4148"/>
</w:tblGrid>
<#list headerInfos as item>
<w:tr w:rsidR="002D34D9" w:rsidRPr="002D34D9" w14:paraId="1556B648" w14:textId="77777777" w:rsidTr="002D34D9">
<w:tc>
<w:tcPr>
<w:tcW w:w="4148" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="403327C7" w14:textId="76B547FA" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>${item.text?if_exists}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="4148" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="0589DFAD" w14:textId="14C36BD0" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="007753C9" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>${item.info?if_exists}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
<#list stepInfos as item>
<w:tr w:rsidR="002D34D9" w:rsidRPr="002D34D9" w14:paraId="4B0FCABA" w14:textId="77777777" w:rsidTr="002115B7">
<w:tc>
<w:tcPr>
<w:tcW w:w="8296" w:type="dxa"/>
<w:gridSpan w:val="2"/>
</w:tcPr>
<w:p w14:paraId="60FC532D" w14:textId="0B0C8EDD" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="000306B5" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>${item_index + 1}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>.</w:t>
</w:r>
<w:r w:rsidR="002D34D9" w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>${item.name?if_exists}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<#if item.images?? && (item.images?size > 0)>
<#list item.images as image>
<w:tr w:rsidR="002D34D9" w:rsidRPr="002D34D9" w14:paraId="6B51B6C3" w14:textId="77777777" w:rsidTr="002D34D9">
<w:tc>
<w:tcPr>
<w:tcW w:w="4148" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="6E030ECE" w14:textId="77777777" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>${image.title?if_exists}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="37E11232" w14:textId="065BEEE6" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>${image.text?if_exists}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="4148" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="5083CCB9" w14:textId="69DB488F" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:spacing w:line="240" w:lineRule="atLeast"/>
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="002D34D9">
<w:rPr>
<w:rFonts w:asciiTheme="minorEastAsia" w:hAnsiTheme="minorEastAsia" w:hint="eastAsia"/>
<w:noProof/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:drawing>
<wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="1C7B8E10" wp14:editId="4CFE615F">
<wp:extent cx="2204016" cy="1470394"/>
<wp:effectExtent l="0" t="0" r="6350" b="0"/>
<wp:docPr id="${image.id}" name="${image.name}"/>
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
</wp:cNvGraphicFramePr>
<a:graphic
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic
xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="${image.id}" name="${image.name}"/>
<pic:cNvPicPr/>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="${image.embed}" cstate="print">
<a:extLst>
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
<a14:useLocalDpi
xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/>
</a:ext>
</a:extLst>
</a:blip>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr>
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="2204016" cy="1470394"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
</#if>
</#list>
</w:tbl>
<w:p w14:paraId="3BA3E4AF" w14:textId="77777777" w:rsidR="002D34D9" w:rsidRPr="002D34D9" w:rsidRDefault="002D34D9" w:rsidP="002D34D9">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
</w:pPr>
</w:p>
<w:sectPr w:rsidR="002D34D9" w:rsidRPr="002D34D9">
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/>
<w:cols w:space="425"/>
<w:docGrid w:type="lines" w:linePitch="312"/>
</w:sectPr>
</w:body>
</w:document>
document.xml.rels 图片文件处理
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships
xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
<#if images?? && (images?size > 0)>
<#list images as item>
<Relationship Id="${item.embed}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="${'media/'+item.name}"/>
</#list>
</#if>
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" Target="endnotes.xml"/>
<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml"/>
<Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
</Relationships>