[node 工具] 用 Node.js 将 bugzilla 上的 bug 列表导入到 excel 表格在线版本之一(server 端)
之前写了个 用 Node.js 将 bugzilla 上的 bug 列表导入到 excel 表格里 的 cli 工具虽然可以用,但考虑到一下几点,总觉得需要再做点什么。
- 界面简陋,我那截图上是在 VSCode 下的 git bash 里使用的,看起来倒还好一些。如果是在 CMD 下使用,不忍直视。
- 需要使用命令的方式启动,URL 地址还需要添加双引号,体验不好。
- 需要自行安装 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 表格