做者:leo
更新:2019.03.14
項目源碼:githubphp
因爲公司 V2項目 須要作組件化升級,但由於 V2項目 項目歷史包袱大, 代碼和文件很是多,並且嵌套較多,難以全面瞭解所須要調整的組件的影響範圍,因此須要開發這麼一個工具,來實現如下幾個功能:html
基於上面需求,我大概整理思路使用 Nodejs
和 Python
進行需求開發,緣由有這幾點:前端
起初我準備只使用 Nodejs
完成這個需求,後面開發到一半,發現 數據可視化 方面,實在找不到一個滿意的可視化插件,因而想到 Python
的一個2D繪圖庫—— Matplotlib
,使用起來很是方便,因而便選擇了它。node
這也是我用 Nodejs 作的第一個做品,還有不少優化空間,歡迎大佬指點哈,感激涕零。python
對於 Nodejs
環境搭建,相信對於咱們前端開發仔來講,應該是很簡單,但這裏考慮到可能原生的同窗還不太清楚,這裏我簡單介紹:android
Nodejs
咱們到 Nodejs官網 ,選擇對應系統環境進行下載,而後直接打開安裝。git
Nodejs
環境打開命令行工具,執行 node -v
,看是是否輸出對應 Nodejs
版本號,我這顯示:github
v10.8.0
複製代碼
另外在 WIN7 系統下可能會出現下面報錯,則須要將 nodejs
安裝目錄,添加全局路徑:npm
node : 沒法將「node」項識別爲 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫,若是包括路徑,請確保路徑正確,而後再試一次。
複製代碼
Python
在 Python官網 ,選擇 3.x 版本下載(因爲Python2.x版本已經中止維護,而且即將被淘汰),下載完成直接安裝。json
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
複製代碼
首先先介紹下開發的思路:
最終我實現的效果是,開發 search_current_file.js
和 search_current_file_python.py
兩個文件,並經過執行兩個命令,來獲取對應數據文件:
node search_current_file.js
複製代碼
python search_current_file_python.py
複製代碼
這裏須要輸入須要生成的指定文件夾的數據,默認不輸入則生成全部文件夾下的數據。
首先定義幾個下面主要使用的變量,其餘沒有寫在這裏的變量和做用,能夠查看源碼。
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文件內容數組
複製代碼
目的: 搜索包含關鍵詞的全部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); // 保存最終結果(當前關鍵詞下的對象)
}
}
};
複製代碼
目的: 將獲取到的數據,去重,格式化並保存成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文件
};
複製代碼
目的: 解析外部路由Excel表,合併到原有數據
getExcelFile()
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;
});
}
複製代碼
目的: 將處理後的結果生成對應的 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 部分的數據源。
Python 部分的內容相對比較簡單,作的只有 加載數據,簡單處理數據和可視化操做 三部分。
一樣在剛開始部分,將幾個重要的定義寫一下:
# ... 省略一些
import matplotlib.pyplot as plt
keyName = [] # 須要顯示的分類圖表(按外層文件夾)
selectName = '' # 用戶選擇的文件夾名稱
複製代碼
咱們經過使用 python
內置的 open
方法來讀取文件,並導入內置方法 json
來讀取前面 Nodejs
部分生成的 search_current_file_json.json
文件。
file = open('./search_current_file_json.json','r', encoding='utf-8')
file = json.load(file)
複製代碼
設置命令行輸入項的目的是:讓用戶經過輸入要查看的文件夾名稱,來展現對應文件夾的餅圖,默認顯示全部文件夾餅圖。
在設置以前,咱們須要先經過 getKeyName()
方法獲取到全部第一層文件夾的名稱:
def getKeyName():
for name in file:
keyName.append(name)
複製代碼
而後才能設置命令行輸入項:
getKeyName()
select = ','.join(keyName)
selectName = input('檢索到的文件夾有:【' + select + '】,請輸入要查看的文件夾名稱(默認全部):')
複製代碼
接下來繪製單張餅圖,這裏主要就是設置餅圖的參數:
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()
複製代碼
最後經過循環調用 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('輸入有誤')
複製代碼
這部分用得比較多的是 Nodejs 中的:
所以爲了之後開發相似或者其餘類型工具,仍是須要增強這三方面的知識,這部分的代碼可能不夠簡潔,代碼也不夠美觀,但畢竟做爲本身的經驗積累,對這類工具開發會有更加清晰的思路。
這部分用得比較多的,實際上是 Python 中的一些基礎語法,這部分代碼,其實也是加深本身對 Python 基礎語法的使用和理解,練習操做。
接下來會找時間,優化項目代碼,而後改造這個項目,將使用 Nodejs 和 Python 分別單獨開發一套,並比較二者差距(執行效率/代碼量)。 另外 Nodejs 的繪圖庫還有: node-echarts 和 d3-node。