有一段时间工作重心集中在公众号的开发上,虽然团队有类似jenkins的自动部署,但有时为了解决兼容问题需要反复进行发布测试,类似基于仓库的部署工具就会造成许多没必要的提交信息,故有了这篇文章。
理想的部署工具需要具备的功能
- 基于目前团队的开发,这款部署工具平常需要发布经打包后的webpack以及类express的整库上传
- 自动对需要发布的文件进行压缩
- 对服务器账号增删查
- 对存储在本地的服务器关键信息进行加密
使用插件
- ssh: 用于处理登录
- commander: 声明指令
- compressing: 打包压缩
- inquirer: 终端交互
- fs: 文件读取
- path:存储路径
- crypto-js: 密码加密
服务器地址存储
插件需实现服务器的增删查就需要对服务器进行存储,故在本地创建data.json进行数据存储。数据格式:
{
"data": [
{
"name": "test",
"config": {
"ip": "*****",
"pw": "*****.",
"dir": "*****",
"user": "*****"
}
}
]
}
增删查服务器
使用commander对指令进行定于,-a为添加操作,-l为列表操作,-rm为删除操作。
program
.option('-a, --add', 'add service')
.option('-rm, --remove', 'remove service')
.option('-l, --list', 'list service');
program.parse(process.argv);
switch (true) {
case program.add:
add()
break
case program.remove:
remove()
break
case program.list:
list()
break
default:
push()
}
fs将通过inquirer获取参数对data.json进行增删查。注:nodejs的写入文件只认识字符串或者二进制数,所以把json对象转换成字符串再写入json文件中。
文件压缩
实现了压缩发布dist以及全库压缩上传两种方式,默认情况下发布当前目录下的dist文件夹,当选择是全库上传时,需要多调用copy2dist函数将当前目录下文件打包成dist压缩包,注:忽略node模块文件夹。
var fs = require('fs');
var path = require("path");
function writeFile(p, text) {
fs.writeFile(p, text, function (err) {
if (!err)
console.log("写入成功!")
})
}
//递归创建目录 同步方法
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirsSync(path.dirname(dirname))) {
console.log("mkdirsSync = " + dirname);
fs.mkdirSync(dirname);
return true;
}
}
}
function _copy(src, dist) {
var paths = fs.readdirSync(src)
paths.forEach(function (p) {
var _src = src + '/' + p;
var _dist = dist + '/' + p;
var stat = fs.statSync(_src)
if (stat.isFile()) { // 判断是文件还是目录
fs.writeFileSync(_dist, fs.readFileSync(_src));
} else if (stat.isDirectory() && p != 'node_modules' && p != 'dist') {
copyDir(_src, _dist) // 当是目录是,递归
}
})
}
/*
* 复制目录、子目录,及其中的文件
* @param src {String} 要复制的目录
* @param dist {String} 复制到目标目录
*/
function copyDir(src, dist) {
var b = fs.existsSync(dist)
if (!b) {
mkdirsSync(dist); //创建目录
}
_copy(src, dist);
}
function createDocs(src, dist, callback) {
console.log("createDocs...")
copyDir(src, dist);
console.log("copyDir finish exec callback")
if (callback) {
callback();
}
}
module.exports = createDocs
加密
存储服务器信息的data是存储在本地的,所以应对服务器信息的密码进行必要的加密。这里的加密选择了crypto-js,可逆的对称加密方案。
const CryptoJS = require('crypto-js'); //引用AES源码js
function getAesString(data, key, iv) { //加密
var key = CryptoJS.enc.Utf8.parse(key);
var iv = CryptoJS.enc.Utf8.parse(iv);
var encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString(); //返回的是base64格式的密文
}
function getDAesString(encrypted, key, iv) { //解密
var key = CryptoJS.enc.Utf8.parse(key);
var iv = CryptoJS.enc.Utf8.parse(iv);
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
function getAES(data) { //加密
var key = '****************'; //密钥
var iv = 'ABCDEF1234123412';
var encrypted = getAesString(data, key, iv); //密文
var encrypted1 = CryptoJS.enc.Utf8.parse(encrypted);
return encrypted;
}
function getDAes(data) { //解密
var key = '****************'; //密钥
var iv = 'ABCDEF1234123412';
var decryptedStr = getDAesString(data, key, iv);
return decryptedStr;
}
module.exports = {
getAES,
getDAes
}
注:密钥key需为16位。
发布
建立连接将dist压缩包发布至服务器指定路径中并执行设定好的comment指令集。
// 连接函数
function conFun() {
console.log('开始连接...');
connection = new ssh2();
connection.connect({
"host": ip,
"username": user,
"password": getDAes(password)
});
connection.on('error', function (err) {
connection.end()
console.log('连接失败');
});
connection.on('ready', function () {
console.log('开始上传');
upLoadFile(connection, params)
});
}
// 上传函数
function upLoadFile(conn, params) {
const file = params.file
const target = params.target
if (!conn) {
return
}
conn.sftp(function (err, sftp) {
if (err) {
throw err
}
console.log(file, target);
sftp.fastPut(file, target, {}, function (err, result) {
if (err) {
throw err
}
Shell(conn)
})
})
}
// 执行脚本函数
function Shell(conn) {
conn.shell(function (err, stream) {
if (err) throw err;
stream.on('close', function () {
console.log('发布完成!!');
// 删除压缩包
fs.unlinkSync('./dist.zip')
conn.end();
}).on('data', function (data) {
console.log('STDOUT: ' + data)
});
stream.end(comment.join('\n'));
});
}
comment = [
`cd ${servicePath}`,
'unzip -o dist.zip',
'rm -f dist.zip',
'exit',
'close'
]
小结
写这款部署插件的初衷是由于真机调试公众号端时需要频繁的发布,总的来说并不建议使用于正式版本的发布。