TMS (tech manage system) 其实是技术内部工具集合的平台。其中最主要一个内容是包含了项目发布系统,发布系统是利用 Fabric
可以比较轻松的通过编程的方式在目标机器上执行 shell 命令特性。我在其中主要是设计和开发它的用户界面,方便开发人员通过图形化的界面操作系统及观察命令运行情况。也是第一次用nodejs开发后台应用。
技术选型
- NodeJS
- Express
- xtemplate
- bootstrap
- xterm
- pm2
目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| . ├── libs │ ├── db.js │ ├── fab.js │ └── project.js ├── public │ └── assets ├── routes │ ├── autotest.js │ ├── deploy.js │ └── index.js ├── views │ ├── autotest_start_up.xtpl │ ├── deploy_projects.xtpl │ ├── home.xtpl │ ├── layout.xtpl │ ├── menu.xtpl │ ├── project_build.xtpl │ ├── project_building.xtpl │ ├── project_detail.xtpl │ └── project_history.xtpl ├── app.js ├── config.js ├── package.json └── start-web.sh
|
安装运行
1 2 3
| npm insatll export TMS_ENV="local" node app.js
|
开发相关
路由的设计 - 中间件
应用级中间件,方便模块化路由
1 2 3 4 5 6 7
| var index = require('./routes/index') var deploy = require('./routes/deploy') var autotest = require('./routes/autotest')
app.use('/', index) app.use('/deploy', deploy) app.use('/autotest', autotest)
|
创建路由级中间件
1 2 3 4 5 6 7 8 9 10 11 12 13
| router.get('/:projectName/detail', function (req, res) {
var projectName = req.projectName
var targets = project.getTargetByProjectName(projectName)
res.render('project_detail', { project: projectJson[projectName], projectName: projectName, targets: targets, buildCount: req.buildCount }) })
|
使用 param 中间件给所有带 projectName
的路径挂载同一数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| router.param('projectName', function (req, res, next, projectName) {
var getRunningBuild = db.getBuilding(projectName)
getRunningBuild.done(function (buildList) {
buildList.map(function (item, index) { formatHistoryData(item) })
req.projectName = projectName req.building = buildList req.buildCount = buildList.length next() })
});
|
异步管理 - promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| exports.getBuilding = function (projectName) { var sqlDefer = Q.defer()
pool.getConnection(function (err, connection) {
connection.query(sql, function (err, rows, fields) {
if (!err) { sqlDefer.resolve(rows) } else { sqlDefer.reject(err) }
connection.release() }) })
return sqlDefer.promise }
var getBuild = getBuilding(projectName);
getBuild.done(function (builds) {
})
|
面向前端的模版 - xtemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| {{extend ("./layout")}}
{{#block("page_class")}} page_project_build {{/block}}
{{#block ("menu")}} {{parse ("./menu", current="deploy")}}
{{/block}}
{{#block ("body")}}
.....
{{/block}}
|
调用 shell 命令 - child_process
1 2 3 4 5 6 7
| var cp = require('child_process') cp.exec(commondLine, { cwd: path.join(__dirname, '../../tms-belt'), timeout: 3000 }, function (error, stdout, stderr) { })
|
调试工具 - node-inspector
1 2 3 4 5 6
| node-inspector node app.js --debug
或者
node-debug app.js
|
进程管理 - pm2
1 2 3 4 5 6 7
| pm2 start app.js --node-args="--debug" --watch pm2 list pm2 show 0 pm2 logs pm2 restart pm2 kill 0 pm2 monit
|
日志的实时显示
以为web页面中显示出的日志文件也是需要类似代码格式化类的工具,其实是需要web版的终端来实现的。我采用的方案是 xtrm 特别当我们需要实时数据时,代码格式化的性能是跟不上的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| function websocket(server){
const socket = io.listen(server)
socket.on('connection', (client) => {
client.on('cmd_stop', (projectName, targetKey)=>{
var fab_param = 'do:' + projectName + ',target_key=' + targetKey + ',cmd=stop'
var fab = cp.spawn('fab', [fab_param], { cwd: path.join(__dirname, '../../tms-belt'), timeout: 3000 })
fab.stdout.on('data', function(buffer){ client.emit('cmd_stop_data', buffer.toString()) })
fab.stderr.on('data', (buffer) => { client.emit('socket_error', `${buffer}`) });
fab.on('exit', function(){ client.emit('cmd_exit') })
client.on('disconnect', () => { fab.kill() })
})
....
})
});
}
|
其它
NodeJS 版本问题
现在 NodeJS 版本已经到了 5.X 以上了,对 ES6 的特性支持的很好,但是模块的依赖关系复杂,目前还是用在 0.X 的比较安全
如何处理好环境配置信息
- 使用环境变量 env/export
- 使用项目外文件管理 config.json/confiy.yml
- 使用 config 模块