将多个Echarts图片导入word文档

  大家好,今天跟大家分享一个近期项目中遇到的问题以及解决的方案。我是抛砖引玉,大家如果有更好的方案,欢迎讨论;如果正好你也遇到这样的问题,可以参考一下这个方案。


功能需求

  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编码部分,具体代码如下:

  1. 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>
  1. 参数带入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.最后导出结果

QQ图片20200316103120.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容