1. 程式人生 > >分針網——每日分享:nodejs導出excel實戰

分針網——每日分享:nodejs導出excel實戰

use 內容 bject 順序 type 可用 類型 完全 date

本文轉載:http://www.f-z.cn/id/268 我們都知道nodejs的內存由於v8內存分配機制的原因十分有限 技術分享 64位系統也只能占1.4G左右, 因此當我們要生成或者讀取大文件的時候內存的吃緊會給我們造成極大的困擾, 遇到這樣的情況Node給了我們一個很好的解決方法 stream 簡單的了解一下流 流是數據的集合 —— 就像數組或字符串一樣。區別在於流中的數據可能不會立刻就全部可用,並且你無需一次性地把這些數據全部放入內存。這使得流在操作大量數據或是數據從外部來源逐段發送過來的時候變得非常有用 管道與流的結合在Linux中運用得非常多, 也使得linux能夠通過 pipe 組合多個命令實現復雜的功能, 其實通俗一點來說, stream 就是把我們需要一口氣吃下的東西分成多次按量的吃下去, 避免一口氣吃撐, 在吃的過程中, 我們甚至可以邊 ”吃“ 邊 ”排“(transform), 使我們的身體能夠保持一個均衡低負荷的狀態, 同時, 也就是保證node進程內存不會太吃緊的條件.
nodejs中的流 因為先天的原因, 流在node中通常被我們用來處理大文件, 甚至說在nodejs的各類模塊中均采用了 stream 在server中的 http request 是可讀流(Readable Stream), http response是一個可寫流(Writable Stream), 這也就是為什麽我們通常在request中讀取client傳回的值, 而通過response寫入數據返回 fs則是一個可讀可寫的流(Duplex Stream), 我們可以對一個文件進行寫入和讀取的操作以上這些 模塊只是nodejs中流應用的一小部分場景, 在其官方API中流的類型和解釋則是
Readable - streams from which data can be read (for example fs.createReadStream()). Writable - streams to which data can be written (for example fs.createWriteStream()). Duplex - streams that are both Readable and Writable (for example net.Socket). Transform - Duplex streams that can modify or transform the data as it is written and read (for example zlib.createDeflate()).
沒錯也就是四種類型, 其中transform繼承自duplex 流的典型例子 在nodejs中讀取和寫入大文件通常是流應用得最廣泛、最重要的場景之一, 這也是寫這篇博客的原因之一, 因此以一個簡單的文件讀取的例子作為我們了解流的小Demo 讀取一個大小為160M的文件, 采用fs.readFile()方法 const fs = require(‘fs‘) const server = require(‘http‘).createServer() server.on(‘request‘, (req, res) => { fs.readFile(‘./Demo.txt‘, (err, result)=>{ if(err){ throw err } res.end(result) }) }) server.listen(3000) 啟動這樣一個node進程, 內存占用約為8m, 當我們執行請求 curl localhost:3000時, 會發現內存暴增到160m, 差不多是我們讀取的文件的大小 技術分享 技術分享 采用流讀取的方式 const fs = require(‘fs‘) const server = require(‘http‘).createServer() server.on(‘request‘, (req, res) => { let data = fs.createReadStream(‘./Demo.txt‘) data.pipe(res) }) server.listen(3000) 可以看見內存基本穩定在11m, 這證明了采用讀取流方式在內存上給我們帶來了極大的優化, 當然這只是一個小Demo, 我們可以嘗試著去讀取1G 甚至超過2G的文件, fs.readFile()的方式可能就會突破內存的限制而導致進程crash掉, 假如在生產環境中, 請求多並發相對較高的環境下, 這種方式是行不通的 通過流的方式導出Excel文件 背景 需求是希望能夠將項目下的所有分組以一個項目excel文件包含多個分組sheet導出 如果是導出csv文件, 我們完全可以通過流的方式導出, 但是在excel中, 由於文件類型的限制, 我們很難把excel通過流的方式直接導出, 最終選擇了exceljs 編碼格式 Node.js支持 ascii 、utf8、base64、binary 編碼方式,不支持 utf-8 + BOM(字節順序標記) 格式, 而微軟給utf8加了BOM頭(在windows下不管是utf8還是utf16(Unicode)都有BOM, utf16自帶BOM頭), 因此excel會出現中文亂碼, 因此我們需要在文件頭加上三個標識字節, 由於utf8對應的BOM是 EF BB BF 傳送門, 因此這麽實現: res.write(Buffer.from(‘\xEF\xBB\xBF‘, ‘binary‘)) 既然是導出excel文件, 那文件內容(‘Content-Type’)是什麽, 最終在StackOverflow上找到了答案, 傳送門StackOverflow, 文件類型有專屬的Office套件: ‘Content-Type‘: ‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet‘ 實現 使用exceljs最重要的原因是支持偽流 The interface to the streaming workbook and worksheet is almost the same as the document versions with a few minor practical differences: Once a worksheet is added to a workbook, it cannot be removed. Once a row is committed, it is no longer accessible since it will have been dropped from the worksheet. unMergeCells() is not supported. 從文檔的說明來看, exceljs是支持將每一個sheet寫入excel document中, 然後立馬pipe出去, 這是相對可行的一個方案, 但是當每個sheet數據量過大怎麽辦? 同樣會導致我們內存的暴增, 導致進程的crash, 而源碼也確實是這樣實現了exceljs Stream, 所以這是一個偽流, 它並沒有解決我們的根本問題, 但是很大程度的舒緩了我們遇到的問題, 問題的根本原因還是在excel中我們需要區別每一個sheet, 導致我們不能持續的進行讀寫, 還是需要先把這部分的數據先讀取到內存中進行寫入。 這也導致我們在業務代碼中需要添加一些數據量上的限制針對每一個sheet 部分代碼, Demo.coffee res.set(‘Content-Type‘,‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet‘) res.setHeader(‘Content-Disposition‘, filename) res.set(‘Set-Cookie‘, ‘fileDownload=true; path=/‘) excel = require(‘exceljs‘) options = { stream: res, useStyles: true, useSharedStrings: true } workbook = new excel.stream.xlsx.WorkbookWriter(options) worksheet = workbook.addWorksheet(tasklist.title) headers = [ { header: ‘csv.content‘, width: 15 } { header: ‘csv.ancestor‘, width: 10 } { header: ‘csv.note‘, width: 20 } { header: ‘csv.priority‘, width: 10 } { header: ‘csv.executor‘, width: 10 } { header: ‘csv.startDate‘, width: 20 } { header: ‘csv.dueDate‘, width: 20 } { header: ‘csv.creator‘, width: 20 } { header: ‘csv.created‘, width: 20 } { header: ‘csv.isDone‘, width: 10 } { header: ‘csv.accomplished‘, width: 20 } { header: ‘csv.tasklist‘, width: 10 } { header: ‘csv.stage‘, width: 10 } { header: ‘csv.delayDays‘, width: 10 } { header: ‘csv.delayed‘, width: 10 } { header: ‘csv.totaltime‘, width: 10 } { header: ‘csv.usedtime‘, width: 10 } { header: ‘csv.tag‘, width: 10 } ] // 生成標題頭 worksheet.columns = headers rows = [ [5,‘Bob‘,new Date()], // row by array {id:6, name: ‘Barbara‘, dob: new Date()} ] worksheet.addRows rows worksheet.commit() workbook.commit() 總結 導出excel僅僅是stream一個小小的體現和應用, 真正要掌握它的整個實現背景和場景應用還是需要我們去實踐以及查看源碼的實現, 這裏僅僅是一個讀、寫的單向實現, 可以去嘗試transform從一個文件讀取數據進行相關處理之後寫入一個新的文件, 這樣更能感受它給我們帶來的性能上優化和簡便, 以上

分針網——每日分享:nodejs導出excel實戰