整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          Java純后端生成PDF格式報表的三種方案(包含echarts圖表)

          近做了一個奇葩的需求,研究了一下Java純后端生成PDF報表的方案,順便將研究的方案做個總結(jié)復(fù)盤,分享一下。

          需求分析:Java后端定時任務(wù)統(tǒng)計匯總成報表數(shù)據(jù),并生成PDF格式的報表文件,并通過郵件、企業(yè)微信等發(fā)送給指定接收人。報表界面包含動態(tài)文字說明、折線圖、餅圖、條形圖等圖表,界面效果和前端生成的界面相同。


          功能難點:前端要生成樣式好看的圖表比較簡單,像Echarts這些前端工具都有現(xiàn)成的功能來完成。但是現(xiàn)在的需求是后端定時任務(wù)生成報表文件,報表界面的渲染都必須有后端來完成,由于缺少前端的用戶操作動作,也無法在前端生成圖表的圖片后傳到后端來。


          方案一:

          使用FreeMarker+iText生成PDF文件。

          原理和流程:

          FreeMarker是一款模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來生成輸出文本(HTML網(wǎng)頁、電子郵件、配置文件、源代碼等)的通用工具。

          iText是一種生成PDF報表的Java組件。通過在服務(wù)器端使用Jsp或JavaBean生成PDF報表,客戶端采用超鏈接顯示或下載得到生成的報表,這樣就很好的解決了B/S系統(tǒng)的報表處理問題。

          具體的流程如下:


          缺點:這種方案只能生成很簡單的Table模板,由于iText對html的要求非常嚴格,太復(fù)雜的界面會報錯,所以無法生成Echarts的圖表。


          方案二:

          SwingUI+JFreeChart+JFreePDF生成PDF文件

          這里JFreeChart和JFreePDF都是maven依賴包

          JFreeChart是Java客戶端應(yīng)用的一個界面組件,在SwingUI上畫出圖表控件。

          JFreePDF是能將JPanel面板截屏生成PDF的插件。

          流程和原理:


          缺點:

          由于是將JPanel截屏生成的PDF。所以界面樣式上比較難看,比不上前端界面生成的報表頁面。

          而且JFreePDF這個maven依賴的插件是基于JDK11開發(fā)的,如果要兼容JDK8,就要到github上將源碼下載下來,自己編譯生成一個兼容JDK8的依賴包。


          方案三:(最終采用方案)

          使用wkhtmltopdf+靜態(tài)html界面生成pdf界面

          wkhtmltopdf是一個將靜態(tài)html網(wǎng)頁截屏生成pdf文件的工具,Linux、Mac、Windows各個操作系統(tǒng)的版本都有。只需要輸入目標網(wǎng)頁的URL就能將網(wǎng)頁完成的導(dǎo)出PDF文件。

          流程和原理:

          1.在操作系統(tǒng)安裝wkhtmltopdf工具

          2.前端編碼html+jquery+echarts的純靜態(tài)頁面,由于wkhtmltopdf工具使用內(nèi)置的WebKit內(nèi)核版本較低,所以不兼容太新的js語言,像VueJS這些最新的框架就無法使用這個工具。目前測試的能夠兼容的echarts版本是4.2.1.

          3.調(diào)用wkhtmltopdf命令輸入靜態(tài)網(wǎng)頁地址生成pdf文件。

          之前為了調(diào)試網(wǎng)頁寫了一個Java桌面應(yīng)用來調(diào)用wkhtmltopdf工具生成pdf。

          github地址:https://github.com/WrathLi/html2pdf

          缺點:

          1.需要在服務(wù)器系統(tǒng)中先安裝wkhtmltopdf工具;

          2.只能單獨開發(fā)一個純靜態(tài)的html頁面來生成報表

          優(yōu)點:

          界面美觀,因為是直接截取html網(wǎng)頁,所以和前端生成的圖表樣式一樣。

          開發(fā)量最小。

          最終效果:

          tml2canvas

          簡介

          我們可以直接在瀏覽器端使用html2canvas,對整個或局部頁面進行‘截圖’。但這并不是真的截圖,而是通過遍歷頁面DOM結(jié)構(gòu),收集所有元素信息及相應(yīng)樣式,渲染出canvas image。

          由于html2canvas只能將它能處理的生成canvas image,因此渲染出來的結(jié)果并不是100%與原來一致。但它不需要服務(wù)器參與,整個圖片都由客戶端瀏覽器生成,使用很方便。

          使用

          使用的API也很簡潔,下面代碼可以將某個元素渲染成canvas:

          html2canvas(element, {
           onrendered: function(canvas) {
           // canvas is the final rendered <canvas> element
           }
          });
          

          通過onrendered方法,可以將生成的canvas進行回調(diào),比如插入到頁面中:

          html2canvas(element, {
           onrendered: function(canvas) {
           document.body.appendChild(canvas);
           }
          });
          

          做個小例子代碼如下,在線展示鏈接demo1

          <html>
           <head>
           <title>html2canvas example</title>
           <style type="text/css">...</style>
           </head>
           <body>
           <header>
           <nav>
           <ul>
           <li>one</li>
           ...
           </ul>
           </nav>
           </header>
           <section>
           <aside>
           <h3>it is a title</h3>
           <a href="">Stone Giant</a>
           ...
           </aside>
           <article>
           <img src="./Stone.png">
           <h2>Stone Giant</h2>
           <p>Coming ... </p>
           <p>以一團石頭...</p>
           </article>
           </section>
           <footer>write by linwalker @2017</footer>
           <script type="text/javascript" src="./html2canvas.js"></script>
           <script type="text/javascript">
           html2canvas(document.body, {
           onrendered:function(canvas) {
           document.body.appendChild(canvas)
           }
           })
           </script>
           </body>
          </html>
          

          這個例子將頁面body中的元素渲染成canvas,并插入到body中

          jsPDF

          jsPDF庫可以用于瀏覽器端生成PDF。

          文字生成PDF

          使用方法如下:

          // 默認a4大小,豎直方向,mm單位的PDF
          var doc = new jsPDF();
          // 添加文本‘Download PDF’
          doc.text('Download PDF!', 10, 10);
          doc.save('a4.pdf');
          

          在線演示demo2

          圖片生成PDF

          使用方法如下:

          // 三個參數(shù),第一個方向,第二個單位,第三個尺寸格式
          var doc = new jsPDF('landscape','pt',[205, 115])
          // 將圖片轉(zhuǎn)化為dataUrl
          var imageData = ‘data:image/png;base64,iVBORw0KGgo...’;
          doc.addImage(imageData, 'PNG', 0, 0, 205, 115);
          doc.save('a4.pdf');
          

          在線演示demo3

          文字與圖片生成PDF

          // 三個參數(shù),第一個方向,第二個尺寸,第三個尺寸格式
          var doc = new jsPDF('landscape','pt',[205, 155])
          // 將圖片轉(zhuǎn)化為dataUrl
          var imageData = ‘data:image/png;base64,iVBORw0KGgo...’;
          //設(shè)置字體大小
          doc.setFontSize(20);
          //10,20這兩參數(shù)控制文字距離左邊,與上邊的距離
          doc.text('Stone', 10, 20);
          // 0, 40, 控制文字距離左邊,與上邊的距離
          doc.addImage(imageData, 'PNG', 0, 40, 205, 115);
          doc.save('a4.pdf')
          

          在線演示demo4

          生成pdf需要把轉(zhuǎn)化的元素添加到j(luò)sPDF實例中,也有添加html的功能,但某些元素?zé)o法生成在pdf中,因此可以使用html2canvas + jsPDF的方式將頁面轉(zhuǎn)成pdf。通過html2canvas將遍歷頁面元素,并渲染生成canvas,然后將canvas圖片格式添加到j(luò)sPDF實例,生成pdf。

          html2canvas + jsPDF

          單頁

          將demo1的例子修改下:

          <script type="text/javascript" src="./js/jsPdf.debug.js"></script>
          <script type="text/javascript">
           var downPdf = document.getElementById("renderPdf");
           downPdf.onclick = function() {
           html2canvas(document.body, {
           onrendered:function(canvas) {
           //返回圖片dataURL,參數(shù):圖片格式和清晰度(0-1)
           var pageData = canvas.toDataURL('image/jpeg', 1.0);
           //方向默認豎直,尺寸ponits,格式a4[595.28,841.89]
           var pdf = new jsPDF('', 'pt', 'a4');
           //addImage后兩個參數(shù)控制添加圖片的尺寸,此處將頁面高度按照a4紙寬高比列進行壓縮
           pdf.addImage(pageData, 'JPEG', 0, 0, 595.28, 592.28/canvas.width * canvas.height );
           pdf.save('stone.pdf');
           }
           })
           }
          </script>
          

          在線演示demo5

          如果頁面內(nèi)容根據(jù)a4比例轉(zhuǎn)化后高度超過a4紙高度呢,生成的pdf會怎么樣?會分頁嗎?

          你可以試試,驗證一下自己的想法: demo6

          jsPDF提供了一個很有用的API,addPage(),我們可以通過pdf.addPage(),來添加一頁pdf,然后通過pdf.addImage(...),將圖片賦予這頁pdf來顯示。

          那么我們?nèi)绾未_定哪里分頁?

          這個問題好回答,我們可以設(shè)置一個pageHeight,超過這個高度的內(nèi)容放入下一頁pdf。

          來捋一下思路,將html頁面內(nèi)容生成canvas圖片,通過addImage將第一頁圖片添加到pdf中,超過一頁內(nèi)容,通過addPage()添加pdf頁數(shù),然后再通過addImage將下一頁圖片添加到pdf中。

          嗯~,很好!巴特,難道沒有發(fā)現(xiàn)問題嗎?

          這個方法實現(xiàn)的前提是 — — 我們能根據(jù)pageHeight先將整頁內(nèi)容生成的canvas圖片分割成對應(yīng)的小圖片,然后一個蘿卜一個坑,一頁一頁addImage進去。

          What? 想一想我們的canvas是腫么來的,不用拉上去,直接看下面:

          html2canvas(document.body, {
           onrendered:function(canvas) {
           //it is here we handle the canvas
           }
          })
          

          這里的body就是要生成canvas的元素對象,一個元素生成一個canvas;那么我們需要一頁一頁的canvas,也就是說。。。

          你覺得可能嗎? 我覺得不太現(xiàn)實,按這思路要獲取頁面上不同位置的DOM元素,然后通過htnl2canvas(element,option)來處理,先不說能不能剛好在每個pageHeight的位置剛好找到一個DOM元素,就算找到了,這樣做累不累。

          累的話

          :)可以看看下面這種方法

          多頁

          我提供的思路是我們只生成一個canvas,對就一個,轉(zhuǎn)化元素就是你要轉(zhuǎn)成pdf內(nèi)容的母元素,在這篇demo里就是body了;其他不變,也是超過一頁內(nèi)容就addPage,然后addImage,只不過這里添加的是同一個canvas。

          當然這樣做只會出現(xiàn)多頁重復(fù)的pdf,那到底怎么實現(xiàn)正確分頁顯示。其實主要利用了jsPDF的兩點:

          - 超過jsPDF實例格式尺寸的內(nèi)容不顯示
          (var pdf = new jsPDF('', 'pt', 'a4'); demo中就是a4紙的尺寸)
          - addImage有兩個參數(shù)可以控制圖片在pdf中的位置
          

          雖然每一頁pdf上顯示的圖片是相同的,但我們通過調(diào)整圖片的位置,產(chǎn)生了分頁的錯覺。以第二頁為例,將豎直方向上的偏移設(shè)置為-841.89即一張a4紙的高度,又因為超過a4紙高度范圍的圖片不顯示,所以第二頁顯示了圖片豎直方向上[841.89,1682.78]范圍內(nèi)的內(nèi)容,這就得到了分頁的效果,以此類推。

          還是看代碼吧:

          html2canvas(document.body, {
           onrendered:function(canvas) {
           var contentWidth = canvas.width;
           var contentHeight = canvas.height;
           //一頁pdf顯示html頁面生成的canvas高度;
           var pageHeight = contentWidth / 592.28 * 841.89;
           //未生成pdf的html頁面高度
           var leftHeight = contentHeight;
           //頁面偏移
           var position = 0;
           //a4紙的尺寸[595.28,841.89],html頁面生成的canvas在pdf中圖片的寬高
           var imgWidth = 595.28;
           var imgHeight = 592.28/contentWidth * contentHeight;
           var pageData = canvas.toDataURL('image/jpeg', 1.0);
           var pdf = new jsPDF('', 'pt', 'a4');
           //有兩個高度需要區(qū)分,一個是html頁面的實際高度,和生成pdf的頁面高度(841.89)
           //當內(nèi)容未超過pdf一頁顯示的范圍,無需分頁
           if (leftHeight < pageHeight) {
           pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight );
           } else {
           while(leftHeight > 0) {
           pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
           leftHeight -= pageHeight;
           position -= 841.89;
           //避免添加空白頁
           if(leftHeight > 0) {
           pdf.addPage();
           }
           }
           }
           pdf.save('content.pdf');
           }
          })
          

          在線演示demo7

          兩邊留邊距

          修改imgWidth,并且在addImage時x方向參數(shù)設(shè)置你要的邊距,具體代碼如下

          var imgWidth = 555.28;
          var imgHeight = 555.28/contentWidth * contentHeight;
          ...
          pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight );
          ...
          pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight);
          

          在線演示demo8

          tml2pdf

          selenium

          Selenium 通過使用 WebDriver 支持市場上所有主流瀏覽器的自動化。 Webdriver 是一個 API 和協(xié)議,它定義了一個語言中立的接口,用于控制 web 瀏覽器的行為。 每個瀏覽器都有一個特定的 WebDriver 實現(xiàn),稱為驅(qū)動程序。 驅(qū)動程序是負責(zé)委派給瀏覽器的組件,并處理與 Selenium 和瀏覽器之間的通信。

          這種分離是有意識地努力讓瀏覽器供應(yīng)商為其瀏覽器的實現(xiàn)負責(zé)的一部分。 Selenium 在可能的情況下使用這些第三方驅(qū)動程序, 但是在這些驅(qū)動程序不存在的情況下,它也提供了由項目自己維護的驅(qū)動程序。

          Selenium 框架通過一個面向用戶的界面將所有這些部分連接在一起, 該界面允許透明地使用不同的瀏覽器后端, 從而實現(xiàn)跨瀏覽器和跨平臺自動化。

          selenium 驅(qū)動

          # selenium 驅(qū)動
          https://selenium-python.readthedocs.io/installation.html#drivers
          https://selenium-python.readthedocs.io/api.html

          selenium-java

          主要依賴

                  <dependency>
                      <groupId>org.seleniumhq.selenium</groupId>
                      <artifactId>selenium-java</artifactId>
                      <version>4.16.1</version>
                  </dependency>
          

          測試代碼

                  // 獲取 java 版本
                  String version = System.getProperty("java.specification.version");
          
                  // 獲取系統(tǒng)類型
                  String platform = System.getProperty("os.name", "");
                  platform = platform.toLowerCase().contains("window") ? "win" : "linux";
          
                  // 當前程序目錄
                  String current = System.getProperty("user.dir");
          
                  System.out.println("current:" + current);
          
                  // firefox 運行參數(shù)配置
                  FirefoxOptions options = new FirefoxOptions();
                  // 無頭模式
                  options.addArguments("--headless");
                  // 最大化
                  options.addArguments("--start-maximized");
          
                  FirefoxDriver browser = new FirefoxDriver(options);
          
                  Path url = Paths.get(current, "..", "index.html");
                  System.out.println("url:" + url.toString());
          
                  // NOTE 要使用 file 協(xié)議
                  browser.get(String.format("file://%s", url.toString()));
          
                  // 打印設(shè)置
                  PrintOptions print = new PrintOptions();
                  Pdf pdf = browser.print(print);
          
                  // pdf base64 內(nèi)容
                  String content = pdf.getContent();
                  // 解碼內(nèi)容
                  Base64.Decoder decoder = Base64.getDecoder();
                  byte[] buffer = decoder.decode(content);
          
                  try {
                      // 將 byte 寫入文件
                      Path file = Paths.get(String.format("java%s_%s.pdf", version, platform));
                      Files.write(file, buffer);
                  } catch (IOException e) {
                      throw new RuntimeException(e);
                  } finally {
                      browser.quit();
                  }
          

          效果預(yù)覽

          selenium/java11_linux.pdf · yjihrp/linux-html2pdf-demo - Gitee.com

          selenium/java11_win.pdf · yjihrp/linux-html2pdf-demo - Gitee.com

          測試結(jié)果


          測試結(jié)果

          下一篇 6-LINUX HTML 轉(zhuǎn) PDF-selenium-python


          主站蜘蛛池模板: 成人国产一区二区三区| 日韩内射美女人妻一区二区三区| 国产另类TS人妖一区二区| 东京热无码av一区二区| 综合人妻久久一区二区精品| 亚洲爆乳无码一区二区三区| 国产精品视频一区二区三区| 亚洲日韩一区精品射精| 濑亚美莉在线视频一区| 中文字幕一区二区三区视频在线| 色欲综合一区二区三区| 在线视频一区二区日韩国产| 丝袜人妻一区二区三区网站| 91video国产一区| 美女免费视频一区二区| 福利一区二区三区视频在线观看| 亚洲日韩AV无码一区二区三区人 | 国产精品自拍一区| 日本韩国一区二区三区| 亚洲乱色熟女一区二区三区丝袜 | 国产成人无码aa精品一区| 中文无码精品一区二区三区| 久久毛片一区二区| 亚洲色欲一区二区三区在线观看| 少妇一夜三次一区二区| 高清国产精品人妻一区二区| 国产乱码精品一区二区三区香蕉 | 亚洲不卡av不卡一区二区| 午夜性色一区二区三区不卡视频 | 在线精品视频一区二区| 国产成人高清视频一区二区 | 东京热无码av一区二区| 精产国品一区二区三产区| 高清一区二区三区日本久| 日本美女一区二区三区| 亚洲中文字幕乱码一区| 国产精品毛片VA一区二区三区| 在线观看日韩一区| 中文字幕乱码一区久久麻豆樱花| 国产一区三区二区中文在线| 午夜性色一区二区三区不卡视频|