shellTransform.js文件如下:
// 使用 Mock var Mock = require('mockjs') var request = JSON.parse(process.argv[2]), response = JSON.parse(process.argv[3]); response.body = response.body.replace('${city}', Mock.mock('@city')); console.log(JSON.stringify(response)); |
那么通过http://localhost:8086访问后的结果如下:
Hello, 厦门市! |
那么我们可以联想下,能够shellTransform.js使用外部数据源,解耦性更强了,只要外部输出json数据到控制台就可以拿到数据了。 shellTransform.js本身可以是nodejs,那么连接数据库、nosql都是分钟级的事情了,有包含上下文的业务场景挡板不就直接搞定了。
至此mountebank的使用已经讲解完了,有不明白的可以和我联系(微信号dcs678),加之前请备注mountebank或mb,否则拒绝通过。 接着看后面的实战干活哈,go on!
mockjs简介
功能介绍
|主要功能 | 功能描述| |-- |-- | |前后端分离 |让前端攻城师独立于后端进行开发。 | |增加单元测试的真实性 |通过随机数据,模拟各种场景。 | |开发无侵入 |不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。 | |用法简单 |符合直觉的接口。 | |**数据类型丰富** |**支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。** | |方便扩展 |支持支持扩展更多数据类型,支持自定义函数和正则。 |
其实人家也是可以直接mock的,根据名字也能看出,但我们可以使用他的丰富数据类型,说白了就是随机可以动态造数据,有兴趣同学可以研究下他们的功能。
常用功能实现
别忘了npm install mockjs
这里好多示例!
// 声明变量 var Mock = require('mockjs') //生成10个星 Mock.mock({ "string|1-10": "★" }) // => { "string": "★★★★★★★★"} //随机生成邮件地址 var Random = Mock.Random Random.email() // => "n.clark@miller.io" Mock.mock('@email') // => "y.lee@lewis.org" Mock.mock( { email: '@email' } ) // => { email: "v.lewis@hall.gov" } //生成随机数字 Mock.mock({ "number|+1": 202 }) // =>{"number": 201} // 生成uid Random.guid() Mock.mock('@guid') Mock.mock('@guid()') // =>{"number": 201}"6CeEb1D9-a54F-b90b-EA28-fD271E6eAe01" // =>{"number": 201}"7f11ef3A-d270-BafE-289f-f6B4DFCbD94f" // =>{"number": 201}"eCfD7F48-d8dE-9caB-cE77-bE5c6b16Fbd4" |
挡板实战
上面也算是详细介绍了mountebank、mockjs的用法,那么接下来介绍下我的挡板实现(结合上面的第三张图)。
本次讲解的的挡板demo目录为baffle,他的目录结构如下:
|路径 |类型 |备注 | |-- |-- |-- | |baffle/logs |目录 |日志文件目录,存放业务日志和定时任务日志 | |baffle/quartz |目录 |存放定时任务,为了实现挡板的回调功能包含log.js(quartz专用)、testquartz.js | |baffle/test |目录 |存放挡板服务实现 | |baffle/utils |目录 |常用工具类,数据查询、日志输出等,包含db.js、log.js、utils.js | |baffle/imposters.ejs |文件 |启动参数 文件| |baffle/start.sh |文件 |linux启动脚本,方便服务启动 | |baffle/stop.sh |文件 |linux停止脚本,方便服务停止 | |baffle/startMac.sh |文件|mac启动脚本,里面包含停止脚本| |
挡板回调
利用定时任务实现挡板回调 log.js代码如下:
/** * 日志打印工具类 */ var log4js = require('log4js'); log4js.configure({ appenders: { file: { type: "dateFile", filename: './logs/quartz.log', //您要写入日志文件的路径 alwaysIncludePattern: true, //(默认为false) - 将模式包含在当前日志文件的名称以及备份中 //compress: true,//(默认为false) - 在滚动期间压缩备份文件(备份文件将具有.gz扩展名) pattern: "-yyyy-MM-dd.log", //(可选,默认为.yyyy-MM-dd) - 用于确定何时滚动日志的模式。格式:.yyyy-MM-dd-hh:mm:ss.log encoding: 'utf-8', //default "utf-8",文件的编码 maxLogSize: 10 //文件最大存储空间,当文件内容超过文件存储空间会自动生成一个文件xxx.log.1的序列自增长的文件 } }, categories: { default: { appenders: ['file'], level: 'info' } } }); function info(obj) { logger.info(obj); } var logger = log4js.getLogger('log_file'); module.exports = { info } |
testquartz.js文件如下:
var logger = require('./log'); const cron = require('cron'); var db = require('../utils/db'); var utils = require('../utils/utils'); /** * 定时任务 */ // https://www.npmjs.com/package/cron const CronJob = cron.CronJob; // CREATE TABLE `prduct` ( // `id` int(255) NOT NULL AUTO_INCREMENT, // `context` varchar(1000) NOT NULL, // `flag` smallint(255) NOT NULL DEFAULT '0', // PRIMARY KEY (`id`) // ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; // const prductParam = { // cronTime: '*/10 * * * * *', // cronTime: '0 */5 * * * *', onTick: function() { db.query('SELECT * from prduct where flag=0', [], function(ret){ if(ret.length>0){ for(var row in ret){ var formData=ret[row].context; var parm=[]; parm.push(ret[row].id); var callRet = utils.call(utils.testUrl.prod.url, formData, function(results) { logger.info("callRet=" + JSON.stringify(results)); // console.log(results); db.query("update prduct set flag='1' where flag=0 and id=?", parm, function(err, retu){ logger.info("产品状态变更完成:"+parm); console.log(err); }); }); } }else{ logger.info("没有对应的产品"); console.log("没有对应的产品"); } }); } }; // 产品定时推送接口 const prductJob = new CronJob(prductParam); prductJob.start(); utils工具类 db.js代码如下: var logger = require('./log'); var mysql = require('mysql'); /** * @param {Object} sql * @param {Object} arr * @param {Object} callback * 执行sql */ exports.query = function(sql, arr, callback) { var connection=getConnection(); //查 connection.query(sql,arr, function(err, result) { if (err) { logger.info('[SELECT ERROR] - ', err.message); return; } callback && callback(result); }); connection.end(); }; /** * 获取连接 */ function getConnection(){ var connection = mysql.createConnection({ host: '127.0.0.1', user: 'root', password: '123456', database: 'test_mock' }); connection.connect(); return connection; } |
log.js代码如下:
/** * 日志打印工具类 */ var log4js = require('log4js'); log4js.configure({ appenders: { file: { type: "dateFile", filename: './logs/mock.log', //您要写入日志文件的路径 alwaysIncludePattern: true, //(默认为false) - 将模式包含在当前日志文件的名称以及备份中 //compress: true,//(默认为false) - 在滚动期间压缩备份文件(备份文件将具有.gz扩展名) pattern: "-yyyy-MM-dd.log", //(可选,默认为.yyyy-MM-dd) - 用于确定何时滚动日志的模式。格式:.yyyy-MM-dd-hh:mm:ss.log encoding: 'utf-8', //default "utf-8",文件的编码 maxLogSize: 10 //文件最大存储空间,当文件内容超过文件存储空间会自动生成一个文件xxx.log.1的序列自增长的文件 } }, categories: { default: { appenders: ['file'], level: 'info' } } }); function info(obj) { logger.info(obj); } var logger = log4js.getLogger('log_file'); module.exports = { info } |
utils.js代码如下:
var logger = require('./log'); var moment = require('moment'); moment.locale('zh-cn'); function exc(process) { var request = JSON.parse(process.argv[2]), response = JSON.parse(process.argv[3]); var method = request.path.slice(1).replace(new RegExp("/","gm"), "_"); var excutor = method + '(request,response)'; logger.info(excutor); return excutor; } function httpPost(url, formData,callback) { var superagent = require('superagent'); superagent .post(url) .send(formData) .set('header_key', 'header_value') .set('Content-Type', 'application/json') .end(function(err, res) { if (err) { logger.info(err); callback && callback(""); // return ""; } else { var retData = JSON.parse(res.text) callback && callback(retData); // return retData } }) } /** * 默认的函数 * @param {Object} request * @param {Object} response */ function defaults(request, response) { console.log(JSON.stringify(response)); var reqData = request.body; var reqJson = JSON.parse(reqData); logger.info(reqJson); } /** * 回调地址 */ var testUrl = { prod: { url: '/openapi/prodList', description: '产品列表' } }; function getUuid() { // 声明变量 var Mock = require('mockjs'); return Mock.mock('@guid'); } function getDateYYYYMMDD() { return moment().format('YYYY-MM-DD'); /*现在的时间*/ } function getDateYYYYMMDDHHMMSS() { return moment().format('YYYY-MM-DD HH:mm:ss'); /*格式化时间*/ } function formatDate(val) { return moment(val).format('YYYY-MM-DD HH:mm:ss'); /*格式化时间*/ } function subDay(val) { var _today = moment(); return _today.subtract(val, 'days').format('YYYY-MM-DD'); /*前一天的时间*/ } function addDay(val) { var _today = moment(); return _today.add(val, 'days').format('YYYY-MM-DD'); /*明天天的时间*/ } /** * @param {Object} url * @param {Object} formdata * 调用其他项目的接口 */ function call(url, formdata, callback) { logger.info(url); logger.info(formdata); var postUrl = "http://localhost:8082" + url; logger.info("postUrl=" + postUrl); httpPost(postUrl, formdata, callback); } module.exports = { exc, httpPost, defaults, testUrl, getUuid, getDateYYYYMMDD, getDateYYYYMMDDHHMMSS, formatDate, subDay, addDay, call } |
挡板实现
存放挡板服务实现,请参照上面讲的案例或到github拉取。
imposters.ejs配置文件
{ "imposters": [ <% include ./test/predicates_inject.json %>, <% include ./test/predicates.json %>, <% include ./test/proxy.json %>, <% include ./test/mockjs.json %>, <% include ./test/responses_inject.json %>, <% include ./test/_behaviors.json %>, <% include ./test/shellTransform.json %> ] } |
start.sh linux启动脚本
利用node的npm start也是可以的,这里自己写的目的是把定时任务也包含在里面。
#停止服务 mb stop #---------------------------定时任务 start----------------------------- #先停止进程 ps -ef|grep testquartz |grep -v grep|cut -c 9-15|xargs kill -9 #再启动进程 node quartz/testquartz.js & #---------------------------定时任务 end----------------------------- #启动服务 mb start --configfile imposters.ejs --allowInjection >test.out 2>&1 & |
stop.sh linux停止脚本
mb stop #---------------------------定时任务 start----------------------------- ps -ef|grep testquartz |grep -v grep|cut -c 9-15|xargs kill -9 #---------------------------定时任务 end----------------------------- netstat -antp|grep 2525 |grep -v grep|cut -c 80-85|xargs kill -9 |
DEMO代码
完整的代码demo已经传到Git,在这里:https://github.com/dcs678/baffle。
总结
本文主要是先从当前的微服务架构说起,调用外围系统给开发、测试、演示的痛点,给出挡板的架构规划,然后利用mountebank的强大功能,通过脚本+实战的方式一步步详细说明。
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。