一、開發介紹
作者:leo
更新:2019.03.14
專案原始碼:github
1.開發背景
由於公司 V2專案 需要做元件化升級,但因為 V2專案 專案歷史包袱大, 程式碼和檔案非常多,而且巢狀較多,難以全面瞭解所需要調整的元件的影響範圍,所以需要開發這麼一個工具,來實現以下幾個功能:
- 需要能支援 自定義關鍵詞檢索 ,便於按不同的已有元件名搜尋;
- 需要能支援檢索出該元件的 影響檔案範圍 ,還有 頁面名稱路由 等,便於測試按照頁面快速測試;
- 需要能支援 資料視覺化 ,便於判斷所有影響範圍的權重;
- 需要能 匯出影響範圍的路由檔案 和 必要資料 ;
基於上面需求,我大概整理思路使用 Nodejs
和 Python
進行需求開發,原因有這幾點:
- 需求以操作檔案為主,包括讀寫;
- 需求對資料處理操作比較多,包括過濾,組裝資料格式;
- 需求對資料視覺化的需求;
起初我準備只使用 Nodejs
完成這個需求,後面開發到一半,發現 資料視覺化 方面,實在找不到一個滿意的視覺化外掛,於是想到 Python
的一個2D繪相簿—— Matplotlib
,使用起來非常方便,於是便選擇了它。
這也是我用 Nodejs 做的第一個作品,還有很多優化空間,歡迎大佬指點哈,感激不盡。
2.工具文件
二、開發環境搭建
1.Nodejs環境搭建
對於 Nodejs
環境搭建,相信對於我們前端開發仔來說,應該是很簡單,但這裡考慮到可能原生的同學還不太清楚,這裡我簡單介紹:
- 下載和安裝
Nodejs
我們到 Nodejs官網 ,選擇對應系統環境進行下載,然後直接開啟安裝。
- 測試
Nodejs
環境
開啟命令列工具,執行 node -v
,看是是否輸出對應 Nodejs
版本號,我這顯示:
v10.8.0
複製程式碼
另外在 WIN7 系統下可能會出現下面報錯,則需要將 nodejs
安裝目錄,新增全域性路徑:
node : 無法將“node”項識別為 cmdlet、函式、指令碼檔案或可執行程式的名稱。請檢查名稱的拼寫,如果包括路徑,請確保路徑正確,然後再試一次。
複製程式碼
- 安裝完成
2.Python環境搭建
- 下載和安裝
Python
在 Python官網 ,選擇 3.x 版本下載(由於Python2.x版本已經停止維護,並且即將被淘汰),下載完成直接安裝。
- 測試
Python
環境
安裝完成,開啟命令列工具,執行 python
,看看輸出結果是否是版本號和命令列互動模式,我這顯示:
PS C:\Users\mi> python
Python 3.6.3 |Anaconda, Inc.| (default, Oct 15 2017, 03:27:45) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>
複製程式碼
- 安裝繪相簿
Matplotlib
python
安裝其他包是用 pip install packageName
來安裝,跟 Nodejs
中的 npm install packageName
是一樣的,我們就這麼安裝 Matplotlib
:
pip install Matplotlib
複製程式碼
- 安裝完成
三、開發過程
首先先介紹下開發的思路:
1.最終效果
最終我實現的效果是,開發 search_current_file.js
和 search_current_file_python.py
兩個檔案,並通過執行兩個命令,來獲取對應資料檔案:
- 獲取 所有包含關鍵詞的檔案的路徑、所在資料夾內檔案數量和所有檔案對應頁面的路由/引數/標題等資料 統計的檔案和表格。
node search_current_file.js
複製程式碼
- 獲取 所有資料夾中檔案數量佔總檔案數的比例 的餅圖結果。
python search_current_file_python.py
複製程式碼
這裡需要輸入需要生成的指定資料夾的資料,預設不輸入則生成所有資料夾下的資料。
2.Nodejs開發部分
首先定義幾個下面主要使用的變數,其他沒有寫在這裡的變數和作用,可以檢視原始碼。
var Excel = require('exceljs');
var XLSX = require('xlsx');
var filterFile = ['.html']; // 需要檢索的檔案型別
var filterDir = ['lib']; // 需要排除的資料夾
var classArray = [ // 需要檢索的類名陣列
'search-holder','exe-bar-search','輸入搜尋內容','<exe-search','learn-search','ion-android-search'
];
var resultArray = []; // 最終結果
var resultAlassify = {}; // 最終結果分類
var excelFileArr = []; // excel檔案內容陣列
複製程式碼
2.1獲取搜尋結果
目的: 搜尋包含關鍵詞的所有HTML檔案,並儲存這些資料。
- 核心方法
getCurrenAllFile()
我們通過 fs.readdir
方法,來獲取路徑下所有檔案和資料夾名稱作為一個集合;
然後遍歷該集合,當 stat.isDirectory()
為 true
則表示該結果為一個資料夾,為 false
則繼續使用 getCurrenAllFile()
來讀取下一層的檔案資訊。
/**
* 獲取當前專案的所有HTML檔案
* @param {string} paths 檔案的路徑
*/
var getCurrenAllFile = function (paths){
// ... 省略部分
var fileArr = [];// 初始化最終結果分類的物件
fs.readdir(paths, function(err, files){
_.forEach(files, function(item, index){
var c_path = path.join(paths, item);
var stat = fs.lstatSync(c_path);
// TODO 關鍵
if(stat && stat.isDirectory()){
// .. 省略過濾資料夾的操作
getCurrenAllFile(c_path);
}
}else{
// .. 省略過濾資料夾的操作
getCurrentFile(c_path, item);
}
});
});
return fileArr;
}
複製程式碼
- 核心方法
getCurrentFile()
讀取每個檔案的內容,然後再使用 searchCurrentFile()
方法去檢索我們要搜尋的關鍵詞。
/**
* 獲取當前檔案內容
* @param {string} paths 檔案的路徑
* @param {string} filename 檔名
*/
var getCurrentFile = function(paths, filename){
fs.readFile(paths, 'utf8', function(err, data){
// ... 省略部分
if (err) console.log(err);
searchCurrentFile(data, paths);
});
};
複製程式碼
- 核心方法
searchCurrentFile()
這裡遍歷我們定義的 classArray
陣列,這是包含我們所需要檢索的所有關鍵詞,如果檢索結果為 true
則將結果儲存到 resultArray
陣列和 resultAlassify
陣列。
/**
* 檢索當前檔案內容
* @param {object} data 檔案的內容
* @param {string} paths 檔案的路徑
*/
var searchCurrentFile = function(data, paths){
_.forEach(classArray, function(val){
// ... 省略部分
if(data.indexOf(val) >= 0){
resultArray.push(paths);
resultAlassify[val].push(paths); // 儲存最終結果(當前關鍵詞下的物件)
}
}
};
複製程式碼
2.2處理搜尋結果
目的: 將獲取到的資料,去重,格式化並儲存成JSON,作為視覺化的資料來源。
這裡有定義兩個簡單方法 unique()
用於資料去重,和 setEachDirFileNum()
統計檔案數量,不做具體介紹。
這裡我們使用 saveDataToJson()
將資料整理成 JSON 格式,並使用 setJSONFile()
方法,將JSON資料儲存為 json
檔案,用於視覺化操作。
- 核心方法
saveDataToJson()
這一步主要只用 loadsh
的分組函式 _.ground
來處理 JSON 資料,我們需要的格式是:
result = {
template: [
home:[ {}, {} ],
my: [ {}, {} ]
// ...
],
view: [
// ...
]
}
複製程式碼
然後還需要處理成儲存 Excel 時所需要的格式,再使用 setJSONFile()
方法儲存 JSON 檔案。
/**
* 轉成JSON資料,用來資料視覺化
* @param {*} data 需要處理的資料
*/
var saveDataToJson = function (data){
var result = {};
// 第一層分組 外層資料夾
result = _.groupBy(data, function(item){
item = item.replace(filePath+'\\','');
var list = item.split('\\');
return list[0];
});
// 第二層分組 內層資料夾
for(var k in result){
result[k] = _.groupBy(result[k], function(i){
i = i.replace(filePath+'\\','');
var r = i.split('\\');
return r[1];
});
}
for(var i in result){
for(var m in result[i]){
for(var n in result[i][m]){
var currentPath = result[i][m][n].replace(filePath+'\\','');
currentPath = currentPath.replace(/\\/g, '/');
var current = excelFileObj[currentPath];
result[i][m][n] = {
title : current ? current['路由名稱'] : '該檔案為模組',
path : current ? current['檔案路徑'] : currentPath,
url : current ? current['url'] : '該檔案為模組',
params: current ? current['路由引數'] : '該檔案為模組',
ctrl : current ? current['控制器名稱'] : '該檔案為模組',
urls : current ? current['url'] : '該檔案為模組',
};
}
}
}
setJSONFile(result); // 儲存JSON檔案
};
複製程式碼
2.3加入檔案標題路由等資料
目的: 解析外部路由Excel表,合併到原有資料
- 核心方法
getExcelFile()
讀取 Excel 資料並通過resolve
返回。
/**
* 讀取Excel資料
*/
var getExcelFile = function(){
return new Promise(function(resolve, reject){
var excelPath = path.join(__dirname, excelReadName);
fs.exists(excelPath, function(exists){
if(exists){
var workbook = XLSX.readFile(excelPath, {type: 'base64'});// 獲取 Excel 中所有表名
var sheetNames = workbook.SheetNames;
resolve({workbook: workbook, sheetNames: sheetNames});
}else{
reject({message:'錯誤提示:請先獲取路由列表檔案!(執行node get_router.js)'});
}
});
})
};
複製程式碼
- 核心方法
getEachSheet()
這裡我們需要將 Excel 中的每個表的資料,都儲存到 excelFileObj
中,另外需要注意,我們專案的 lodash
不能使用 4.0.0 以上版本的API。
/**
* 解析Excel資料
* @param {object} workbook excel工作區資料
* @param {object} sheetNames excel工作表名資料
*/
var getEachSheet = function(workbook, sheetNames){
_.forEach(sheetNames,function(item,index){
var sheet = workbook.Sheets[sheetNames[index]];
var json = XLSX.utils.sheet_to_json(sheet); // 針對單個表,返回序列化json資料
excelFileArr = excelFileArr.concat(json); // 不能使用lodash的_.concat 因為lodash版本太低
})
_.forEach(excelFileArr, function(val, key){
excelFileObj[val['檔案路徑']] = val;
});
}
複製程式碼
2.4生成結果檔案
目的: 將處理後的結果生成對應的 Excel/JSON/TXT 檔案:
這裡生成 JSON/TXT 檔案不做介紹,使用的是 Nodejs 內建的檔案儲存方法fs.write
- 核心方法
setExcelFile()
主要是整理資料為儲存 Excel 的資料格式。
/**
* 儲存Excel資料
* @param {object} data 需要處理的資料
* return excelFileName.xlsx
*/
var setExcelFile = function(data){
var workbook = new Excel.Workbook();
workbook.creator = 'EXE';
workbook.lastModifiedBy = 'Leo';
workbook.created = new Date();
workbook.modified = new Date();
workbook.lastPrinted = new Date();
for(var item in data){ // 第一層迴圈 外層資料夾 templates views
for(var list in data[item]){
var worksheet = workbook.addWorksheet(list.toUpperCase()),
rowData = data[item][list];
worksheet.columns = [
{ header: '頁面標題' , key: 'title' , width: 40 },
{ header: '檔案路徑' , key: 'path' , width: 60 },
{ header: '路由地址' , key: 'url' , width: 40 },
{ header: '路由引數' , key: 'params', width: 40 },
{ header: '控制器名稱', key: 'ctrl' , width: 40 },
{ header: 'url' , key: 'urls' , width: 40 },
];
for(var row in rowData){
worksheet.addRow({
title : rowData[row].title,
path : rowData[row].path,
url : rowData[row].url,
params: rowData[row].params,
ctrl : rowData[row].ctrl,
urls : rowData[row].urls,
})
}
}
}
workbook.xlsx.writeFile(path.join(__dirname, excelFileName)).then(function() {
// ... 省略部分
});
};
複製程式碼
到這裡我們 Nodejs 程式開發完成,我們最後會有一個檔案 search_current_file_json.json
作為 Python 部分的資料來源。
3.Python開發部分
Python 部分的內容相對比較簡單,做的只有 載入資料,簡單處理資料和視覺化操作 三部分。
同樣在剛開始部分,將幾個重要的定義寫一下:
# ... 省略一些
import matplotlib.pyplot as plt
keyName = [] # 需要顯示的分類圖表(按外層資料夾)
selectName = '' # 使用者選擇的資料夾名稱
複製程式碼
2.1讀取資料來源
我們通過使用 python
內建的 open
方法來讀取檔案,並匯入內建方法 json
來讀取前面 Nodejs
部分生成的 search_current_file_json.json
檔案。
file = open('./search_current_file_json.json','r', encoding='utf-8')
file = json.load(file)
複製程式碼
2.2設定命令列輸入項
設定命令列輸入項的目的是:讓使用者通過輸入要檢視的資料夾名稱,來展示對應資料夾的餅圖,預設顯示所有資料夾餅圖。
在設定之前,我們需要先通過 getKeyName()
方法獲取到所有第一層資料夾的名稱:
def getKeyName():
for name in file:
keyName.append(name)
複製程式碼
然後才能設定命令列輸入項:
getKeyName()
select = ','.join(keyName)
selectName = input('檢索到的資料夾有:【' + select + '】,請輸入要檢視的資料夾名稱(預設所有):')
複製程式碼
2.3繪製單張餅圖
接下來繪製單張餅圖,這裡主要就是設定餅圖的引數:
- 核心方法
drawOneChart()
def drawOneChart(name, label, data):
plt_title = name
plt.figure(figsize=(6,9)) # 調節圖形大小
labels = label # 定義標籤
sizes = data # 每塊值
colors = [ # 每塊顏色定義 這裡省略掉
#...
]
explode = [] # 將某一塊分割出來,值越大分割出的間隙越大
max_data = max(sizes)
for i in sizes: # 初始化每塊之間間距,最大值分割出來
if i == max_data:
explode.append(0.2)
else:
explode.append(0)
patches,text1,text2 = plt.pie(
sizes, explode = explode, labels = labels, colors = colors,
autopct = lambda pct: pctName(pct, data), # 數值保留固定小數位
frame = 1, # 是否顯示餅圖的圖框,這裡設定顯示
shadow = True, # 無陰影設定
labeldistance = 1.1, # 圖例距圓心半徑倍距離
counterclock = False, # 是否讓餅圖按逆時針順序呈現;
startangle = 90, # 逆時針起始角度設定
pctdistance = 0.6 # 數值距圓心半徑倍數距離
)
plt.xticks(())
plt.yticks(())
plt.axis('equal')
plt.legend()
plt.title(plt_title+'資料夾下檔案分佈(順時針)', bbox={'facecolor':'0.8', 'pad':5})
plt.savefig(plt_title+'_'+saveImgName) # 一定放在plt.show()之前
plt.show()
複製程式碼
2.4繪製多張餅圖
最後通過迴圈呼叫 drawOneChart()
來生成所有的餅圖:
- 核心方法
drawAllChart()
這個方法中需要對之前 JSON 資料再處理,將每個資料夾中檔案數量作為餅圖的資料,也就是這裡的 values
的值。
def drawAllChart(openName):
for name in keyName:
labels = []
values = []
for view_name in file[name]:
labels.append(view_name)
values.append(len(file[name][view_name]))
if openName == '' or openName == name:
drawOneChart(name, labels, values)
else:
print('輸入有誤')
複製程式碼
四、總結
1.Nodejs知識點
這部分用得比較多的是 Nodejs 中的:
- 檔案讀/寫操作
- 正則匹配操作
- 資料格式處理操作
因此為了以後開發類似或者其他型別工具,還是需要加強這三方面的知識,這部分的程式碼可能不夠簡潔,程式碼也不夠美觀,但畢竟作為自己的經驗積累,對這類工具開發會有更加清晰的思路。
2.Python知識點
這部分用得比較多的,其實是 Python 中的一些基礎語法,這部分程式碼,其實也是加深自己對 Python 基礎語法的使用和理解,練習操作。
3.擴充
接下來會找時間,優化專案程式碼,然後改造這個專案,將使用 Nodejs 和 Python 分別單獨開發一套,並比較兩者差距(執行效率/程式碼量)。 另外 Nodejs 的繪相簿還有: node-echarts 和 d3-node。