前言
企业我的项目进行数据埋点后,埋点事件名须要整顿成 Excel 表格便于统计,指标是将下图左侧数据转化成下图右侧的 Excel 表格:
思考到左侧埋点数据是随我的项目迭代减少的,埋点数据每减少一次我就要把数据一条一条的 Ctrl+C/ V 复制粘贴至 Excel 表格内。
懒,不想这样玩,于是我写了一个主动帮我整顿成表格的脚本。
脚本实现
实现流程
- Node.js 生成 Excel 表格工具库技术选型
- 独自复制一份埋点数据进去,保障它的变动不会影响业务相干埋点逻辑
- 整顿埋点数据成咱们须要的数据结构
分成三步走
技术选型
Node.js 操作 Excel 表格工具库有:
- exceljs
- excellentexport
- node-xlsx
- xlsx-template
仅列举以上四个。
抉择的角度有以下几点:
- 学习成本低,文档 API 简略易用,仅生成表格即可,其余性能并不需要,所以 API 越简略越好
- 生成 Excel 表格须要提供的数据结构简略,便于实现
- 能导出 xlsx 表格,满足最根本要求
node-xlsx 最贴近以上要求,首选应用它。
node-xlsx 官网生成 Excel 表格给出的代码块:
import xlsx from 'node-xlsx';
// Or var xlsx = require('node-xlsx').default;
const data = [[1, 2, 3],
[true, false, null, 'sheetjs'],
['foo', 'bar', new Date('2014-02-19T14:30Z'), '0.3'],
['baz', null, 'qux'],
];
var buffer = xlsx.build([{name: 'mySheetName', data: data}]); // Returns a buffer
生成表格数据 data
是二维数组,对应表格的行列。data.length
为表格行数,data[0].length
为表格的列数;data[0][0]
对应至表格的第一行第一列的值,data[0][1]
对应至表格的第一行第二列的值。
所以将埋点数据整顿为一个二维数组即可,二维数组数据结构整顿容易实现。
复制埋点数据
埋点数据对立搁置在buryData.js
文件,但不能随便改变它,所以将该文件独自再复制一份进去。
buryData.js
export default {
version1: 'v1.5.3',
bury1: 'ding 揭示',
bury2: '审批 - 筛选',
bury3: '工作 - 点击工作题目关上工作详情',
bury4: '工作详情弹框 - 点击详情 tab',
bury5: '工作详情弹框 - 点击日志记录 tab',
bury6: '工作详情弹框 - 点击工作总结 tab',
bury7: '工作详情弹框 - 点击动静 tab',
//...
}
buryData.js
复制进去文件命名为 bury.js
,还有一个问题:bury.js
须要执行它,拿到它导出的数据对象,导出数据是应用 ES6 模块化语法,这边须要将 ES6 模块化转化成 CommonJs 模块化,将 export default {}
替换成module.exports ={}
即可做到。
Node.js fs 模块 + 正则替换是能够达成以上目标,但为了更快捷,我抉择应用工具库 magic-string
magic-string 它是操作字符串库,它能够帮我去掉写正则替换字符串的步骤。
const path = require('path');
const magicString = require('magic-string')
const fs = require('fs');
//buryData.js 文件门路
const buryFile = path.join(__dirname, '../src/lib/buryData.js')
const getBuryContent = (filePath) => {const content = fs.readFileSync(filePath, 'utf8')
// 将 export default 替换成 module.exports =
const s = new magicString(content)
s.replace('export default', 'module.exports =')
return s.toString()}
(async () => {const str = getBuryContent(buryFile)
// 将替换后的内容写入至 bury.js 文件
const copyFilePath = path.join(__dirname, '/bury.js')
fs.writeFileSync(copyFilePath, str)
// 动静导入 bury.js 获取埋点数据
const {default: data} = await import(copyFilePath)
})()
生成二维数组
上文已提及,node-xlsx 生成表格须要先将数据整顿成二维数组。
export default {
version1: 'v1.5.3',
bury1: 'ding 揭示',
/...
version2: 'v1.5.4',
bury21: '通讯录人员列表',
//..
}
以上数据整顿成:
[['v1.5.3','v1.5.4'],
['ding 揭示','通讯录人员列表'],
//...
]
首先,将数据全副寄存至一个 Map
对象中。因为埋点数据是一个对象,其中 version1、version2
示意版本号,随我的项目迭代版本号会增多 version3、version4……以version
进行划分Map
值。
const _ = require('lodash');
//...
const getFormatDataMap = (data) => {
let version
const map = new Map();
_.forIn(data, (value, key) => {if (key.includes('version')) {
version = value
!map.has(version) && map.set(version, [value])
return
}
const mapValue = map.get(version)
mapValue.push(value)
})
return map
}
(async () => {const str = getBuryContent(buryFile)
const copyFilePath = path.join(__dirname, '/bury.js')
fs.writeFileSync(copyFilePath, str)
const {default: data} = await import(copyFilePath)
// 新增
const map = getFormatDataMap(data)
})()
getFormatDataMap
函数执行后,返回的数据是:
{'v1.5.3'=>['v1.5.3','ding 揭示' //...]
'v1.5.4'=>['v1.5.4','通讯录人员列表' //...]
}
而后,须要晓得表格最大行数,表格列数即为 map.size()
,最大行数通过获取Map.values()
获取所有的值 values
,遍历values
获取 values
内寄存的每一个数组的长度,长度对立用另一个数组 lens
长期记录,遍历完结后比拟 lens
中的数值失去最大的值,
MAX_LEN
即为表格最大的行数,也是 values
寄存的所有数组中长度最大的值。
const _ = require('lodash');
//...
const getMergeArr = (map) => {const values = _.toArray(map.values())
const lens = []
// 获取长度,长度值对立寄存至 lens 数组中
values.forEach((value) => {lens.push(value.length) })
// 比拟
const MAX_LEN = _.max(lens)
return getTargetItems({mapValue: values, forNum: MAX_LEN})
}
(async () => {const str = getBuryContent(buryFile)
const copyFilePath = path.join(__dirname, '/bury.js')
fs.writeFileSync(copyFilePath, str)
const {default: data} = await import(copyFilePath)
const map = getFormatDataMap(data)
// 新增
const table = getMergeArr(map)
})()
最初,以 values
、MAX_LEN
进行双循环。表格列数 map.size()
可获取,但为了不便间接mapValue.length
, 两者是相等的。
有了表格列数即可创立二维数组的第二层数组,new Array(len).fill('')
第二层数组长度即为mapValue.length
,创立时数组内的值先对立填充为' '
。
const getTargetItems = ({mapValue, forNum}) => {
const len = mapValue.length
const targetItems = []
mapValue.forEach((v, i) => {for (let index = 0; index < forNum; index++) {const element = v[index];
let targetItem = targetItems[index]
if (!targetItem) {
// 创立数组,值先对立填充为 ' '
targetItem = new Array(len).fill(' ')
}
/**
如果以后 index 大于数组 v 的长度,这时获取值 v[index]为 undefined。为 undefined 的话间接跳过,放弃 targetItem[i]为 ' '
*/
targetItem[i] = element ? element : ' '
targetItems[index] = targetItem
}
})
return targetItems
}
实现二维数组的转化,数据结构为下图:
生成表格
数据已实现,留下的就是写入数据生成表格,间接复制 node-xlsx 演示的代码下来。
//...
(async () => {const str = getBuryContent(buryFile)
const copyFilePath = path.join(__dirname, '/bury.js')
fs.writeFileSync(copyFilePath, str)
const {default: data} = await import(copyFilePath)
const map = getFormatDataMap(data)
const table = getMergeArr(map)
// 写入数据,生成表格,返回 buffer 数据
const buffer = xlsx.build([{name: '埋点', data: table}])
const outPath = path.join(__dirname, '/bury.xlsx')
//bury.js 文件能够删除,bury.xlsx 如果已存在就先删了
fs.existsSync(outPath) && fs.unlinkSync(outPath)
fs.existsSync(copyFilePath) && fs.unlinkSync(copyFilePath)
// 创立一个 bury.xlsx 文件,将失去的 buffer 写入
fs.writeFileSync(outPath, buffer)
})()
脚本完。
残缺源码:
const path = require('path');
const fs = require('fs');
const xlsx = require('node-xlsx');
const magicString = require('magic-string')
const _ = require('lodash');
const buryFile = path.join(__dirname, '../src/lib/buryData.js')
const getBuryContent = (filePath) => {const content = fs.readFileSync(filePath, 'utf8')
const s = new magicString(content)
s.replace('export default', 'module.exports =')
return s.toString()}
const getFormatDataMap = (data) => {
let version
const map = new Map();
_.forIn(data, (value, key) => {if (key.includes('version')) {
version = value
!map.has(version) && map.set(version, [value])
return
}
const mapValue = map.get(version)
mapValue.push(value)
})
return map
}
const getTargetItems = ({mapValue, forNum}) => {
const len = mapValue.length
const targetItems = []
mapValue.forEach((v, i) => {for (let index = 0; index < forNum; index++) {const element = v[index];
let targetItem = targetItems[index]
if (!targetItem) {targetItem = new Array(len).fill(' ')
}
targetItem[i] = element ? element : ' '
targetItems[index] = targetItem
}
})
return targetItems
}
const getMergeArr = (map) => {const values = _.toArray(map.values())
const lens = []
values.forEach((value) => {lens.push(value.length) })
const MAX_LEN = _.max(lens)
return getTargetItems({mapValue: values, forNum: MAX_LEN})
}
(async () => {const str = getBuryContent(buryFile)
const copyFilePath = path.join(__dirname, '/bury.js')
fs.writeFileSync(copyFilePath, str)
const {default: data} = await import(copyFilePath)
const map = getFormatDataMap(data)
const table = getMergeArr(map)
debugger
const buffer = xlsx.build([{name: '埋点', data: table}])
const outPath = path.join(__dirname, '/bury.xlsx')
fs.existsSync(outPath) && fs.unlinkSync(outPath)
fs.existsSync(copyFilePath) && fs.unlinkSync(copyFilePath)
fs.writeFileSync(outPath, buffer)
})()
去掉空行,一百行以内。
总结
Node.js 可应用的场景非赏多,不单单是用于服务器接口的开发,咱们还能通过写脚本的模式解决生存中重复性的工作,凭藉 js 的语法简略及弱小的生态,前端不用学习 shell、python 等,仅应用 js 就能够搞定爬虫、自动化脚本等场景。
如果我的文章对你有帮忙,你的👍就是对我的最大反对 ^_^。