Java生成word通報(使用echart、poi-tl、PhantomJS)
前段時間客戶需要系統自動生成服務通報,word文檔中要有圖片、表格、文字。第一次做這種通報,項目經理又想用以前的方式,只好找新技術、新方法去實現這個功能。希望能幫到有需要的人
poi-tl介紹
使用Java導出Word一般用POI,但是用POI來導出Word,要寫大量的段落、樣式等細節代碼,代碼多且不易維護。通過 poi-tl 只需要製作導出的模版,服務端基本一行代碼調用,傳入模版路徑和Map或者Bean即可生成Word模版,代碼量大大降低,以後導出樣式不滿意的時候,只需要修改Word模版文件即可。但是poi-tl比較新,且只能生成 docx 文件,對word2007之前的 doc 文檔不支持。
關於poi-tl的更多信息可在https://github.com/Sayi/poi-tl這裏仔細學習,技術較新,但使用方便。
首先下載poi-tl包,並導入工程中。這裏注意,如果工程裏有poi3.15的包最好替換掉,最起碼要poi3.16才行。
代碼實例如下:
/**
* 測試的bean.
* @author
*/
public class Travel {
private String title;
private String smallTitle;
private String startDate;
private String endDate;
private int count;
private double money;
private String place1;
private String place2;
private PictureRenderData pic;
/**
* 構造方法.
*/
public Travel() {
super();
}
/*getter和setter方法.*/
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSmallTitle() {
return smallTitle;
}
public void setSmallTitle(String smallTitle) {
this.smallTitle = smallTitle;
}
public String getStartDate() {
return startDate;
}
public void setStartDate(String startDate) {
this.startDate = startDate;
}
public String getEndDate() {
return endDate;
}
public void setEndDate(String endDate) {
this.endDate = endDate;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getPlace1() {
return place1;
}
public void setPlace1(String place1) {
this.place1 = place1;
}
public String getPlace2() {
return place2;
}
public void setPlace2(String place2) {
this.place2 = place2;
}
public PictureRenderData getPic() {
return pic;
}
public void setPic(PictureRenderData pic) {
this.pic = pic;
}
}
/**
* poi-tl庫的使用示例.
* Created by blinkfox on 2017/6/27.
*/
public class PoitlTest {
private static final Logger log = LoggerFactory.getLogger(PoitlTest.class);
/** 項目資源路徑. */
private static final String PATH = "E:/poitl/test";
/** word模板路徑. */
private static final String DOC_PATH = PATH + "/template/test.docx";
/** 圖片路徑. */
private static final String PIC_PATH = PATH + "/template/image/pic.png";
/** 輸出文件及路徑. */
private static final String OUTPUT_PATH = "F:/doctest/out_word.docx";
/**
* 構造Bean型的data數據.
* @return map
*/
private static Travel buildBeanData() {
Travel travel = new Travel();
travel.setTitle("這是一個大標題");
travel.setSmallTitle("小標題一個");
travel.setStartDate("2018-01-01");
travel.setEndDate("2022-01-01");
travel.setCount(6666);
travel.setPlace1("測試文字1");
travel.setPlace2("測試文字2");
travel.setMoney(10000000);
travel.setPic(new PictureRenderData(600, 400, PIC_PATH));
return travel;
}
/**
* main方法.
* @param args 數組參數
*/
public static void main(String[] args) throws IOException {
XWPFTemplate template = XWPFTemplate.compile(DOC_PATH).render(buildBeanData());
FileOutputStream out = new FileOutputStream(OUTPUT_PATH);
template.write(out);
out.flush();
out.close();
template.close();
log.info("通過'poi-tl'導出word成功!");
}
}
至此對poi-tl的使用應該有了基本的瞭解,下面就是使用echart調取後臺程序生成想要的圖表,然後使用PhantomJS打開顯示echart圖表的jsp,我這裏偷懶了,沒有將echart圖片的Base64信息傳到後臺解析,而是直接使用PhantomJS的js腳本中render這個方法將圖表截取成圖片存放在一個固定位置,poi-tl取圖片時也直接從此位置調取。
下面是echart生成圖表的代碼:
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>主要事項統計餅圖</title>
<link rel="stylesheet" type="text/css" href="/app/css/yth/css.css"/>
<script type="text/javascript" src="fw/html/jscript/jquery-1.4.2.min.js"></script>
</head>
<div id="main" style="width:800px;height:300px"></div>
</html>
<!-- ECharts單文件引入-->
<script src="fw/echarts/echarts.js" charset="UTF-8"></script>
<script type="text/javascript">
//路徑監控
$(document).ready(function(){
//alert("路徑問題?");
});
window.onload = function(){
qudaotj_onload();
}
var myChart;
var option;
// 路徑配置
require.config({
paths: {
echarts: 'fw/echarts'
}
});
function qudaotj_onload(){
require(
[
'echarts',
'echarts/chart/funnel',
'echarts/chart/pie'
],
function(ec) {
myChart = ec.init(document.getElementById('main'));
// 過渡---------------------
option = {
noDataLoadingOption: {
text: ' ',
effect: 'bubble',
effectOption: {
effect: {
n: 0
}
}
},
title : {
text: '主要事項統計',
subtext: '',
x:'center'
},
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient : 'vertical',
x : 'left',
data:[]
},
toolbox: {
show : true,
feature : {
}
},
calculable : true,
series : [
{
name:'事項類型',
type:'pie',
radius : '55%',
center: ['50%', '60%'],
data:[
],
itemStyle:{
normal:{
label:{
show: true,
formatter: '{b} : {c} ({d}%)'
},
labelLine :{show:true}
},
emphasis : {
label : {
show : true,
position : 'center',
formatter : "{d}%",
textStyle : {
color : 'red',
fontSize : '20',
fontFamily : '微軟雅黑',
fontWeight : 'bold'
}
}
}
}
}
]
};
getSllxtj();
myChart.setOption(option);
myChart.hideLoading();
}
,100);
}
//setInterval(getQdtj,15000); 暫時先註釋
function getSllxtj(){
var Request = new Object();
Request = GetRequest();
var tbmonth,method;
tbmonth=Request['tbmonth'];
method=Request['method'];
//alert(tbmonth+"2222"+method);
$.ajax({
type: "POST",
url:"servlet/Itemtypeservlet?tbmonth="+tbmonth+"&method="+method,
cache: false,
dataType : "text",
success:function(data){
try{
//alert(data);
var result = eval(data)[0];
var seriesArray=[];
seriesArray.push({
name: '事項類型',
type: 'pie',
radius : '55%',
center: ['50%', '60%'],
data: eval(result['sxlxdata']),
itemStyle:{
normal:{
label:{
show: true,
formatter: '{b} : {c} ({d}%)'
},
labelLine :{show:true}
},
emphasis : {
label : {
show : true,
position : 'center',
formatter : "{d}%",
textStyle : {
color : 'red',
fontSize : '20',
fontFamily : '微軟雅黑',
fontWeight : 'bold'
}
}
}
}
});
option.legend= {orient : 'vertical',x : 'left',data:eval(result['shixiangleixing'])} ;
option.series = seriesArray;
myChart.setOption(option);
}catch(err){
alert("獲取數據失敗。\n"+err.description);
return false;
}
},
error: function(data) {
return false;
}
});
}
function GetRequest() {
var url = location.search; //獲取url中"?"符後的字串
var theRequest = new Object();
if (url.indexOf("?") != -1) {
var str = url.substr(1);
strs = str.split("&");
for(var i = 0; i < strs.length; i ++) {
theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}
</script>
下面是Java使用PhantomJS截圖
public class Phantomjsdownload {
private static final Logger log = LoggerFactory.getLogger(Phantomjsdownload.class);
//phantomjs瀏覽器路徑
private static final String PHANTOM_PATH = "/fw/phantomjs2.1.1/phantomjs";
//截圖js的路徑
private static final String TEST_JS = "/jsp/fuwutongbao/js/echarts_load.js ";
public static String downloadImage(String url,String tnames,String projectPath) throws IOException {
String tname=tnames.replace("-", "");
String cmdStr = projectPath+PHANTOM_PATH +" "+ projectPath+TEST_JS +" "+ url+" "+tname;
log.info("命令行字符串:{}", cmdStr);
System.out.println(cmdStr);
Runtime rt = Runtime.getRuntime();
try {
Process p = rt.exec(cmdStr);
InputStream is = p.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuffer sbf = new StringBuffer();
String tmp = "";
while((tmp = br.readLine())!=null){
sbf.append(tmp);
}
System.out.println(sbf.toString());
return "/opt/image/echart_"+tname+".png";
} catch (IOException e) {
log.error("執行phantomjs的指令失敗!請檢查是否安裝有PhantomJs的環境或配置path路徑!PhantomJs詳情參考這裏:http://phantomjs.org", e);
}
return "/opt/image/echart_"+tname+".png";
}
}
書寫PhantomJS腳本 echarts_load.js 來加載和調用圖片下載的代碼:
var system = require('system');
var page = require('webpage').create();
page.settings.userAgent= 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR2.0.50727)';
// 如果是windows,設置編碼爲gbk,防止中文亂碼,Linux本身是UTF-8
var osName = system.os.name;
console.log('os name:' + osName);
if ('windows' === osName.toLowerCase()) {
phantom.outputEncoding="gbk";
}
// 獲取第二個參數(即請求地址url).
var url = system.args[1];
console.log('url:' + url);
//獲取第三參數(用來區別各圖片)
var tname = system.args[2];
// 顯示控制檯日誌.
page.onConsoleMessage = function(msg, lineNum, sourceId) {
console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
};
page.viewportSize = { width: 750, height: 400 };
page.clipRect = { top: 0, left: 0, width: 750, height: 400 };
//打開給定url的頁面.
var start = new Date().getTime();
page.open(url, function(status) {
if (status == 'success') {
console.log('echarts頁面加載完成,加載耗時:' + (new Date().getTime() - start) + ' ms');
// 由於echarts動畫效果,延遲500毫秒確保圖片渲染完畢再調用下載圖片方法.
setTimeout(function() {
page.evaluate(function() {
//postImage();
console.log("調用了echarts的下載圖片功能.");
});
}, 1000);
} else {
console.log("頁面加載失敗 Page failed to load!");
}
var path="/opt/image/echart_"+tname+".png"
// 2秒後再關閉瀏覽器.
setTimeout(function() {
page.render(path);
phantom.exit();
}, 4000);
});
這裏可使用同一js爲不同echart截圖,接收不同的值爲生成圖片命名就好了。
到這裏技術上的問題基本解決了,整理上方poi-tl生成word的方式就可以生成想要的word通報。poi-tl比傳統的poi要簡單的多,但也有不足,就是在生成表格時會很麻煩,特別是需要合併單元格的複雜表格。建議word模板裏的表格在word裏先畫好,再以插入文字的方式生成想要的表格,而且樣式只要在word中更改就可以了。
主要參考了閃爍之狐的文章 :使用Java調用PhantomJS動態導出ECharts圖片到Word文件中
http://blinkfox.com/shi-yong-javadiao-yong-phantomjsdong-tai-dao-chu-echartstu-pian-dao-wordwen-jian-zhong/
非常全面易懂