前言
企業專案進行資料埋點後,埋點事件名需要整理成Excel表格便於統計,目標是將下圖左側資料轉化成下圖右側的Excel表格:
考慮到左側埋點資料是隨專案迭代增加的,埋點資料每增加一次我就要把資料一條一條的Ctrl+C/V複製貼上至Excel表格內。
懶,不想這樣玩,於是我寫了一個自動幫我整理成表格的指令碼。
指令碼實現
實現流程
- Node.js生成Excel表格工具庫技術選型
- 單獨複製一份埋點資料出來,保證它的變動不會影響業務相關埋點邏輯
- 整理埋點資料成我們需要的資料結構
分成三步走
技術選型
Node.js操作Excel表格工具庫有:
僅羅列以上四個。
選擇的角度有以下幾點:
- 學習成本低,文件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就可以搞定爬蟲、自動化指令碼等場景。
如果我的文章對你有幫助,你的?就是對我的最大支援^_^。