整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Java后端實現(xiàn)HTML網頁報表導出pdf方案

          持原創(chuàng),共同進步!請關注我,后續(xù)分享更精彩!

          概述

          項目中經常有生成圖表報告的需求。實現(xiàn)的方式有很多,下面幾種方案,各有優(yōu)缺點。

          純java后端實現(xiàn):后端JFreeChart等繪制庫畫好圖表,再通過itext庫導出為pdf。該方案能實現(xiàn)簡單的圖表功能,樣式、格式調整等可能會花大量時間。適合中小型報表開發(fā)項目。

          前端繪制圖表,后端運行時命令調用wkhtmltopdf生成pdf:后端通過運行時命令調用node js,js使用wkhtmltopdf庫動態(tài)訪問報表url地址,HTML內容渲染完成后生成pdf文件。該方案,使用純前端js繪制圖表,能實現(xiàn)復雜需求。但wkhtmltopdf庫對不同瀏覽器的js存在兼容性問題,導出成pdf文件時存在各種坑,在單頁面技術支持還不太成熟。適合豐富報表的pdf導出,但兼容性問題維護成本太高。

          前端繪制圖表,后端運行時命令調用puppeteer生成pdf:后端通過運行時命令調用node js,js使用puppeteer庫動態(tài)訪問報表url地址,HTML內容渲染完成后生成pdf文件。該方案和wkhtmltopdf方案類似,但兼容性更好。puppeteer是 Chrome 開發(fā)團隊在 2017 年發(fā)布的一個 Node.js 包,用來模擬 Chrome 瀏覽器的運行。可以在無界面的環(huán)境中運行Chrome或通過命令行、程序語言操作 Chrome。理論上Chrome中顯示的圖表,就能通過該庫生成一致的pdf文件內容,不用浪費很多時間在頁面樣式和兼容性問題上。

          本文選擇puppeteer方案介紹如何生成一個pdf報表。細心的小伙伴可能注意到了,既然puppeteer是js庫,為什么不直接前端導出pdf,干嘛這么麻煩還通過后端繞一圈來實現(xiàn)?

          這主要出于需求和用戶體驗的考慮,有些業(yè)務場景需要通過api接口動態(tài)生成pdf報表,不需要用戶訪問界面。如果生成的pdf的報表很大,直接在用戶端生成,可能占用大量客戶端資源,導致頁面崩潰或假死,從而影響使用體驗。

          實現(xiàn)

          1.先安裝NodeJs,網上教程很多,本文不再贅述。

          2.安裝puppeteer依賴,如果npm下載不成功就使用cnpm命令(cnpm需要先安裝)

          npm install puppeteer --save

          3.在安裝puppeteer依賴的目錄下創(chuàng)建page2pdf.js

          const puppeteer = require('puppeteer');
          const options = process.argv;
          var siteUrl;
          //執(zhí)行 node page2pdf.js https://www.baidu.com
          
          (async() => {
          if(options.length>=3){
              siteUrl=options[2];
              //types=options[3];
          	//console.log(siteUrl);
          }
          
          const browser = await puppeteer.launch();
          const page = await browser.newPage();
          
          //console.log(options.length);
          //console.log(options[0]);
          //console.log(options[1]);
          //console.log(options[2]);
          //console.log(options[3]);
          
          const userAgent = "Mozilla/5.0 (Linux; Android 8.1.0; MI 8 Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36";
          page.setUserAgent(userAgent);
          
          await page.setViewport({ width: 1000, height: 1080 });
          
          //await page.setViewport({ width: 480, height: 800,isMobile: true}); 
          
          //通過css樣式可見,動態(tài)設置站點加載完成標識。
          //page.waitForSelector('img').then(() => console.log('siteUrl with page load success: ' + siteUrl));
          
          await page.goto(siteUrl, {timeout: 10*60000, waitUntil: 'networkidle2'});
          
          /**await page.goto(siteUrl, {timeout: 10*60000, waitUntil: 'networkidle2'})
          			.catch(e => {
          			  console.log(siteUrl+" is error:"+e);
          			  browser.close()
          			});*/
          
          const pdf = await page.pdf({
          		path: 'page.pdf', //便于測試驗證,實際使用時可屏蔽
          		format: 'A4'
          	});
          	
          await browser.close();
          process.stdout.write(pdf);
          })();

          page2pdf.js文件引入puppeteer依賴庫,通過傳入siteUrl參數訪問HTML page頁面,page.pdf生成文件,再通過process.stdout.write(pdf)返回java后臺。

          4.創(chuàng)建java PuppeteerHtmlToPdf.java文件

          /**
           * 用谷歌提供的node實現(xiàn)的Puppeteer,實現(xiàn)網頁生成pdf文件
           */
          public class PuppeteerHtmlToPdf {
              /**
               * html轉pdf,直接通過流輸出到瀏覽器
               * @param response 瀏覽器響應
               * @param fileName 文件名稱
               * @param puppeteerjs 要采用哪個js文件執(zhí)行
               * @param webSiteUrl 要生成pdf的網頁
               */
              public static void parseHtml2Pdf(HttpServletResponse response, String fileName, String puppeteerjs, String webSiteUrl) {
                  try {
                      Runtime rt = Runtime.getRuntime();
                      Process p = rt.exec("node "+puppeteerjs+" "+webSiteUrl);
                      InputStream is = p.getInputStream();
                      BufferedInputStream bf=new BufferedInputStream(is);
                      byte[] data = IOUtils.toByteArray(bf);
                      fileName = URLEncoder.encode(fileName, "UTF-8");
                      response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
                      response.addHeader("Content-Length", "" + data.length);
                      response.setContentType("application/octet-stream;charset=UTF-8");
                      OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
                      outputStream.write(data);
                      outputStream.flush();
                      outputStream.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
          
              }
          }

          5.報表生成

          page2pdf.js頁面目錄打開命令行,執(zhí)行指令。


          運行指令,生成pdf

          node page2pdf.js https://www.baidu.com


          查看對應目錄,已動態(tài)生成了一個page.pdf文件


          打開pdf文件,對應HTML內容已生成。

          小結

          本文介紹了報表導出pdf的3種方式,通過優(yōu)缺點分析,詳細闡述了puppeteer的實現(xiàn)方式。并通過百度頁面的pdf導出做了演示。

          希望本文對有類似報表pdf導出需求的小伙伴有所參考和幫助。若存在不足或更好方案,請留言討論。

          日客戶要求表內的數據依據某種分組生成HTML頁面進行展示,一般處理這種需求直接上編程工具就好了,從數據庫里讀取數據,根據規(guī)則生成字符串,最后將字符串寫出到文件。由于需求比較急,作為數據庫編程系列文章,如果能用SQL實現(xiàn)首選還是SQL,這樣處理既直接又快速,不過針對SQL要真的有耐心和信心寫完,調試更是崩潰。由于要寫出文件到硬盤,最后還是選擇MySQL作為數據庫工具,Navicat作為開發(fā)工具。

          有兩張表計劃表、市縣表,二者依靠市縣編碼(sxbm)進行等值連接,計劃表內含有各個學校投放在各個市縣的專業(yè)代號(zydh),專業(yè)名稱(zymc)、招生備注(bz)、學制(xz)、要求的學歷(xl)、計劃數(jh)等字段組成的計劃信息,院校編碼(yxbm)為學校的兩位數編碼,院校代號(yxdh)為院校編碼(yxbm)+市縣編碼(sxbm)組成的四位數編碼,院校代號其實可以區(qū)分出學校在哪個市縣的投檔的專業(yè)計劃。要求以學校為單位創(chuàng)建HTML頁面,頁面首先要以市縣作為表格分割,然后根據專業(yè)代號排序。具體實現(xiàn)過程如下:

          創(chuàng)建計劃表:

          CREATE TABLE `zzjh2019v` (
          `YXDH` varchar(9) COMMENT '學校代號',
          `YXMC` varchar(54) COMMENT '學校名稱',
          `ZYDH` varchar(2) COMMENT '專業(yè)代號',
          `ZYMC` varchar(28) COMMENT '專業(yè)名稱',
          `XZ` varchar(3) COMMENT '學制',
          `XL` varchar(4) COMMENT '學歷',
          `JH` varchar(6) COMMENT '招生計劃數',
          `BZ` varchar(200) COMMENT '備注',
          `yxbm` char(2) COMMENT '學校編碼',
          `sxbm` char(2) COMMENT '市縣編碼'
          ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
          

          創(chuàng)建市縣編碼表:

          CREATE TABLE `sx` (
          `sxbm` char(2) COMMENT '市縣編碼',
          `sxmc` varchar(20) COMMENT '市縣名稱'
          ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
          

          糾結了很久這個東西怎么寫,最后采取游標、拼接字符串、字符串聚合,動態(tài)SQL,寫文件等一些列操作完成需求,創(chuàng)建的存儲過程如下:

          CREATE DEFINER=`root`@`localhost` PROCEDURE `splitjh`()
          BEGIN
          declare done INT DEFAULT 0;
          declare pyxbm char(2);
          declare psxmc varchar(10);
          declare pyxmc varchar(50);
          declare pjhall int;
          declare pjhrows TEXT;
          declare yxjh cursor
          for
          select yxbm,yxmc,sum(jh) jhall from zzjh2019v a,sx b where a.sxbm=b.sxbm group by yxbm,yxmc order by yxbm;
          declare CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
          open yxjh;
          fetch yxjh into pyxbm,pyxmc,pjhall;
          while done !=1 do
          select group_concat(jhrow separator '') into pjhrows from
          (select concat('<tr class="subtitle"><td>',yxdh,'</td><td>',yxmc,'在 <span><font color="red">',b.sxmc,'</font></span> 招生計劃如下</td><td>',sum(jh),'</td><td></td><td></td></tr>',group_concat('<tr class="jhrow"><td>',zydh,'</td><td>',zymc,'(',bz,')</td><td>',jh,'</td><td>',xz,'</td><td>',xl,'</td></tr>' order by zydh separator '')) jhrow
          from zzjh2019v a,sx b where yxbm=pyxbm and a.sxbm=b.sxbm group by yxdh order by yxdh,zydh) jhs;
          set @pfilename = concat('''d:/32/1/1/jh11',pyxbm,'.html''');
          set @sql =concat('select concat(''<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" type="text/css" href="zsjh.css" ><title>3+2計劃</title></head><body><h3></h3><table><tr class="subtitle"><th>代號</th><th>專業(yè)及名稱備注</th><th>人數</th><th>學制</th><th>學歷</th></tr>'',''',pjhrows,''',''</body></html>'') from dual into outfile ',@pfilename);
          prepare execsql from @sql;
          execute execsql;
          DEALLOCATE PREPARE execsql;
          fetch yxjh into pyxbm,pyxmc,pjhall;
          end while;
          close yxjh;
          END;
          

          首先看效果,執(zhí)行過程

          call splitjh();
          

          在磁盤形成的HTML文件效果如下圖(數據有一定的敏感性,進行了遮擋處理):

          文件展示頁面

          生成的文件列表如下圖:

          生成的文件列表

          這里一共有87所學校,所以生成了87的文件,添加CSS樣式文件,讓表格呈現(xiàn)如前圖所示。

          技術點

          1)MySQL的游標,以及循環(huán)讀取游標的方法,涉及的語句如下:

          declare yxjh cursor
          for
          select yxbm,yxmc,sum(jh) jhall from zzjh2019v a,sx b where a.sxbm=b.sxbm group by yxbm,yxmc order by yxbm;#游標定義
          declare CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;#游標循環(huán)條件,注意此句一定要定義在游標之后,才起作用
          open yxjh;#打開游標
          fetch yxjh into pyxbm,pyxmc,pjhall;#將游標行內容賦值給變量。
          

          2)執(zhí)行動態(tài)SQL,由于MySQL into outfile 后接的文件名不能為變量,所以必須使用動態(tài)SQL的方法,涉及的語句如下:

          prepare execsql from @sql;#從一個變量準備一個動態(tài)sql,注意execsql不用提前定義
          execute execsql;#執(zhí)行準備好的語句
          DEALLOCATE PREPARE execsql;#銷毀語句
          

          綜上就是使用MySQL數據庫,并借用MySQL寫文件的方式將數據從數據庫內按照需求導出文件,為何不用navicat導出呢?因為無法達到要求,又是聚合、又是格式,所以只能自己編寫過程通過SQL語句拼接字符串的方式來實現(xiàn)。沒有太多的技術難度,主要是想法和調試難度。后續(xù)在此基礎上又開發(fā)了以市縣為單位創(chuàng)建HTML文件,各招生學校作為分割的過程。本案例是實際需求催生出來的做法,在遇到這樣的需求前你是先想到SQL還是先想到開發(fā)工具呢?從實際效果看使用SQL這種方式更加靈活。這樣的SQL實現(xiàn)的字符串拼接是不是有點極限呢?

          導出當前HTML頁面,可以按照以下步驟操作:

          1、打開瀏覽器:首先,在你的電腦上打開一個支持開發(fā)者工具的瀏覽器(如Chrome、Firefox或Edge)。

          2、打開開發(fā)者工具:通過按下`F12`鍵或在瀏覽器的地址欄輸入`chrome://inspect/#devices`(對于Chrome)、`about:debugging`(對于Firefox)或者右鍵點擊頁面上的任何元素并選擇“檢查”(對于所有瀏覽器),以打開開發(fā)者工具。

          3、定位到元素面板:在開發(fā)者工具的頂部菜單中,找到并點擊“Elements”(在Chrome和Edge中)或“Inspector”(在Firefox中),這將打開元素面板,顯示當前頁面的HTML結構。

          4、選擇要導出的HTML:在元素面板中,你可以看到頁面的HTML代碼。你可以通過點擊左上角的箭頭圖標選擇頁面上的元素,對應的HTML代碼將在元素面板中高亮顯示。你也可以在元素面板中編輯HTML代碼。

          5、導出HTML代碼:一旦確定要導出的HTML部分,可以使用以下幾種方法之一來導出:

          在元素面板中,右鍵點擊選擇的HTML代碼,然后選擇“Edit as HTML”或類似選項,這將打開一個新的編輯器窗口,其中包含所選HTML的完整代碼。接下來,你可以通過復制這段代碼并粘貼到文件中來保存。

          使用快捷鍵`Ctrl+C`或`Cmd+C`來復制選定的HTML代碼。

          6、保存HTML代碼:最后,將復制的HTML代碼粘貼到合適的位置,以便將其保存在本地文件系統(tǒng)中。

          以上步驟綜合了不同開發(fā)環(huán)境下的開發(fā)者工具的使用方式,無論是在Windows、Mac還是Linux操作系統(tǒng)下,都可以根據自己常用的開發(fā)工具來進行相應的操作。


          主站蜘蛛池模板: 中文乱码人妻系列一区二区| 亚洲av乱码一区二区三区| 成人免费一区二区三区在线观看| 亚洲欧美日韩国产精品一区| 亚洲日韩AV无码一区二区三区人| 伊人久久一区二区三区无码| 国产成人一区二区精品非洲| 精品无码综合一区| 亚洲一区二区成人| 亚洲熟女综合色一区二区三区| 国产日韩精品一区二区在线观看播放| 亚洲AⅤ无码一区二区三区在线| 中文字幕aⅴ人妻一区二区| 精品视频一区二区三区四区| 一区二区免费国产在线观看| 无码国产精品一区二区免费vr| 亚洲国产成人久久综合一区 | 精品国产亚洲一区二区三区在线观看| 丰满人妻一区二区三区视频| 精品一区二区三区免费| 精品无码国产一区二区三区51安 | 日韩免费无码视频一区二区三区| 精品少妇ay一区二区三区| 国产人妖视频一区在线观看| 国产自产V一区二区三区C| 激情久久av一区av二区av三区| 国精产品一区一区三区免费视频 | 免费高清av一区二区三区| 成人日韩熟女高清视频一区| 中文字幕av人妻少妇一区二区 | 亚洲日本精品一区二区| 日本一区中文字幕日本一二三区视频| 成人免费区一区二区三区| 亚洲高清毛片一区二区| 精品福利一区二区三| 国产伦精品一区三区视频| 99精品国产一区二区三区2021 | 亚洲欧美日韩中文字幕在线一区 | 国产微拍精品一区二区| 看电影来5566一区.二区| 成人在线视频一区|