提升效率,記一個內部工具的開發經歷

1、開發介紹

做者:leo
更新:2019.03.14
項目源碼:githubphp

1.開發背景

因爲公司 V2項目 須要作組件化升級,但由於 V2項目 項目歷史包袱大, 代碼和文件很是多,並且嵌套較多,難以全面瞭解所須要調整的組件的影響範圍,因此須要開發這麼一個工具,來實現如下幾個功能:html

  • 須要能支持 自定義關鍵詞檢索 ,便於按不一樣的已有組件名搜索;
  • 須要能支持檢索出該組件的 影響文件範圍 ,還有 頁面名稱路由 等,便於測試按照頁面快速測試;
  • 須要能支持 數據可視化 ,便於判斷全部影響範圍的權重;
  • 須要能 導出影響範圍的路由文件必要數據

基於上面需求,我大概整理思路使用 NodejsPython 進行需求開發,緣由有這幾點:前端

  • 需求以操做文件爲主,包括讀寫;
  • 需求對數據處理操做比較多,包括過濾,組裝數據格式;
  • 需求對數據可視化的需求;

起初我準備只使用 Nodejs 完成這個需求,後面開發到一半,發現 數據可視化 方面,實在找不到一個滿意的可視化插件,因而想到 Python 的一個2D繪圖庫—— Matplotlib ,使用起來很是方便,因而便選擇了它。node

這也是我用 Nodejs 作的第一個做品,還有不少優化空間,歡迎大佬指點哈,感激涕零。python

2.工具文檔

2、開發環境搭建

1.Nodejs環境搭建

對於 Nodejs 環境搭建,相信對於咱們前端開發仔來講,應該是很簡單,但這裏考慮到可能原生的同窗還不太清楚,這裏我簡單介紹:android

  • 下載和安裝 Nodejs

咱們到 Nodejs官網 ,選擇對應系統環境進行下載,而後直接打開安裝。git

  • 測試 Nodejs 環境

打開命令行工具,執行 node -v ,看是是否輸出對應 Nodejs 版本號,我這顯示:github

v10.8.0
複製代碼

另外在 WIN7 系統下可能會出現下面報錯,則須要將 nodejs 安裝目錄,添加全局路徑:npm

node : 沒法將「node」項識別爲 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫,若是包括路徑,請確保路徑正確,而後再試一次。
複製代碼
  • 安裝完成

2.Python環境搭建

  • 下載和安裝 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
複製代碼
  • 安裝完成

3、開發過程

首先先介紹下開發的思路:

20190311share4

1.最終效果

最終我實現的效果是,開發 search_current_file.jssearch_current_file_python.py 兩個文件,並經過執行兩個命令,來獲取對應數據文件:

  • 獲取 全部包含關鍵詞的文件的路徑所在文件夾內文件數量全部文件對應頁面的路由/參數/標題等數據 統計的文件和表格。
node search_current_file.js
複製代碼

20190311share3

  • 獲取 全部文件夾中文件數量佔總文件數的比例 的餅圖結果。
python search_current_file_python.py
複製代碼

這裏須要輸入須要生成的指定文件夾的數據,默認不輸入則生成全部文件夾下的數據。

20190311share2

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('輸入有誤')

複製代碼

4、總結

1.Nodejs知識點

這部分用得比較多的是 Nodejs 中的:

  • 文件讀/寫操做
  • 正則匹配操做
  • 數據格式處理操做

所以爲了之後開發相似或者其餘類型工具,仍是須要增強這三方面的知識,這部分的代碼可能不夠簡潔,代碼也不夠美觀,但畢竟做爲本身的經驗積累,對這類工具開發會有更加清晰的思路。

2.Python知識點

這部分用得比較多的,實際上是 Python 中的一些基礎語法,這部分代碼,其實也是加深本身對 Python 基礎語法的使用和理解,練習操做。

3.拓展

接下來會找時間,優化項目代碼,而後改造這個項目,將使用 Nodejs 和 Python 分別單獨開發一套,並比較二者差距(執行效率/代碼量)。 另外 Nodejs 的繪圖庫還有: node-echartsd3-node

bg
相關文章
相關標籤/搜索