目标
使用nodejs的express框架完成一个rest接口,调用一个go语言编写的工具生成两个文件(为啥用go写这个工具,因为不会用js写),返回给前端一个压缩包,并对文件内容做一些业务处理。
调用
使用child_process包,更多官方文档
const {spawn} = require('child_process');
const cmd = 'tools/finca';
let args = ['-type=root'];
let ls_root = spawn(cmd, args);
ls_root.stdout.on('data', (data) => {
logger.info(`stdout: ${data}`);
});
ls_root.stderr.on('data', (data) => {
logger.error(`stderr: ${data}`);
});
ls_root.on('close', (code) => {
logger.info(`finca tool process exited with code ${code}`);
});
压缩
使用jszip包,官网,有时候会被墙。
const JSzip = require('jszip');
let zip = new JSzip();
zip.file(caRequest.commonName + "-cert.pem", CAResult.cert);
zip.file(caRequest.commonName + ".key", CAResult.privateKey);
let data = zip.generate({base64: false, compression: 'DEFLATE'});
文件夹删除
工具生成的文件和压缩文件均不保存,所有全部放在一个临时文件夹,结束后删除。
function deleteFolder(path) {
let files = [];
if (fs.existsSync(path)) {
files = fs.readdirSync(path);
files.forEach(function (file, index) {
let curPath = path + "/" + file;
if (fs.statSync(curPath).isDirectory()) { // recurse
deleteFolder(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}
实现rest接口
这里最重要的是保证顺序执行
util
工具放在特定目录,一共生成了两个可执行文件,其中一个为.exe,方便在window环境下调试。调试时无需更改cmd,child_process调用时自动会根据操作系统调用.exe文件。
const {spawn} = require('child_process');
const fs = require("fs");
const Promise = require("bluebird");
const os = require('os');
const JSzip = require('jszip');
const certFileSuffix = "-cert.pem";
const priFileSuffix = "_sk";
const cmd = 'tools/finca';
let caUtil = {};
caUtil.createRootCA = function (caRequest, res) {
let args = ['-type=root'];
return createCA(caRequest, args, res);
};
caUtil.createClientCA = function (caRequest, res, rootCertBase64String, rootPrivateKeyBase64String, base64PublicKeyString) {
let args = ['-type=client'];
if (rootCertBase64String && rootPrivateKeyBase64String) {
args.push('-rootCert=' + rootCertBase64String);
args.push('-rootPri=' + rootPrivateKeyBase64String);
} else {
return Promise.reject('miss root CA')
}
return createCA(caRequest, args, res, base64PublicKeyString);
};
function createCA(caRequest, args, res, base64PublicKeyString) {
let exitPubKey = false;
let CAResult = {
privateKey: '',
cert: ''
};
let dir;
if (base64PublicKeyString) {
logger.info("public exit,do not create private key");
exitPubKey = true;
args.push('-clientPub=' + base64PublicKeyString)
}
if (caRequest.commonName) {
args.push('-cn=' + caRequest.commonName);
dir = os.tmpdir() + '/' + caRequest.commonName + '/';
args.push('-dir=' + dir);
} else {
return Promise.reject("miss commonName")
}
if (caRequest.country) {
args.push('-c=' + caRequest.country);
}
if (caRequest.effectiveDays) {
args.push('-day=' + caRequest.effectiveDays);
}
if (caRequest.locality) {
args.push('-l=' + caRequest.locality);
}
if (caRequest.organization) {
args.push('-o=' + caRequest.organization);
}
if (caRequest.organizationalUnit) {
args.push('-ou=' + caRequest.organizationalUnit);
}
if (caRequest.province) {
args.push('-p=' + caRequest.province);
}
if (caRequest.role) {
args.push('-role=' + caRequest.role);
}
logger.info("create CA args:", args);
let ls_root = spawn(cmd, args);
ls_root.stdout.on('data', (data) => {
logger.info(`stdout: ${data}`);
});
ls_root.stderr.on('data', (data) => {
logger.error(`stderr: ${data}`);
});
return new Promise(function (accept, reject) {
ls_root.on('close', (code) => {
logger.info(`finca tool process exited with code ${code}`);
if (code === 0) {
CAResult.cert = fs.readFileSync(dir + caRequest.commonName + certFileSuffix);
if (!exitPubKey) {
CAResult.privateKey = fs.readFileSync(dir + caRequest.commonName + priFileSuffix);
}
if (res) {
logger.info(`do zip`);
let zip = new JSzip();
zip.file(caRequest.commonName + "-cert.pem", CAResult.cert);
if (!exitPubKey) {
zip.file(caRequest.commonName + ".key", CAResult.privateKey);
}
let data = zip.generate({base64: false, compression: 'DEFLATE'});
res.set({
'Content-Type': 'application/vnd.openxmlformats',
"Content-Disposition": "attachment; filename=" + caRequest.commonName + ".zip"
});
res.end(data, 'binary');
}
logger.info(`ca tool work done, delete temp dir`);
deleteFolder(dir);
accept(CAResult);
} else {
reject("create CA error with code:", code)
}
})
})
}
function deleteFolder(path) {
let files = [];
if (fs.existsSync(path)) {
files = fs.readdirSync(path);
files.forEach(function (file, index) {
let curPath = path + "/" + file;
if (fs.statSync(curPath).isDirectory()) { // recurse
deleteFolder(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}
module.exports = caUtil;
route
const express = require('express');
const router = express.Router();
const caUtil = require('../utils/caUtil');
router.post('/register', function (req, res, next) {
let promise= caUtil.createRootCA(req.body,res);
promise.then(CAResult=>{
console.log("这里做一些业务相关操作",CAResult);
})
});
module.exports = router;
最后app.js添加引用
mocha测试框架
const app = require("../app.js");
const supertest = require("supertest");
const should = require('should');
const request = supertest(app);
const caReq={
commonName:"test.orgTest.cn",
country:"CN",
locality:"GuangDong",
province:"ShenZhen",
effectiveDays:365,
organization:"xxxxx.orgTest.cn",
organizationalUnit:"xxxxx",
role:"admin"
};
describe('===组织管理测试===', function (done) {
it("---组织注册成功---",function(done){
request.post('/org/register')
.send(caReq)
.end(function (err, res) {
console.log(res.body);
done();
});
});
});
这个测试看不出来下载效果,测试代码逻辑比较方便。可以启动项目用postman点击Send And Download试一下下载压缩文件即可。