前言
企業(yè)項(xiàng)目進(jìn)行數(shù)據(jù)埋點(diǎn)后茎匠,埋點(diǎn)事件名需要整理成Excel表格便于統(tǒng)計(jì)究驴,目標(biāo)是將下圖左側(cè)數(shù)據(jù)轉(zhuǎn)化成下圖右側(cè)的Excel表格:
考慮到左側(cè)埋點(diǎn)數(shù)據(jù)是隨項(xiàng)目迭代增加的祭芦,埋點(diǎn)數(shù)據(jù)每增加一次我就要把數(shù)據(jù)一條一條的Ctrl+C/V復(fù)制粘貼至Excel表格內(nèi)失乾。
懶通惫,不想這樣玩翎苫,于是我寫(xiě)了一個(gè)自動(dòng)幫我整理成表格的腳本旺聚。
腳本實(shí)現(xiàn)
實(shí)現(xiàn)流程
- Node.js生成Excel表格工具庫(kù)技術(shù)選型
- 單獨(dú)復(fù)制一份埋點(diǎn)數(shù)據(jù)出來(lái)沈贝,保證它的變動(dòng)不會(huì)影響業(yè)務(wù)相關(guān)埋點(diǎn)邏輯
- 整理埋點(diǎn)數(shù)據(jù)成我們需要的數(shù)據(jù)結(jié)構(gòu)
分成三步走
技術(shù)選型
Node.js操作Excel表格工具庫(kù)有:
僅羅列以上四個(gè)。
選擇的角度有以下幾點(diǎn):
- 學(xué)習(xí)成本低踩晶,文檔API簡(jiǎn)單易用执泰,僅生成表格即可,其他功能并不需要合瓢,所以API越簡(jiǎn)單越好
- 生成Excel表格需要提供的數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單坦胶,便于實(shí)現(xiàn)
- 能導(dǎo)出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
生成表格數(shù)據(jù)data
是二維數(shù)組,對(duì)應(yīng)表格的行列税弃。data.length
為表格行數(shù)纪岁,data[0].length
為表格的列數(shù);data[0][0]
對(duì)應(yīng)至表格的第一行第一列的值则果,data[0][1]
對(duì)應(yīng)至表格的第一行第二列的值幔翰。
所以將埋點(diǎn)數(shù)據(jù)整理為一個(gè)二維數(shù)組即可,二維數(shù)組數(shù)據(jù)結(jié)構(gòu)整理容易實(shí)現(xiàn)西壮。
復(fù)制埋點(diǎn)數(shù)據(jù)
埋點(diǎn)數(shù)據(jù)統(tǒng)一放置在buryData.js
文件遗增,但不能隨意改動(dòng)它,所以將該文件單獨(dú)再?gòu)?fù)制一份出來(lái)款青。
buryData.js
export default {
version1: 'v1.5.3',
bury1: 'ding提醒',
bury2: '審批-篩選',
bury3: '任務(wù)-點(diǎn)擊任務(wù)標(biāo)題打開(kāi)任務(wù)詳情',
bury4: '任務(wù)詳情彈框-點(diǎn)擊詳情tab',
bury5: '任務(wù)詳情彈框-點(diǎn)擊日志記錄tab',
bury6: '任務(wù)詳情彈框-點(diǎn)擊工作總結(jié)tab',
bury7: '任務(wù)詳情彈框-點(diǎn)擊動(dòng)態(tài)tab',
//...
}
buryData.js
復(fù)制出來(lái)文件命名為bury.js
做修,還有一個(gè)問(wèn)題:bury.js
需要執(zhí)行它,拿到它導(dǎo)出的數(shù)據(jù)對(duì)象,導(dǎo)出數(shù)據(jù)是使用ES6模塊化語(yǔ)法饰及,這邊需要將ES6模塊化轉(zhuǎn)化成CommonJs模塊化蔗坯,將export default {}
替換成module.exports ={}
即可做到。
Node.js fs模塊+正則替換是可以達(dá)成以上目的燎含,但為了更快捷宾濒,我選擇使用工具庫(kù)magic-string
magic-string它是操作字符串庫(kù),它可以幫我去掉寫(xiě)正則替換字符串的步驟屏箍。
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)
//將替換后的內(nèi)容寫(xiě)入至bury.js文件
const copyFilePath = path.join(__dirname, '/bury.js')
fs.writeFileSync(copyFilePath, str)
//動(dòng)態(tài)導(dǎo)入bury.js 獲取埋點(diǎn)數(shù)據(jù)
const { default: data } = await import(copyFilePath)
})()
生成二維數(shù)組
上文已提及绘梦,node-xlsx生成表格需要先將數(shù)據(jù)整理成二維數(shù)組。
export default {
version1: 'v1.5.3',
bury1: 'ding提醒',
/...
version2: 'v1.5.4',
bury21: '通訊錄人員列表',
//..
}
以上數(shù)據(jù)整理成:
[
['v1.5.3','v1.5.4'],
['ding提醒','通訊錄人員列表'],
//...
]
首先铣除,將數(shù)據(jù)全部存放至一個(gè)Map
對(duì)象中谚咬。因?yàn)槁顸c(diǎn)數(shù)據(jù)是一個(gè)對(duì)象鹦付,其中version1尚粘、version2
表示版本號(hào),隨項(xiàng)目迭代版本號(hào)會(huì)增多version3敲长、version4……以version
進(jìn)行劃分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
函數(shù)執(zhí)行后,返回的數(shù)據(jù)是:
{
'v1.5.3'=>['v1.5.3','ding提醒' //...]
'v1.5.4'=>['v1.5.4','通訊錄人員列表' //...]
}
然后祈噪,需要知道表格最大行數(shù)泽铛,表格列數(shù)即為
map.size()
,最大行數(shù)通過(guò)獲取Map.values()
獲取所有的值values
辑鲤,遍歷values
獲取values
內(nèi)存放的每一個(gè)數(shù)組的長(zhǎng)度盔腔,長(zhǎng)度統(tǒng)一用另一個(gè)數(shù)組lens
臨時(shí)記錄,遍歷結(jié)束后比較lens
中的數(shù)值得到最大的值月褥,
MAX_LEN
即為表格最大的行數(shù)弛随,也是values
存放的所有數(shù)組中長(zhǎng)度最大的值。
const _ = require('lodash');
//...
const getMergeArr = (map) => {
const values = _.toArray(map.values())
const lens = []
//獲取長(zhǎng)度宁赤,長(zhǎng)度值統(tǒng)一存放至lens數(shù)組中
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
進(jìn)行雙循環(huán)决左。表格列數(shù)map.size()
可獲取愕够,但為了方便直接mapValue.length
,兩者是相等的。
有了表格列數(shù)即可創(chuàng)建二維數(shù)組的第二層數(shù)組佛猛,new Array(len).fill(' ')
第二層數(shù)組長(zhǎng)度即為mapValue.length
惑芭,創(chuàng)建時(shí)數(shù)組內(nèi)的值先統(tǒng)一填充為' '
。
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) {
//創(chuàng)建數(shù)組继找,值先統(tǒng)一填充為' '
targetItem = new Array(len).fill(' ')
}
/**
如果當(dāng)前index大于數(shù)組v的長(zhǎng)度遂跟,這時(shí)獲取值v[index]為undefined。
為undefined的話直接跳過(guò),保持targetItem[i]為' '
*/
targetItem[i] = element ? element : ' '
targetItems[index] = targetItem
}
})
return targetItems
}
完成二維數(shù)組的轉(zhuǎn)化漩勤,數(shù)據(jù)結(jié)構(gòu)為下圖:
生成表格
數(shù)據(jù)已完成感挥,留下的就是寫(xiě)入數(shù)據(jù)生成表格,直接復(fù)制node-xlsx演示的代碼下來(lái)越败。
//...
(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)
//寫(xiě)入數(shù)據(jù)触幼,生成表格,返回buffer數(shù)據(jù)
const buffer = xlsx.build([{ name: '埋點(diǎn)', 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)
//創(chuàng)建一個(gè)bury.xlsx文件置谦,將得到的buffer寫(xiě)入
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: '埋點(diǎn)', 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)
})()
去掉空行亿傅,一百行以內(nèi)媒峡。
總結(jié)
Node.js可使用的場(chǎng)景非賞多,不單單是用于服務(wù)器接口的開(kāi)發(fā)葵擎,我們還能通過(guò)寫(xiě)腳本的形式解決生活中重復(fù)性的工作谅阿,憑藉js的語(yǔ)法簡(jiǎn)單及強(qiáng)大的生態(tài),前端不必學(xué)習(xí)shell酬滤、python等签餐,僅使用js就可以搞定爬蟲(chóng)、自動(dòng)化腳本等場(chǎng)景盯串。
如果我的文章對(duì)你有幫助氯檐,你的??就是對(duì)我的最大支持_。
本文由mdnice多平臺(tái)發(fā)布