写一个搭建本地mock服务器的命令行工具
之前一直使用mockjs模拟接口返回数据,由于其内部是改写XHR实现,因此存在一些局限性,比如无法拦截JSONP请求、无法直接在小程序中使用,最近恰好有点时间,因此决定写一个工具解决这些问题。
整个包已发布在npm上,
- @shymean/koa-mock,koa中间件,返回对应的mock数据
- @shymean/mock-server,快速启动一个本地mock数据服务器
其中koa-mock
是整个项目的核心包,根据请求url和method,自动注入路由并返回对应的mock数据;而mock-server
则是一个命令行工具,根据指定的mock模板文件,在本地快速启动一个服务器。
相关依赖
- mockjs文档,模板文档使用mockjs语法编写
- Node.js 命令行程序开发教程
mockjs使用及实现
统一管理mock模板
在开发过程中,一般后两种形式的调用比较多,且我会把所有的mock请求通过一个_mock.js
文件进行管理,这样做有几个好处
可以很方便地切换mock环境
在webpack中,通过环境参数判断开发环境,并引入指定的mock文件即可
entry: {
index: isDev ? [
getPath("./js/_mock.js"),
getPath("./index.js")
] : getPath("./index.js")
},
这样避免侵入逻辑代码,后续还要将对应的mock代码进行移除
let apiModel = {
submit(){
return Mock.mock({
// mock数据
code: 1,
msg: '',
data: {}
})
}
}
将模板文件当做接口文档
接口文档可能会更新不及时,而mock模板应用在最新的开发环境中。 相对于文档而言,mock的字段模板对开发人员更加友好,因此与其把mock代码散落在控制器和接口中,不如把mock模板整合在一起,作为接口文档进行管理
原理实现
mockjs的Mock.mock
接口,支持下面几种形式的调用
Mock.mock( template )
Mock.mock( rurl, template )
Mock.mock( rurl, rtype, template )
在浏览器版本中,如果指定了rurl,则会拦截对应的请求,并直接返回模板数据,文档上介绍了其实现原理是通过模拟XHR对象来实现的
从 1.0 开始,Mock.js 通过覆盖和模拟原生 XMLHttpRequest 的行为来拦截 Ajax 请求
这种实现有一些限制,比如JSONP形式请求、小程序内的网络请求等场景,则使用rurl形式进行拦截的操作不太靠谱。
koa-mock
中间件就是为了解决这个问题,可以在koa中拦截对应的url并返回模板数据。甚至可以在前端和后台环境中共用一套mock模板。
本地mock服务器
koa-mock中间件
与浏览器环境不同的是,在mock服务器中,需要根据rurl,劫持对应的路由,并直接返回数据。
因此可以在Mock.mock方法调用时,收集url对应的mock模板,然后在中间件中根据ctx.url
,获取该url的模板。
因此现在问题就是如何收集url对应的mock配置了,我的处理方式是改写改方法,在内部通过一个对象保存依赖
Mock._urls = {}
Mock.mock = function () {
let args = arguments
// let config = getConfigFromArgs(args)
// Mock._urls[url].push(config)
return mock.apply(Mock, args)
}
这样在中间件中,只需要根据url和Mock._urls,获取其对应的配置即可
let middleware = function () {
return async function (ctx, next) {
let url = ctx.url,
allConfig = util.getUrlAllConfig(Mock._urls, url),
data
// 如果对应的url有模板配置,则拦截请求并返回数据
if (Array.isArray(allConfig)) {
let template = util.getMockTemplate(allConfig, ctx.method)
data = template ? Mock.mock(template) : null
}
if (data) {
ctx.body = data
} else {
await next()
}
}
}
mock-server
上面存在的一个问题是,在_mock.js
等模板中引入的Mock对象,和在中间件中引入的Mock对象,必须是同一个才行,否则无法完成依赖。
由于我们的预期是将_mock.js
模板文件解耦,服务端和浏览器端都可以公用同一套模板,很明显浏览器版本中的Mock和中间件中的Mock对象不一定是同一个,除非采用同构渲染的项目。
我的解决办法是使用eval
读取模板文件,然后将中间件中使用的Mock对象注入到模板文件上,编写模板文件时不用考虑其中的mock接口,到底是哪个对象提供的。
const mockMiddleware = require("@shymean/koa-mock")
let start = (file, port) => {
fs.readFile(file, 'utf-8', function (err, tpl) {
{
// 注入相同的Mock对象
let Mock = mockMiddleware.Mock
let res = eval(tpl)
}
app.use(mockMiddleware());
app.listen(port);
console.log(`mock server listen at ${port}`);
})
}
这样做的一个好处是,我们可以读取任意磁盘位置的模板文件,而不用担心mock-server莫生效。
同样也存在缺点,由于只有启动服务时会读取模板文件,对于模板文件的更新需要重启整个服务器,需要单独进行处理。
NodeJS开发命令行工具
上面理清了整个工具的核心实现,现在需要把他们封装成一个命令行工具,预期目标是通过指令,直接启动一个指定模板文件的mock服务器
mock -p 9999 -f ./_mock.js
从命令可以看出,我们需要处理两个地方,
- 终端中关联mock命令到指定脚本,添加环境变量
- 解析命令行参数
不同系统的终端命令
windows的path环境变量
参考:
执行某个指令,实际上是运行某个应用程序(XXX.exe
),在应用程序的安装目录下,可以通过应用程序名直接启动,但是在其他目录下,只能通过完整路径进行启动。难道每次运行命令都要这么麻烦?
通过设置path环境变量可以解决这个问题,path
环境变量中存放的值,就是一连串的路径。
系统执行用户命令时,若用户未给出程序所在的完整路径,
- 首先在当前目录下寻找相应的可执行文件、批处理文件(另外一种可以执行的文件)等。
- 若找不到,再依次在PATH保存的这些路径中寻找相应的可执行的程序文件。系统就以第一次找到的为准;
- 若搜寻完PATH保存的所有路径都未找到,则提示命令不存在
可以通过set path
指令来设置path环境变量,这种方式只对当前命令窗口有效
set path=%path%;D:\Java\jdk1.6.0_24\bin
如果需要持久设置,可以在windows操作系统中可以通过我的电脑-〉系统属性-〉高级系统设置->环境变量,来查看和设置系统的path
环境变量。
在windows上可以通过dokey设置别名,详情可以参考这里
linux 参考
- https://blog.csdn.net/dlutbrucezhang/article/details/8811456
- https://blog.csdn.net/love666666shen/article/details/78294633
shell环境依赖于多个文件的设置。当shell被调用时,它从两个初始文件读取命令。
- /etc/profile包含了系统变量,它由系统管理员维护,由系统管理员设置本地系统变量和特殊命令。
- 普通用户的启动信息文件($HOME/.bash_project)由各用户自己维护,该文件可以被修改以实现任何特定的系统初始化。
在linux下,$PATH
环境变量决定了shell将到哪些目录中寻找命令或程序,PATH的值是一系列目录,当运行一个程序时,Linux在这些目录下进行搜寻编译链接。
可以通过export
指令设置path变量,但是只在当前终端有效
echo $PATH
export PATH=/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH
如果需要持久设置环境变量,一般做法是修改上面提到的初始文件
vim /etc/profile
# 在文档最后添加新的path
export PATH="/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH"
# 保存退出,重新读取初始文件
source /etc/profile
可以通过alias属性为指令设置别名
vim /etc/profile
alias ls='ls --color=auto'
关联终端命令
上面将shell脚本手动添加到环境变量中,然后使用mock
指令的方式显得比较繁琐,npm提供了注册环境变量的快捷方法:向package.json中添加bin参数
"bin": {
"mock": "./bin/index.js"
}
然后执行npm link
,就可以快速使用mock指令了。 其中,指定以node运行对应的shell脚本,脚本头部需添加注解
#!/usr/bin/env node
require('../index.js')
解析命令行参数
在nodejs中可以通过process.argv
来获取命令行参数,该属性返回的是一个包含参数的数组,具体的含义需要我们手动去实现
通过上面的命令行参数格式不难理解,
- -p表示参数名,后空格接的9999表示参数值
- -f同理
这里使用的工具是yargs,该插件为我们封装了命令行参数
let yargs = require('yargs')
// 对单个参数进行配置
yargs.option('p', {
alias : 'port',
demand: false,
default: 7654,
describe: 'server port',
type: 'number'
})
yargs.option('f', {
alias : 'file',
demand: true,
default: './_mock.js',
describe: 'mock template',
type: 'number'
})
let argv = yargs.argv
let {file, port} = argv
这样就可以很方便地进行业务处理了。
npm本地包及发布
安装本地包
由于整个项目拆分成了koa-mock
中间件和mock-server
两个工具,因此在开发时需要安装本地包,整个过程可分为下面几步
- 首先打包对应的文件压缩包,在文件夹中使用
npm pack
将整个文件夹打包,会显示生成name-0.0.1.tgz
,这个名称和版本号是在package.json中定义。 - 然后切换到需要安装该包的项目目录下,使用
npm install path/name-0.0.1.tgz
安装对应的模块压缩包。- 需要注意不能在包文件夹中直接使用
npm install name-0.0.1.tgz
,会出现Refusing to install name as a dependency of itself
的错误信息 - 可以在整个项目文件夹的
node_modules
文件夹中发现我们的模块包了。
- 需要注意不能在包文件夹中直接使用
- 最后在项目的文件中就可以直接使用使用该包导出的接口了
修改本地工具包后,需要重新执行上面的操作,更新本地依赖。
发布包
发布npm包到npm仓库,需要注意下面几个问题
将镜像切换回npm,推荐nrm工具
因为本地的npm镜像一般会选择国内的淘宝镜像,需要将npm镜像切回到官网
npm set registry https://registry.npmjs.org/
手动设置比较麻烦,这里推荐工具nrm,可以方便地在不同的npm进行安装
npm i nrm -g
# 查看可使用的镜像
nrm ls
# 使用对应的镜像名
nrm use npm
登录npm账户
发包需要先登录账号,可以去~/.npmrc
查看当前登录用户
npm adduser
npm login
# 切换到包根目录发布
npm publish
选择合适的包名
注意包名和版本号,是否已经存在了,目前npm的包名为了防止“误植”攻击,会自动检测相近的包名,参考这篇文章
如果发现返回403错误解决办法是为包名添加命名空间
"name": "@shymean/koa-mock",
然后修改发布权限
npm publish --access=public
正式发布 如果上面设置都没问题了,就可以使用
npm publish
进行发布了,每次重新发布需要更新版本号,以v1.0.1
形式保存在package.json中,需要注意版本号的设置,会影响npm install
的更新
- 第一种caret(箭头)表示: ^2.0.2能帮你下载最新的2.x.x的包,不能下载1.x.x的包。比如最新的是2.1.0, 就是直接下载2.1.0。
- 二种tilde(波浪线)表示: ~2.0.2能帮你下载2.0.x的最新包,不能下载2.1.x的包,比 ^ 要更加谨慎一些。比如最新的包如果是2.0.3, 就会下载,而如果是2.1.3就不会下载。
- 第三种没有任何符号就表示严格匹配。
小结
这篇文章内容有点杂
- 前面主要整理在通过mock模板管理mock数据的好处,以及在本地实现mock-server的思路
- 后面主要整理了如何开发命令行工具,以及如何进行npm本地包的安装和发布
其中一些知识点,是很早之前整理在有道云笔记上的,有时候经常要去查阅,比较麻烦,因此一并整理在这里。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。