Java生成word通報(使用echart、poi-tl、PhantomJS)

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/
非常全面易懂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章