[node 工具] 用 Node.js 将 bugzilla 上的 bug 列表导入到 excel 表格在线版本之一(server 端)

之前写了个 用 Node.js 将 bugzilla 上的 bug 列表导入到 excel 表格里 的 cli 工具虽然可以用,但考虑到一下几点,总觉得需要再做点什么。

  1.  界面简陋,我那截图上是在 VSCode 下的 git bash 里使用的,看起来倒还好一些。如果是在 CMD 下使用,不忍直视。
  2.  需要使用命令的方式启动,URL 地址还需要添加双引号,体验不好。
  3.  需要自行安装 nodejs 环境

因此我将这个工具做成了在线的版本,只要复制个 URL,点击开始,傻瓜操作,多人使用。

 

 

 

 1 var express = require('express');
 2 var bodyParser = require('body-parser');
 3 var childProcess = require('child_process');
 4 var fs = require('fs');
 5 
 6 const MaxTask = 10;
 7 const port = 1024;
 8 
 9 var app = express();
10 
11 app.use(express.static('public'));
12 
13 app.get("/", function(req, res) {
14     console.log("[New Visitors]: " + req.ip.replace(/[^\d\.]/g, ""));
15     res.sendfile("./public/index.html");
16 });

首先加载模块,定义端口和同时运行的最大数目任务(下载 bug 的 server 扛得住,bugzilla server扛不住,任务一多连同事的正常使用也会变慢 )。

定义一个 express 的实例,设置静态文件目录。

设置首页。

 

1 var tasks = new Array();
2 app.use(bodyParser.json());

定义任务列表,因为 api 的数据格式是 json ,所以添加一个中间件解析数据

 

/*tasks = [{
    fingerprint: 12345678,
    startTime: 123456,
    saveName: "test.xlsx"
    child: child_process,
    status: [error|running|done],
    reason: "",
    total: 245,
    done: 34
}]*/

这是任务列表的数据结构

 

app.post('/start', function(req, res) {
    var fromIP = req.ip.replace(/[^\d\.]/g, "");
    if (getRunningNum() > (MaxTask - 1)) {
        res.json({ result: "fail", reason: "maxTask", maxTask: MaxTask });
        return;
    }

    var url = req.body.taskURL;
    var fingerprint = req.body.fingerprint;
    var index = getTaskIndex(fingerprint);
    if (index != -1) {
        if (tasks[index].status == "running") {
            res.json({ result: "fail", reason: "running" });
            return;
        } else {
            tasks.splice(index, 1);
            deleteExcel(fingerprint);
        }
    }
    var child = childProcess.fork("./transport.js", [url, fingerprint, fromIP]);

    console.log("[New Task]: From IP " + fromIP)

    tasks.push({ fingerprint: fingerprint, child: child, status: "running", startTime: (new Date()).getTime() });

    res.json({ result: "success", reason: "" });

    child.on('message', function(msg) {
        var id = getTaskIndex(msg.fingerprint);
        if (id != -1) {
            tasks[id].status = msg.status;
            tasks[id].reason = msg.reason;
            tasks[id].total = msg.total;
            tasks[id].done = msg.done;
            tasks[id].saveName = msg.saveName;
        }
    });
});

收到一个任务请求 API。先判断是否达到最大任务数目,再根据 fingerprint 判断任务列表里是否已经存在当前浏览器的一个任务。如果有,并且正在运行,就返回错误。如果任务已经完成,从任务列表里删除。

启动一个子进程去下载 bug 信息,并将任务添加到任务列表。监听子进程的消息,实时更新任务列表的状态。

 

1 function getTaskIndex(fingerprint) {
2     for (var i in tasks) {
3         if (tasks[i].fingerprint == fingerprint)
4             return i;
5     }
6     return -1;
7 }

根据 fingerprint 来判断一个任务是否有在任务列表里

 

app.get("/status", function(req, res) {
    var fingerprint = req.query.fingerprint;
    var i = getTaskIndex(fingerprint);
    if (i != -1) {
        res.json({ status: tasks[i].status, reason: tasks[i].reason, total: tasks[i].total, done: tasks[i].done });
    } else {
        res.json({ status: "error", reason: "noProcess" });
    }
})

获得当前任务的状态 API

 

app.get("/taskNum", function(req, res) {
    res.json({ running: getRunningNum(), max: MaxTask });
})
function getRunningNum() {
    var num = 0;
    for (var i in tasks) {
        if (tasks[i].status == "running")
            num++;
    }
    return num;
}

获得任务总数 API

 

app.get("/download", function(req, res) {
    var fingerprint = req.query.fingerprint;
    var files = fs.readdirSync('excel');
    var saveName = "result.xlsx";
    var index = getTaskIndex(fingerprint);
    if (index != -1)
        saveName = tasks[index].saveName;
    if (files.indexOf(fingerprint + ".xlsx") != -1) {
        res.type("application/binary");
        res.download("excel/" + fingerprint + ".xlsx", saveName);
    } else {
        res.json({ status: "fail" });
    }
})

下载 excel 结果 API

 

var server = app.listen(port, function() {
    console.log('Bug To Excel web site start at ' + (new Date).toLocaleString());
});

启动 server

 

setInterval(clearTask, 1 * 60 * 60);

function clearTask() {
    var current = (new Date()).getTime();
    for (var i in tasks) {
        if (parseInt((current - tasks[i].startTime) / 1000) > 1 * 60 * 60) {
            deleteExcel(tasks[i].fingerprint);
            tasks.splice(i, 1);
        }
    }
}

function deleteExcel(fingerprint) {
    var file = "excel/" + fingerprint + ".xlsx";
    fs.exists(file, function(exists) {
        if (exists) {
            fs.unlink(file, function(err) {
                if (err) {
                    console.log("Delete " + file + " failed.");
                }
            });
        }
    })
}

每隔一个小时判断任务是否过期,果然过期,就删除 excel 文件和移出任务列表。

 


 

 

提供静态文件和 API 的 server 就是以上这样。接下来看子进程是如果去下载 bug 信息以及生成 excel 表格。同时也有将状态更新到父进程。

下载 bug 信息的代码,其实跟之前 cli 那篇差不多。但是有个最重要的改动是,cli 那个工具是一个一个 bug 去取信息的,这样对服务器的负担重,在 bug 有几百个的时候,和服务器的连接经常出错。

因此在在线版本这里改成每 200 个 bug 发送一次请求。这样就大大减少了连接数目,而且还使得速度快了很多。使得同时支持多个任务成为了可能。

 

大部分代码请参考 cli 那篇。这里只说分块下载 bug 信息的部分以及更新任务状态。

 

var splitLength = 200;

.then(function(bugs) {
    var splitBugs = new Array();
    do {
        splitBugs.push(bugs.splice(0, splitLength));
    }
    while (bugs.length > 0)

    var done = 0;

    return Promise.all(splitBugs.map(function(eachGroup, index) {
        var splitPromise = getLongFormat(eachGroup);

        splitPromise.then(function() {
            done += eachGroup.length;
            msg.done = done;
            process.send(msg);
        })

        return splitPromise;
    }))
})

在获得 bug id 的数组后,将数组每隔200个分出来

 

function getLongFormat(bugs) {
    var postData = bugs.join("&") + "&ctype=xml&excludefield=attachmentdata";
    return postFunc(bugUrl, postData, function(url, data) {
        var $ = cheerio.load(data);
        var xmlLists = $("bugzilla bug");
        var bugLists = new Array();
        xmlLists.each(function(key) {
            var oneBug = xmlLists.eq(key);
            var oneInfo = new Object();
            oneInfo.id = oneBug.children("bug_id").text();
            oneInfo.url = bugUrl + "?id=" + oneInfo.id;
            oneInfo.summary = oneBug.children("short_desc").text();
            oneInfo.reporter = oneBug.children("reporter").text();
            oneInfo.product = oneBug.children("product").text();
            oneInfo.component = oneBug.children("component").text();
            oneInfo.version = oneBug.children("version").text();
            oneInfo.status = oneBug.children("bug_status").text();
            oneInfo.priority = oneBug.children("priority").text();
            oneInfo.security = oneBug.children("bug_security").text();
            oneInfo.assign = oneBug.children("assigned_to").text();
            oneInfo.comment = new Array();
            var comments = oneBug.children("long_desc");
            comments.each(function(key) {
                var who = comments.eq(key).find("who").text();
                var when = comments.eq(key).find("bug_when").text();
                when = when.replace(/([^\s]+)\s.*$/g, "$1");
                var desc = comments.eq(key).find("thetext").text();
                if (key == 0 && who == oneInfo.reporter) {
                    oneInfo.detail = desc;
                    return true;
                }
                oneInfo.comment.push({ 'who': who, 'when': when, 'desc': desc });
            })
            bugLists.push(oneInfo);
        })

        msg.done = bugLists.length;
        process.send(msg);

        return bugLists;
    })
}

将 200 个 bug id 附在 post 请求数据里发送出去,得到的数据是所有 bug 的 xml 格式,解析每个 xml 得到每个 bug 信息。至于写入到 excel 部分,还请参考 cli 那篇

 

 

得到的 excel 表格

 

posted @ 2018-01-14 15:32  liqingjht  阅读(385)  评论(0编辑  收藏  举报