从0搭建一个内部mock工具(支持sql转换,自带增删改查)
一. 前言
作为程序员的我们,通常避免不了对需求任务的追赶。
而作为切图仔的我们,作为开发最后提测前最后一环的我们,对无法准时提测,有时候可能存在一些委屈,相信肯定遇到接口不及时而导致页面进度延期的情况。
此外,在日常迭代的规律性的迭代,前端很容易出现一种形象,就是出现"区间性"忙碌。你说非常忙,把工作日平摊下来,实际上也没多少产出,但就是在最后时刻特别忙碌。给人的一种感觉就是,联调前,没事做;联调后,事太多。
如果,有一款能生成虚拟的联调数据,是能分担任务的比重的。而前端mock就是在这种背景中产生。
二. mock的意义
说完mock存在的背景,这里再分析一下mock的意义。
1) 草图
如何解决前端任务不聚焦的问题,笔者这里简单梳理了一个草稿图:
2) 分析
上图已经列出,我们面对的问题:
- 1.当前只有业务逻辑(表结构),无法生成对应接口
- 2.模拟数据成本高,无法快速生成
- 3.有数据了,也无法进行真实增删改查
根据如上,笔者认为一个好用的mock工具,我觉得应该保证下边的功能:
- 1.能自定义我们的数据格式,返回对应的数据结构以及相关的字段等。
- 2.能快速自定义我们的数据源。
- 3.能快速生成增删改查的接口,模拟真实数据的运转,否则就失去了意义
- 4.如果能根据表结构直接生成,那就更完美
当然,博客上,也有好多博主有自己的见解,有些东西我觉得可以非必要追求:
- 1.过于追求数据结构。假设没有技术评审,还未确认表结构,我们设计一个差不多就可以。这玩意再怎么优秀,都只是“模拟”数据而已,你永远猜不中后端最终给你的格式。
- 2.实现复杂的逻辑。越复杂的逻辑,也表示花费的时间越多。只要接口有了,也意味着这玩意作废。所以,不适宜投入太高的成本。
这里也有几个前置条件:
- BFF接口设计规范,我们知道对应的接口设计规范。
- 对应业务的表结构
3) 结论
综上分析,我们需要一款,配置"简单"且能"快速"生成一个增删改查的mock工具。而"简单"与"快速",是mock的核心。
三. 市场的常用方案
此时我们已经有了需求,再来看看市场的解决方案。一起看看,市场是怎么设计这些mock工具的,或者是有什么解决方案。
1)手动虚拟数据
该方案,直接在代码中写死 Mock 数据,或者请求本地的 JSON 文件。相信也是日常大家使用最多的方案。
优势:
- 上手快,无需学习成本。
劣势:
- 做数据比较麻烦。
- 无法自动生成增删改查等。
- 会使项目太多冗余数据,解藕也较难。
2)接口管理工具
这里以swagger为例,相信后端是java的切图仔都看过这玩意。
优势:
- 自带接口规范。配置功能强大。
- 根据后端接口生成。
劣势:
- 资源问题:毕竟是后端项目,启动一个后端项目也麻烦。还耗后端资源。
- 人员问题:让后端帮忙处理,后端还不如直接加紧赶出接口给出来。
- 配置问题:强大也意外着,学习成本高。熟悉一些配置相关的成本也不低。
3)node服务器
结合上边,如果说使用一个java后端比较麻烦,那么启动一个Node服务器的话呢?
常见的有我们的koa,或者是express框架等等。
优势:
- 前端学习成本相对较低,毕竟都是js
劣势:
- 增删改查,还是需要手动写
4)MOCKJS
优势:
可随机生成所需数据,可模拟对数据的增删改查
劣势:
- 可以实现增删改查, 数据源的确比较灵活,支持随机数,正则等,但是模拟数据麻烦一些。
5)抓包工具
代表作Fiddler
优势:
脱离程序本身,可以直接映射,修改数据源。
劣势:
调试相对繁琐。定位并不是mock开发阶段,更适合偶尔模拟数据定位问题。
四.手动搭建自己的mock
介绍自己的Cb mock:
1)功能介绍
- 能快速自定义我们的数据格式,能快速生成增删改查接口
- 支持三种模式,自定义模式,json模式,sql模式
- 支持接口校验,如必填,长度等校验
- 能自定义返回接口格式,统一数据格式规范,项目也支持自定义规范
- 与项目本身脱离解耦,可快速替换真实链接
- 支持生成api文档说明
2)核心技术栈/源码
-
- koa
启动koa服务,作为服务提供者,代码如下:
const Koa = require('koa')
const app = new Koa();
app.use(json())
app.use(logger())
app.use(koaBody());
app.use(bodyParser({
enableTypes: ['json', 'form', 'text'],
multipart: true
}));
- 2. mockjs
返回mock数据:
const { mock } = require("mockjs");
ctx.body = Result.info(mock(Object.assign(defaultResult, result)));
- 3.commander指令
如启动commander指令:
const program = require('commander')
program
.command("startMock")
.description("启动mock服务")
.action((appName, options) => {
startMock()
})
- 4. 正则sql转换
利用正则切割sql:
const nameMatch = line.match(' `(.*)`');
const maxLengthMatch = line.match(' varchar\\((.*)\\) ');
const defaultMatch = line.match(' DEFAULT \'(.*)\' ');
const commentMatch = line.match(' COMMENT \'(.*)\',');
const typePattern = / (?:\w+)(.*?) /;
const typeMatch = line.match(typePattern, "$0");
const requiredReg = /NOT NULL/.test(line);
const key = nameMatch[1];
const maxLength = maxLengthMatch && maxLengthMatch.length > 0 ? Number(maxLengthMatch[1]) : undefined;
const required = !requiredReg;
const defaultValue = defaultMatch && defaultMatch.length > 0 ? defaultMatch[1] : undefined;
const comment = commentMatch && commentMatch.length > 0 ? commentMatch[1] : undefined;
const typeDb = typeMatch && typeMatch.length > 0 ? typeMatch[0] : [];
- 5. fs生成文档
利用fs生成文档文件:
// 生成文档
const generateDoc = (table, port) => {
let str = '';
Object.keys(table).forEach(key => {
str += `## ${key}模块
| 参数名称 | 参数说明 | 数据类型 | 长度(字节) | 是否必填 | 默认值 |
| ------- | ------- | ------ | ----- | ------- | --------- |
`
const { column } = table[key]
column.forEach(item => {
const name = item.key;
const type = item.type;
const comment = item.comment ? item.comment : '-';
const defaultValue = item.default ? item.default : '-';
const isRequired = item.required ? "是" : "否"
const maxLength = item.maxLength ? item.maxLength : "-"
str += `| ${name} | ${comment} | ${type} | ${maxLength} | ${isRequired} | ${defaultValue} |\n`
})
// 获取接口名称
const apiList = getApiList(key);
for (let i = 0; i < apiList.length; i++) {
const { url, name, type } = apiList[i]
str += `### http:/${url}
接口说明:${name}
请求类型: ${type}
\n`
}
})
五.使用配置
1)安装本地
git clone https://github.com/zhuangweizhan/cb-cli
npm install
npm link
2)配置mock.config.js
module.exports = {
common: {//支持公用配置
port: 8000,
timeout: 0,
rate: 1,
},
sqlDataSource: [//支持sql数据源
`
CREATE TABLE \`jsh_depot\` (
\`id\` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
\`name\` varchar(20) DEFAULT NULL COMMENT '仓库名称',
\`address\` varchar(50) DEFAULT NULL COMMENT '仓库地址',
\`warehousing\` decimal(24,6) DEFAULT NULL COMMENT '仓储费',
\`truckage\` decimal(24,6) DEFAULT NULL COMMENT '搬运费',
\`type\` int DEFAULT NULL COMMENT '类型',
\`sort\` varchar(10) DEFAULT NULL COMMENT '排序',
\`remark\` varchar(100) DEFAULT NULL COMMENT '描述',
\`principal\` bigint DEFAULT NULL COMMENT '负责人',
\`tenant_id\` bigint DEFAULT NULL COMMENT '租户id',
\`delete_Flag\` varchar(1) DEFAULT '0' COMMENT '删除标记,0未删除,1删除',
\`is_default\` bit(1) DEFAULT NULL COMMENT '是否默认',
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb3 COMMENT='仓库表';
insert into \`jsh_depot\`(\`id\`,\`name\`,\`address\`,\`warehousing\`,\`truckage\`,\`type\`,\`sort\`,\`remark\`,\`principal\`,\`tenant_id\`,\`delete_Flag\`,\`is_default\`) values (14,'仓库1','dizhi','12.000000','12.000000',0,'1','描述',131,63,'0','');
insert into \`jsh_depot\`(\`id\`,\`name\`,\`address\`,\`warehousing\`,\`truckage\`,\`type\`,\`sort\`,\`remark\`,\`principal\`,\`tenant_id\`,\`delete_Flag\`,\`is_default\`) values (15,'仓库2','地址100','555.000000','666.000000',0,'2','dfdf',131,63,'0','\0');
insert into \`jsh_depot\`(\`id\`,\`name\`,\`address\`,\`warehousing\`,\`truckage\`,\`type\`,\`sort\`,\`remark\`,\`principal\`,\`tenant_id\`,\`delete_Flag\`,\`is_default\`) values (17,'仓库3','123123','123.000000','123.000000',0,'3','123',131,63,'0','\0');
CREATE TABLE \`jsh_depot22\` (
\`id\` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
\`name\` varchar(20) DEFAULT NULL COMMENT '仓库名称',
\`address\` varchar(50) DEFAULT NULL COMMENT '仓库地址',
\`warehousing\` decimal(24,6) DEFAULT NULL COMMENT '仓储费',
\`truckage\` decimal(24,6) DEFAULT NULL COMMENT '搬运费',
\`type\` int DEFAULT NULL COMMENT '类型',
\`sort\` varchar(10) DEFAULT NULL COMMENT '排序',
\`remark\` varchar(100) DEFAULT NULL COMMENT '描述',
\`principal\` bigint DEFAULT NULL COMMENT '负责人',
\`tenant_id\` bigint DEFAULT NULL COMMENT '租户id',
\`delete_Flag\` varchar(1) DEFAULT '0' COMMENT '删除标记,0未删除,1删除',
\`is_default\` bit(1) DEFAULT NULL COMMENT '是否默认',
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb3 COMMENT='仓库表';
insert into \`jsh_depot\`(\`id\`,\`name\`,\`address\`,\`warehousing\`,\`truckage\`,\`type\`,\`sort\`,\`remark\`,\`principal\`,\`tenant_id\`,\`delete_Flag\`,\`is_default\`) values (14,'仓库1','dizhi','12.000000','12.000000',0,'1','描述',131,63,'0','');
insert into \`jsh_depot\`(\`id\`,\`name\`,\`address\`,\`warehousing\`,\`truckage\`,\`type\`,\`sort\`,\`remark\`,\`principal\`,\`tenant_id\`,\`delete_Flag\`,\`is_default\`) values (15,'仓库2','地址100','555.000000','666.000000',0,'2','dfdf',131,63,'0','\0');
insert into \`jsh_depot\`(\`id\`,\`name\`,\`address\`,\`warehousing\`,\`truckage\`,\`type\`,\`sort\`,\`remark\`,\`principal\`,\`tenant_id\`,\`delete_Flag\`,\`is_default\`) values (17,'仓库3','123123','123.000000','123.000000',0,'3','123',131,63,'0','\0');
`
],
jsonDataSource: [//支持json数据源
{
account_detail: {
column: [
"id",
{ key: 'username', required: true },
"auth_key",
"password_hash",
"password_reset_token",
"email",
"created_at",
],
dataSource: [
{
"id": 13,
"email": "368938"
},
[14, 45457]
],
},
},
],
customDataSource: [[//支持自定义数据源
{
url: "/login",
returnFn: (req) => {
const { name, password } = req.query;
if (name === 'admin' || password === 'a123456') {
return { status: 1, msg: "登录成功" }
}
return { status: 0, msg: "账号或密码错误" }
}
},
{
url: "/addUser",
type: "post",
returnFn: (req) => {
const { name } = req.request.body;
if (name !== '' && name !== undefined) {
return { status: 1, msg: "新增账号成功" }
}
return { status: 0, msg: "请输入用户名" }
}
},
]
};
3)启动服务
cb startMock
4)启动结果
会生成mock.md接口文档说明:
五.源码
六.结语
时间仓促,一些功能未完善。分享一下未来计划:
-
- 支持sql的left join生成
-
- 完成文档的生成
-
- 欢迎反馈bug
转载自:https://juejin.cn/post/7211147716893294653