【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

知曉雲發表於2019-03-08

在日常的工作中,常常需要根據運營需求對資料進行各種格式的處理和匯出。匯出後,不少人偏愛將資料放入 excel 在進行處理。

一般來說,處理資料匯出時需要對資料進行一些運算整理。在以前,處理的方式是在一臺獨立的伺服器上跑指令碼。

而現在有了知曉雲,不再需要維護伺服器,直接寫程式碼就能把相關事都都丟給雲函式。 本文將介紹通過知曉云云函式來實現將資料表匯出為 excel 檔案的功能,並使用 webpackmincloud 將程式碼打包上傳到知曉雲。

技術棧:

  • 打包工具:webpack@4.22.0
  • 部署工具:mincloud@1.0.4
  • Excel 處理:node-xlsx@0.14.1
  • 其他:知曉雲 SDK

一、專案搭建

專案檔案結構:

  export-excel-file
  ├── index.js
  ├── package.json
  ├── src
  │   └── index.js
  ├── webpack.config.js
  └── yarn.lock
複製程式碼

專案搭建與雲函式程式碼打包示例文件基本一致。專案搭建好後,還需要安裝以下依賴(兩種安裝方式選其一即可):

// 使用 yarn 安裝
yarn add node-xlsx mincloud
// 使用 npm 安裝
npm install --save node-xlsx minclou
複製程式碼

修改 deploy 指令碼,如下:

// package.json
...
"scripts": {
   "build": "webpack --mode production",
   "predeploy": "npm run build""deploy": "mincloud deploy export-excel-file ../"
},
...
複製程式碼

最終我們會使用以下兩個命令來部署和測試:

npm run deploy  // 部署到知曉雲
mincloud invoke export-excel-file  // 測試已經部署到知曉雲上的雲函式
複製程式碼

二、將資料表匯出為 excel 檔案

我們需要準備兩張表: order: 訂單表 (新建欄位:name, price) export_task:匯出任務記錄表 (新建欄位:file_download_link)

【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

知曉雲的雲函式呼叫有同步和非同步兩種方式,同步呼叫的最大超時時間為 5 s,非同步呼叫的則為 300 s。

假定 order 訂單表有十萬條資料,由於知曉雲單次拉取資料的最大限制為 1000 條,所以需要分批獲取資料,加上後續可能需要對資料進行處理,所花費的時間將會超過 5 s,因此對該雲函式的呼叫將採用非同步的方式。這時候就需要 export_task 匯出任務記錄表來對匯出任務進行管理了。

export_task 表對匯出任務進行管理的流程如下:

  1. 呼叫雲函式時在 export_task 表中建立一條記錄 A,此時記錄 A 中的 file_download_link 欄位值為空,同時拿到記錄 A 的 id,記這個 id 為 jobId
  2. 進行 order 表資料查詢,excel 檔案生成,檔案上傳等操作,拿到檔案下載連結
  3. 之後根據 jobId 來更新第一步建立的記錄,儲存檔案下載連結到 file_download_link 欄位中
  4. 更新完後就能在 export_task 表中拿到檔案下載連結

通過上面的準備和分析,對匯出 excel 檔案操作分為以下 4 個步驟:

  1. order 訂單表資料獲取
  2. 使用獲取的資料在雲函式環境下建立 excel 檔案
  3. 將建立出的 excel 檔案上傳到知曉雲
  4. 儲存檔案下載連結到 export_task 表中的 file_download_link 欄位

完整程式碼如下:

const fs = require('fs')
const xlsx = require('node-xlsx')


const EXPORT_DATA_CATEGORY_ID = '5c711e3119111409cdabe6f2'    // 檔案上傳分類 id
const TABLE_ID = {
  order: 66666,         // 訂單表
  export_task: 66667,   // 匯出任務記錄表
}


const TMP_FILE_NAME = '/tmp/result.xlsx'  // 本地臨時檔案路徑,以 /tmp 開頭,具體請檢視:https://doc.minapp.com/support/technical-notes.html (雲函式的臨時檔案儲存)
const ROW_NAME = ['name', 'price']        // Excel 檔案列名配置
const MAX_CONNECT_LIMIT = 5               // 最大同時請求數
const LIMIT = 1000                        // 單次最大拉取資料數
let result = []


/**
 * 更新匯出記錄中的 file_download_link 欄位
 * @param {*} tableID 
 * @param {*} recordId 
 * @param {*} fileLink 
 */
function updateExportJobIdRecord(tableID, recordId, fileLink) {
  let Schame = new BaaS.TableObject(tableID)
  let schame = Schame.getWithoutData(recordId)


  schame.set('file_download_link', fileLink)
  return schame.update()
}


/**
 * 建立資料匯出任務
 * 設定初始 file_download_link 為空
 * 待匯出任務執行完畢後將檔案下載地址儲存到 file_download_link 欄位中
 * @param {*} tableID 
 */
function createExportJobIdRecord(tableID) {
  let Schame = new BaaS.TableObject(tableID)
  let schame = Schame.create()
  return schame.set({file_download_link: ''}).save().then(res => {
    return res.data.id
  })
}


/**
 * 獲取總資料條數
 * @tableId {*} tableId
 */
function getTotalCount(tableId) {
  const Order = new BaaS.TableObject(tableId)
  return Order.count()
    .then(num => {
      console.log('資料總條數:', num)
      return num
    })
    .catch(err => {
      console.log('獲取資料總條數失敗:', err)
      throw new Error(err)
    })
}


/**
 * 分批拉取資料
 * @param {*} tableId 
 * @param {*} offset 
 * @param {*} limit 
 */
function getDataByGroup(tableId, offset = 0, limit = LIMIT) {
  let Order = new BaaS.TableObject(tableId)
  return Order.limit(limit).offset(offset).find()
    .then(res => {
      return res.data.objects
    })
    .catch(err => {
      console.log('獲取分組資料失敗:', err)
      throw new Error(err)
    })
}


/**
 * 建立 Excel 匯出檔案
 * @param {*} sourceData 源資料
 */
function genExportFile(sourceData = []) {
  const resultArr = []
  const rowArr = []


  // 配置列名
  rowArr.push(ROW_NAME)


  sourceData.forEach(v => {
    rowArr.push(
      ROW_NAME.map(k => v[k])
    )
  })


  resultArr[0] = {
    data: rowArr,
    name: 'sheet1',    // Excel 工作表名
  }


  const option = {'!cols': [{wch: 10}, {wch: 20}]}    // 自定義列寬度
  const buffer = xlsx.build(resultArr, option)
  return fs.writeFile(TMP_FILE_NAME, buffer, err => {
    if (err) {
      console.log('建立 Excel 匯出檔案失敗')
      throw new Error(err)
    }
  })
}


/**
 * 上傳檔案
 */
function uploadFile() {
  let MyFile = new BaaS.File()
  return MyFile.upload(TMP_FILE_NAME, {category_id: EXPORT_DATA_CATEGORY_ID})
    .catch(err => {
      console.log('上傳檔案失敗')
      throw new Error(err)
    })
}


module.exports = async function(event, callback) {
  try {
    const date = new Date().getTime()
    const groupInfoArr = []
    const groupInfoSplitArr = []
    const [jobId, totalCount] = await Promise.all([createExportJobIdRecord(TABLE_ID.export_task), getTotalCount(TABLE_ID.order)])
    const groupSize = Math.ceil(totalCount / LIMIT) || 1


    for (let i = 0; i < groupSize; i++) {
      groupInfoArr.push({
        offset: i * LIMIT,
        limit: LIMIT,
      })
    }


    console.log('groupInfoArr:', groupInfoArr)


    const length = Math.ceil(groupInfoArr.length / MAX_CONNECT_LIMIT)


    for (let i = 0; i < length; i++) {
      groupInfoSplitArr.push(groupInfoArr.splice(0, MAX_CONNECT_LIMIT))
    }


    console.log('groupInfoSplitArr:', groupInfoSplitArr)


    const date0 = new Date().getTime()
    console.log('處理分組情況耗時:', date0 - date, 'ms')


    let num = 0


    // 分批獲取資料
    const getSplitDataList = index => {
      return Promise.all(
        groupInfoSplitArr[index].map(v => {
          return getDataByGroup(TABLE_ID.order, v.offset, v.limit)
        })
      ).then(res => {
        ++num
        result.push(...Array.prototype.concat(...res))
        if (num < groupInfoSplitArr.length) {
          return getSplitDataList(num)
        } else {
          return result
        }
      })
    }


    Promise.all([getSplitDataList(num)]).then(res => {
      const date1 = new Date().getTime()
      console.log('結果條數:', result.length)
      console.log('分組拉取資料次數:', num)
      console.log('拉取資料耗時:', date1 - date0, 'ms')


      genExportFile(result)


      const date2 = new Date().getTime()
      console.log('處理資料耗時:', date2 - date1, 'ms')


      uploadFile().then(res => {
        const fileLink = res.data.file_link
        const date3 = new Date().getTime()
        console.log('上傳檔案耗時:', date3 - date2, 'ms')
        console.log('總耗時:', date3 - date, 'ms')


        updateExportJobIdRecord(TABLE_ID.export_task, jobId, fileLink)
          .then(() => {
            const date4 = new Date().getTime()
            console.log('儲存檔案下載地址耗時:', date4 - date3, 'ms')
            console.log('總耗時:', date4 - date, 'ms')


            callback(null, {
              message: '儲存檔案下載地址成功',
              fileLink,
            })
          })
          .catch(err => {
            callback(err)
          })
      }).catch(err => {
        console.log('上傳檔案失敗:', err)
        throw new Error(err)
      })
    })
  } catch (err)
複製程式碼

三、部署並測試

npm 一樣,部署前需要先登入,請參照文件配置。

使用以下命令即可將雲函式部署到知曉雲:

npm run deploy
複製程式碼

執行結果如下:

【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

使用以下的命令來測試:

mincloud invoke export-excel-file
複製程式碼

執行結果如下:

【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

export_task 表記錄:

【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

上傳到知曉雲的 excel 檔案如下:

【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

檔案內容:

【實戰教程】使用雲函式將資料表匯出為 Excel 檔案

四、參考文件

知曉雲開發文件:https://doc.minapp.com/ node-xlsx 文件:https://www.npmjs.com/package/node-xlsx

五、原始碼

倉庫地址:https://github.com/ifanrx/export-excel-file

六、福利

即日( 3 月 8 日)起,前 50 名報名並通過稽核的 Web 端公測的使用者,在正式接入後即獲賬戶禮金 100 元。?

報名點這裡 ? 知曉雲公測活動 ? 除了這個好訊息,小云還要告訴大家,公測活動結束後還將挑選出 5 名積極反饋的使用者獲得知曉雲限量紀念 T 恤呀

(。・ω・。)ノ♡


本文首發於「知曉雲」公眾號:https://mp.weixin.qq.com/s/g67uTqnWTIsGiqtKdl-W4w

知曉雲是國內首家專注於小程式開發的後端雲服務。使用知曉雲,小程式開發快人一步。

相關文章