前言
最近在一个项目中遇到导出为Word文件的问题,导出Word的功能很简单,但是导出Word文件中包含数据库动态查询的统计数据而生成的Echarts图片,且导出Word的时机又是在凌晨的服务器定时执行,所以不能通过客户端访问统计页面时再去生成。
服务端语言使用Java语言,最开始考虑使用JFreeChart来生成统计图片,但是JFreeChart生成的图片很丑,且和ECharts的统计图效果截然不同。所以最终抛弃了使用JFreeChart
,而采用了在服务端使用Java调用PhantomJS的指令来导出Ehcarts图片。所以主要的技术方案选型如下:
-
poi-tl,一个简单的基于Word模版生成Word的工具。
-
PhantomJS,一个基于webkit内核的无头浏览器,可在服务端程序实现加载、操作页面等功能
使用poi-tl导出Word
poi-tl介绍
使用Java导出Word通常采用的是Apache POI的库,但是使用POI来导出Word,会书写大量的段落、样式等细节代码,代码量巨大,而且不易于维护。通过poi-tl只需要制作导出的模版,服务端一行代码调用,传入模版路径和Map或者Bean即可生成Word模版,代码量大大降低,以后导出样式不满意的时候,只需要修改Word模版文件即可。
注意:
poi-tl
只能生成docx
文件,对word2007之前的doc
文档则不支持。
Maven引入
<dependency> \r\n <groupId>com.deepoove</groupId>\r\n <artifactId>poi-tl</artifactId>\r\n <version>1.0.0</version></dependency>
注:该包带入了POI3.16,如果系统中本身有低于3.15版本的POI,需要排除掉,否则生成Word时会报错。
demo示例
首先,制作一个用于测试的word模版,使用poi-tl
的标记语法做如下标记,如下图所示:
然后,构造一个需要渲染的model JavaBean类,如果有多个Bean,貌似只能通过继承来复用属性,采用组合的方式是渲染不了的,代码如下:
/**\r\n * BaseProp\r\n * @author blinkfox on 2017-06-28.\r\n */public class BaseProp {\r\n\r\n protected String baseProp;\r\n\r\n /**\r\n * 构造方法.\r\n * @param baseProp 基础属性\r\n */\r\n public BaseProp() {\r\n super();\r\n }\r\n\r\n public String getBaseProp() {\r\n return baseProp;\r\n }\r\n\r\n public void setBaseProp(String baseProp) {\r\n this.baseProp = baseProp;\r\n }}
/**\r\n * 测试旅游信息的bean.\r\n * @author blinkfox on 2017-06-28.\r\n */public class Travel extends BaseProp {\r\n\r\n private String title;\r\n\r\n private String smallTitle;\r\n\r\n private String startDate;\r\n\r\n private String endDate;\r\n\r\n private int count;\r\n\r\n private double money;\r\n\r\n private String place1;\r\n\r\n private String place2;\r\n\r\n private PictureRenderData pic;\r\n\r\n /**\r\n * 构造方法.\r\n */\r\n public Travel() {\r\n super();\r\n }\r\n\r\n /*getter和setter方法.*/\r\n\r\n public String getTitle() {\r\n return title;\r\n }\r\n\r\n public void setTitle(String title) {\r\n this.title = title;\r\n }\r\n\r\n public String getSmallTitle() {\r\n return smallTitle;\r\n }\r\n\r\n public void setSmallTitle(String smallTitle) {\r\n this.smallTitle = smallTitle;\r\n }\r\n\r\n public String getStartDate() {\r\n return startDate;\r\n }\r\n\r\n public void setStartDate(String startDate) {\r\n this.startDate = startDate;\r\n }\r\n\r\n public String getEndDate() {\r\n return endDate;\r\n }\r\n\r\n public void setEndDate(String endDate) {\r\n this.endDate = endDate;\r\n }\r\n\r\n public int getCount() {\r\n return count;\r\n }\r\n\r\n public void setCount(int count) {\r\n this.count = count;\r\n }\r\n\r\n public double getMoney() {\r\n return money;\r\n }\r\n\r\n public void setMoney(double money) {\r\n this.money = money;\r\n }\r\n\r\n public String getPlace1() {\r\n return place1;\r\n }\r\n\r\n public void setPlace1(String place1) {\r\n this.place1 = place1;\r\n }\r\n\r\n public String getPlace2() {\r\n return place2;\r\n }\r\n\r\n public void setPlace2(String place2) {\r\n this.place2 = place2;\r\n }\r\n\r\n public PictureRenderData getPic() {\r\n return pic;\r\n }\r\n\r\n public void setPic(PictureRenderData pic) {\r\n this.pic = pic;\r\n }}
最后,是模拟调用示例:
/**\r\n * poi-tl库的使用示例.\r\n * Created by blinkfox on 2017/6/27.\r\n */public class PoitlTest {\r\n\r\n private static final Logger log = LoggerFactory.getLogger(PoitlTest.class);\r\n\r\n /** 项目资源路径. */\r\n private static final String PATH = "F:/poitl-test/web";\r\n\r\n /** word模板路径. */\r\n private static final String DOC_PATH = PATH + "/template/test/test.docx";\r\n\r\n /** 图片路径. */\r\n private static final String PIC_PATH = PATH + "/template/test/pic.png";\r\n\r\n /** 输出文件及路径. */\r\n private static final String OUTPUT_PATH = "G:/test/poitl_out_word.docx";\r\n\r\n /**\r\n * 构造Bean型的data数据.\r\n * @return map\r\n */\r\n private static Travel buildBeanData() {\r\n Travel travel = new Travel();\r\n travel.setTitle("我的旅游日记");\r\n travel.setSmallTitle("再写日记");\r\n travel.setStartDate("2017-01-01");\r\n travel.setEndDate("2017-06-28");\r\n travel.setCount(3);\r\n travel.setPlace1("九寨沟");\r\n travel.setPlace2("天涯海角");\r\n travel.setMoney(1872.52);\r\n travel.setPic(new PictureRenderData(600, 400, PIC_PATH));\r\n travel.setBaseProp("这是");\r\n return travel;\r\n }\r\n\r\n /**\r\n * main方法.\r\n * @param args 数组参数\r\n */\r\n public static void main(String[] args) throws IOException {\r\n XWPFTemplate template = XWPFTemplate.compile(DOC_PATH).render(buildBeanData());\r\n\r\n FileOutputStream out = new FileOutputStream(OUTPUT_PATH);\r\n template.write(out);\r\n out.flush();\r\n out.close();\r\n template.close();\r\n log.info("通过'poi-tl'导出word成功!");\r\n }}
最后,在导出的文件夹中可查看生成的word文件,如下所示:
Java调用PhantomJS导出Ehcarts图片
PhantomJS介绍
PhantomJS
是一个基于webkit内核的无头浏览器,即没有UI界面的一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。PhantomJS提供JavaScript\r\n \r\nAPI接口,即通过编写js程序可以直接与webkit内核交互,在此之上可以结合Java语言等,通过java调用js等相关操作,从而解决了以前c/c++才能比较好的基于webkit开发优质采集器的限制。
PhantomJS的安装配置
windows环境
如果是在windows环境下,则在官网下载解压到某个目录后,将其bin目录加入到path变量中即可。
Linux环境
如果是在Linux环境下,在官网下载解压后,同样需要将PhantomJS的bin目录加入到path环境变量中,参考的命令和配置如下:
# 编辑配置文件.vi ~/.bashrc# 将PhantomJS的bin目录加入到PATH环境变量中.export PHANTOMJS_HOME=/home/blinkfox/Documents/phantomjs-2.1.1-linux-x86_64 \r\nexport PATH=${PHANTOMJS_HOME}/bin:$PATH# 退出vi编辑器,使用source命令让刚才的配置即时生效.source ~/.bashrc# 测试PhantomJS是否安装成功,如果打出了版本信息,即安装成功.phantomjs -v
demo示例
这个demo的需求是这样的,我们使用Java调用PhantomJS的指令来在服务端加载含ECharts统计的图html文件,然后调用ECharts的生成图片方法,将图片传输到Java后台最终实现保存图片到指定路径中。
首先,制作ECharts的html页面,示例页面如下代码如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>测试的ECharts数据统计图</title> </head> <body> \r\n <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->\r\n <div id="main" style="width:560px; height:270px;"></div><script type="text/javascript" src="/js/lib/jquery/jquery-1.9.1.min.js"></script> <script type="text/javascript" src="/js/lib/echarts/v3/echarts.min.js"></script> <script type="text/javascript">// 基于准备好的dom,初始化echarts实例var myChart = echarts.init(document.getElementById('main'));// 指定图表的配置项和数据var option = { \r\n title: {\r\n text: 'ECharts 入门示例'\r\n },\r\n animation: false, // 关闭动画效果\r\n tooltip: {},\r\n legend: {\r\n data:['销量']\r\n },\r\n xAxis: {\r\n data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]\r\n },\r\n yAxis: {},\r\n series: [{\r\n name: '销量',\r\n type: 'bar',\r\n data: [5, 20, 36, 10, 10, 20]\r\n }]};// 使用刚指定的配置项和数据显示图表。myChart.setOption(option);/**\r\n * ajax传输图片信息.\r\n */function postImage() { \r\n // 向后台发起请求保存图片到指定目录.\r\n $.ajax({\r\n type: 'POST',\r\n url: '/test/saveImage',\r\n data: {picInfo: myChart.getDataURL()},\r\n success: function() {\r\n console.log('通过post请求传输数据成功!');\r\n }\r\n });}</script> </body> </html>
然后,使用Servlet来写一个服务端代码,用来获取Base64的图片信息并在后端解析保存图片,Servlet代码如下:
/**\r\n * 保存Echarts统计图片的Servlet.\r\n * @author blinkfox on 2017-06-28.\r\n */public class SaveImageServlet extends HttpServlet {\r\n\r\n private static final long serialVersionUID = 1L;\r\n\r\n private static final Logger log = LoggerFactory.getLogger(SaveImageServlet.class);\r\n\r\n /**\r\n * 执行获取echarts图片的post请求.\r\n * @param request req\r\n * @param response resp\r\n * @throws ServletException Servlet异常.\r\n * @throws IOException IO异常.\r\n */\r\n @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\r\n // 获取图片信息.\r\n String picInfo = request.getParameter("picInfo");\r\n if (StringUtils.isBlank(picInfo)) {\r\n log.error("picInfo为空,未从前台获取到base64图片信息!");\r\n return;\r\n }\r\n\r\n this.getAndsaveImage(picInfo, "G:/test/image1.png");\r\n }\r\n\r\n /**\r\n * 获取并保存图片到本地.\r\n * @param picInfo 图片信息\r\n * @param imagePath 图片保存的路径\r\n */\r\n private void getAndsaveImage(String picInfo, String imagePath) {\r\n // 传递过程中 "+" 变为了 " ".\r\n String newPicInfo = picInfo.replaceAll(" ", "+");\r\n String picPath = decodeBase64(newPicInfo, new File(imagePath));\r\n log.warn("从echarts中生成图片的的路径为:{}", picPath);\r\n }\r\n\r\n /**\r\n * 解析Base64位信息并输出到某个目录下面.\r\n * @param base64Info base64串\r\n * @param picPath 生成的文件路径\r\n * @return 文件地址\r\n */\r\n private String decodeBase64(String base64Info, File picPath) {\r\n if (StringUtils.isEmpty(base64Info)) {\r\n return null;\r\n }\r\n\r\n // 数据中:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABI4AAAEsCAYAAAClh/jbAAA ... 在"base64,"之后的才是图片信息\r\n String[] arr = base64Info.split("base64,");\r\n\r\n // 将图片输出到系统某目录.\r\n OutputStream out = null;\r\n try {\r\n // 使用了Apache commons codec的包来解析Base64\r\n byte[] buffer = Base64.decodeBase64(arr[1]);\r\n out = new FileOutputStream(picPath);\r\n out.write(buffer);\r\n } catch (IOException e) {\r\n log.error("解析Base64图片信息并保存到某目录下出错!", e);\r\n } finally {\r\n IOUtils.closeQuietly(out);\r\n }\r\n\r\n return picPath;\r\n }}
然后,是书写PhantomJS脚本echarts_load.js
来加载和调用图片下载的代码:
var system = require('system'); var page = require('webpage').create();// 如果是windows,设置编码为gbk,防止中文乱码,Linux本身是UTF-8var osName = system.os.name; console.log('os name:' + osName); if ('windows' === osName.toLowerCase()) { \r\n phantom.outputEncoding="gbk";}// 获取第二个参数(即请求地址url).var url = system.args[1]; console.log('url:' + url);// 显示控制台日志.page.onConsoleMessage = function(msg, lineNum, sourceId) { \r\n console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');};//打开给定url的页面.var start = new Date().getTime(); page.open(url, function(status) { \r\n if (status == 'success') {\r\n console.log('echarts页面加载完成,加载耗时:' + (new Date().getTime() - start) + ' ms');\r\n\r\n // 由于echarts动画效果,延迟500毫秒确保图片渲染完毕再调用下载图片方法.\r\n setTimeout(function() {\r\n page.evaluate(function() {\r\n postImage();\r\n console.log("调用了echarts的下载图片功能.");\r\n });\r\n }, 500);\r\n } else {\r\n console.log("页面加载失败 Page failed to load!");\r\n }\r\n\r\n // 3秒后再关闭浏览器.\r\n setTimeout(function() {\r\n phantom.exit();\r\n }, 3000);});
最后,是使用Java
来调用PhantomJS
的指令,代码如下:
/**\r\n * HttpTest.\r\n * @author blinkfox on 2017-06-28.\r\n * @version 1.0\r\n */public class HttpTest {\r\n\r\n private static final Logger log = LoggerFactory.getLogger(HttpTest.class);\r\n\r\n private static final String PHANTOM_PATH = "phantomjs";\r\n\r\n //这里我的test.js是保存在G盘下面的phantomjs目录\r\n private static final String TEST_JS = "G:/test/phantom/test.js ";\r\n\r\n public void String downloadImage(String url) throws IOException {\r\n String cmdStr = PHANTOM_PATH + TEST_JS + url;\r\n log.info("命令行字符串:{}", cmdStr);\r\n\r\n Runtime rt = Runtime.getRuntime();\r\n try {\r\n rt.exec(cmdStr);\r\n } catch (IOException e) {\r\n log.error("执行phantomjs的指令失败!请检查是否安装有PhantomJs的环境或配置path路径!PhantomJs详情参考这里:http://phantomjs.org", e);\r\n }\r\n }\r\n\r\n /**\r\n * main.\r\n * @param args args\r\n * @throws IOException IO异常\r\n */\r\n public static void main(String[] args) throws IOException {\r\n downloadImage("http://127.0.0.1:8080/test/echart_test/test_echarts.html");\r\n }}
通过调用测试代码即可在指定目录生成Echarts的图片啦!
联系上面生成Word的功能,两个功能一结合即可动态导出ECharts图片到Word文件中。
\r\n
转载请注明:闪烁之狐 » 使用Java调用PhantomJS动态导出ECharts图片到Word文件中\r\n
注意:本文归作者所有,未经作者允许,不得转载