大家好,今天跟大家分享一个近期项目中遇到的问题以及解决的方案。我是抛砖引玉,大家如果有更好的方案,欢迎讨论;如果正好你也遇到这样的问题,可以参考一下这个方案。
功能需求
1. 一键导出word报告
2. 需要将多个echarts图片存入word
环境
1. jdk8及以上
2. 前端使用echarts或其他canvas类型图表
3. freemarker
4. tomcat
思路
1. 将echarts图片转为base64编码,以post方式传参到后台。
这里有一个问题,tomcat对于post参数有默认最大长度(2M),echarts图形生成的base64编码是相当大的,一个有几百K大小是正常的,所以当有10个echarts图片要导出时,就会超过tomcat的限制。
有两个解决方案:
1. 修改tomcat的server.xml配置
2. 当echarts初始化完成后,将每个echarts图片的base64编码通过ajax方式单独保存(记得要有编号id),推荐保存到redis(保存到mysql也是一样的,不过redis的读写比较快)。
用过echarts、highcharts以及其他canvas做数据可视化图表的朋友应该都知道,canvas图片可以通过canvas.toDataURL("image/png")方法将图片转化为base64编码。
其实echarts也有类似的方法,代码如下:
// myChart代表echarts实例
var dataUrl = myChart.getDataURL({
// 导出的格式,可选 png, jpeg
type : "png",
// 导出的图片分辨率比例,默认为 1。
pixelRatio : 2,
// 导出的图片背景色,默认使用 option 里的backgroundColor
backgroundColor : '#fff'
});
dataUrl = dataUrl.substring(22);
// savePngBase64(dataUrl,parsenum);
生成的base64编码部分如下(完整的数据太长了,这里只展示部分,大家看格式即可):
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArcAAAPZCAYAAAALFALpAAAgAElEQVR4XuxdB5gURdp+Z2ZzzrsE
ERVEDwMgoh6CKJhQD3P2wIThDOedOVxQ74ynvzmiICeeOWAGBUUEEQEjAipB0i6b887uzvzPW7s1
1vZ2T/dMz8IuVD3PPYc7VdVVb1V3v/3V+32fZ8RBo4Llj58JXaJDIOfiGVi0YJ7HrrUVzv41xSi+
9mnRvPDuC5DQv9C0q8ZlP2P9xHuROKhvp3ryt5Tf/w6ph+2LLbe/gJQD90DBvybBmxCP4r9PR93H
y1Dwj7ORccJItFbWovjGqaif/z3yrj4ZWeeMFdeUY2lasR6ZZ4xBXEG2+HuwyY/aD7+C/5fNSD9m
BBIG9Gn7e0ur6IPXz/nTcciZPF78e+OlDyP96P2RcepolNwyDb7cDBTecR6CLS2omjFXjM1XkCXm
7U1PQeGd56Hpx19RN2sJEvfuj8xTRnfCwCnOduugf489AvtNGL+66tbj+sueWyvrUHzDM217+o7z
2.制作word模板文件demo.ftl,将获得的base64编码以参数的形式带入demo.ftl文件
demo.ftl文件可以通过word制作,将word另存为.xml文件,然后将后缀名改为.ftl即可。flt文件引用base64编码的位置信息一般是这样的,其中${img}代表base64编码部分,具体代码如下:
- ftl文件配置部分
<#list imgs as img>
<w:drawing>
<wp:inline distT="0" distB="0" distL="0" distR="0" wp14:anchorId="19DBB609" wp14:editId="34BC3657">
<wp:extent cx="5259600" cy="7423200"/>
<wp:effectExtent l="0" t="0" r="0" b="0"/>
<wp:docPr id="1" name="图片 1"/>
<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="0" name="Picture 1"/>
<pic:cNvPicPr>
<a:picLocks noChangeAspect="1" noChangeArrowheads="1"/>
</pic:cNvPicPr>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId4${img_index+1}" 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:srcRect/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</pic:blipFill>
<pic:spPr bwMode="auto">
<a:xfrm>
<a:off x="0" y="0"/>
<a:ext cx="5259600" cy="7423200"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</#list>
<pkg:part pkg:name="/word/_rels/document.xml.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml" pkg:padding="256">
<pkg:xmlData>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<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"/>
<Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
<#list imgs as img>
<Relationship Id="rId4${img_index+1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image${img_index+1}.png"/>
</#list>
</Relationships>
</pkg:xmlData>
</pkg:part>
<#list imgs as img>
<pkg:part pkg:name="/word/media/image${img_index+1}.png" pkg:contentType="image/png" pkg:compression="store">
<pkg:binaryData>
${img}
</pkg:binaryData>
</pkg:part>
</#list>
- 参数带入ftl文件Java部分
// 对应上面说的方案:
// 如果是base64编码挨个存库,那么这里需要通过某个关联id去数据库中取base64编码
// 如果是直接通过前端传参,可以通过request.getParameter("dataUrls")方式获取
@RequestMapping("/downloadWord")
public void downloadWord(HttpServletRequest request, HttpServletResponse response) {
String filename = "demo";
final String SPLIT = "###";
String urls = request.getParameter("urls");
String[] urlArr = urls.split(SPLIT);
List<String> imgs = Arrays.asList(urlArr);
Map data = new HashMap();
data.put("imgs",imgs);
try {
WordUtils.exportWord(request, response, data, "demo.ftl");
} catch (Exception e) {
e.printStackTrace();
}
}
WordUtils 工具类(该工具类参考了网上的一些资料)
public class WordUtils {
public static void exportWord(HttpServletRequest request, HttpServletResponse response,
Map map,String ftlFile) throws Exception {
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
String name = URLDecoder.decode(request.getParameter("docName"), "UTF-8");
File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
configuration.setDirectoryForTemplateLoading(new File(templateFolder));
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map,freemarkerTemplate);
fin = new FileInputStream(file);
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
// 设置浏览器以下载的方式处理该文件名
String fileName = name+ ".doc";
// Header Manipulation漏洞,过滤特殊字符
String regex = "[`~!@#$%^&*()\\+\\=\\{}|:\"?><【】\\/r\\/n]";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(fileName);
if(matcher.find()) {
fileName = matcher.replaceAll("");
}
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
out = response.getOutputStream();
byte[] buffer = new byte[512]; // 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} catch (Exception e){
e.printStackTrace();
} finally {
if(fin != null) fin.close();
if(out != null) out.close();
if(file != null) file.delete(); // 删除临时文件
}
}
}
3.最后导出结果