将数据表导出为 Excel 文件

知晓云 - 小晓云发表于:2019年05月24日 18:39:19更新于:2019年05月24日 18:44:19

在日常的工作中,常常需要根据运营需求对数据进行各种格式的处理和导出。导出后,不少人偏爱将数据放入 excel在进行处理。

一般来说,处理数据导出时需要对数据进行一些运算整理。在以前,处理的方式是在一台独立的服务器上跑脚本。

而现在有了知晓云,不再需要维护服务器,直接写代码就能把相关事都都丢给云函数。 本文将介绍通过知晓云云函数来实现将数据表导出为 excel 文件的功能,并使用 webpack 和 mincloud 将代码打包上传到知晓云。


技术栈:

  • 打包工具: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)     


1h3ecAC5pSrDCHHy.png


知晓云的云函数调用有同步和异步两种方式,同步调用的最大超时时间为 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'    // 文件上传分类 idconst 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 一样,部署前需要先登录,请参照文档配置。

使用以下命令即可将云函数部署到知晓云:

 run deploy


执行结果如下:

1h3ecqjEkIkEX7kl.png

使用以下的命令来测试:

mincloud invoke export-excel-filebr


执行结果如下:

1h3edRP6FahYQwrg.png


export_task 表记录:

1h3edrRq2bySgKcH.png


上传到知晓云的 excel 文件如下:

1h3eeGNECR14zCdk.png


文件内容:

1h3eeqo2uGsyRwLs.png

           

四、参考文档

知晓云开发文档:https://doc.minapp.com/

node-xlsx 文档:https://www.npmjs.com/package/node-xlsx


五、源码

仓库地址:https://github.com/ifanrx/export-excel-file


知晓云-小程序开发快人一步
    您需要登录后才可以回复