WEBSOCKET MOCK SERVER 实现方案与应用
2020/06
目录概览
- 一、为什么需要 MOCK
- 二、为什么要自研发
- 三、Websocket Mock 方案设计
- 四、典型用法示例
- FAQ
一、为什么需要 MOCK
一、为什么需要 MOCK
- 约定好接口,前后端协同并发
- 避免开发环境问题长时间阻塞前端开发调试
- 测试场景还原
- 接口更新、接口文档维护、测试用例等(复杂需求,如 RAP2)
- More...
社区中常见的 MOCK 实现
- 在业务逻辑中临时写假数据
- 前端 js 库拦截请求(mock.js)
- 利用 Charles、 Fiddler 等代理工具拦截请求
- 开发简单的 MOCK SERVER
- 功能丰富的 MOCK SERVER
- 功能:前端开发、文档管理、测试用例等
- 多角色:产品、后端、前端、测试等
- More...
开源社区成熟方案:
二、为什么要自研发
二、为什么要自研发
纯前端开发的MOCK基本诉求:
- 不入侵业务逻辑
- 简易、学习成本低
- 引入成本低
- 弱关注后端接口、文档等状态
- More...
二、为什么要自研发
- 简单方案不满足需求
- 复杂方案引入成本、学习高
- 流行的开源 Mock Server 方案学习成本高
- 没有适合 websocket 的开源 Mock 方案
- 定制更符合前端团队当前研发需求的轻量级方案
- More
三、Websocket Mock 方案设计与开发模式
- 基本架构设计
- 主要功能特色
- Mock 的基本策略
Websocket Mock - 基本架构设计
Proxy Server
实现代理功能
simple-mock (独立插件)
实现 Mcok 具体逻辑方案
Mock Server
手动发送 Mock 信息
Websocket Mock - 基本架构设计
graph TD B["fa:fa-desktop 客户端"] B-->C[fa:fa-server Proxy Server] C-->B C-->D(fa:fa-user Mock Client Server); C-->E(fa:fa-server Remote Websocket Server); C-->F[fa:fa-database 本地磁盘SIMPLE-MOCK] F-->C D-->C E-->C
Websocket Mock - 主要功能特色
学习成本低
commonJS 编写 Mock 规则
API 录制
落地后端返回数据到本地文件
不同的目录优先级
customdata > mockdata > customdata/autosave
部分开启或关闭 MOCK
[http完整/websocket部分支持]
修改立即生效
配置文件、自定义 mock 文件
More...
Websocket Mock - 针对 websocket 消息 mock 的基本策略
- 以自动保存至
autosave
目录内的数据为基础 - 借助
simple-mock
以一问一答、一问多答的模式实现 websocket 服务 mock 逻辑:- 根据
funcid
、topic
等主要参数落地文件名,并实现请求与应答的对应关系【TYT】 - 通过
config.customSaveFileName(req, res, filename, type)
参数方法定义客户端数据请求、服务端数据返回的 mock 文件名规则,由此实现应与答的对应 - 通过
config.handlerBeforeMockSend(content, reqParams)
参数方法对通用信息进行调整。比如实时时间戳、与请求对应的reqmsgid
替换等
- 根据
- 对于
登陆流程、信息预加载
等,定义公共规则,放置于mock/mockdata
目录中(会提交至git仓库) - 对于
公共逻辑的广播推送式消息
,可以定义公共规则、定义定时器等方式实现(注意定时器的管理) - 对于
调试需求的广播推送式消息
,建议在启动连接后,通过开启本地MOCK CLIENT SERVER
页面手动编辑并模拟发送
四、典型应用示例
WEBSOCKET 的数据请求与返回示例
请求参数
{
topic: 'gfpb.json',
data: {
method: 'asyncSend',
funcid: 103223,
jgparams: {
g_reqmsgid: '98147387190c41ffb7b4e14ffb770971',
g_menuid: 99000002,
g_funcid: 103223,
g_userid: 2008****,
g_userpwd: '2mS0eQ************+XoA==',
g_sessionid: '9A6************2847DF84F1FED1409',
g_gfstr: '0D6BE4F************806158F28A6A0',
g_clientversion: 'GFETX64-V3.1.1',
g_third_menuid: 99000016,
},
jparams: { config_type: 1 },
},
};
数据返回
{
data: {
data: {
errmsg: '查询系统日期表成功', errno: 0,
funcid: 103223,
reqmsgid: '98147387190c41ffb7b4e14ffb770971',
data: [
{
colnum: 15,
rownum: 1,
keys: [],
rows: [],
},
],
},
topic: 'ans_kams',
},
topic: 'gfpb.pub',
}
示例 - 使用 funcid
值作为默认文件名
对于 websocket 类型的 mock,主要通过对请求参数与数据内容的字段值进行对应,customSaveFileName
参数是实现这种对应关系的核心。
// simple-mock-config.ts
module.exports = {
isEnableMock: false,
customSaveFileName: (reqParams, resData, filename, type) => {
console.log(type, filename);
if (filename) return filename;
if (reqParams.topic) filename = reqParams.topic;
if (type === 'save' && resData) {
filename = resData.funcid;
} else if (type === 'mock' && reqParams) {
filename = reqParams.funcid;
}
return filename;
}
} as SimpleMockConfig;
示例 - 使用 handlerBeforeMockSend
重置通用数据值
// simple-mock-config.ts
module.exports = {
isEnableMock: true,
handlerBeforeMockSend: (content, reqParams, wsClient) => {
if (reqParams.reqmsgid && content.data.reqmsgid) {
content.data.reqmsgid = reqParams.reqmsgid;
}
if (content && content.data && content.data.data) {
if (content.data.data.totalCounts) {
content.data.data.currSno = 0;
content.data.data.totalCounts = 1;
}
}
return content;
},
} as SimpleMockConfig;
通过灵活定义 handlerBeforeMockSend
参数,可以在数据返回前统一处理一些参数值,如模拟当前时间、通用性规则等。
示例 - 过滤部分请求的响应
// simple-mock-config.ts
module.exports = {
isEnableMock: true,
/** 自动保存 API 返回内容时,对内容进行过滤,返回 true 则忽略 */
fnAutosaveFilter: (content) => {
// 示例: 不保存 content.data.length = 0 的数据
if (content && Array.isArray(content.data) && !content.data.length) {
return false;
}
return true;
},
/** 开启 mock 时,过滤一些不需要 mock 的 API */
disableMockFilter: (filename, req) => {
// 示例:按关键字过滤部分请求不 mock
const filterKeyList = ['30001', '30002'];
return filterKeyList.some(key => String(filename).includes(key));
},
} as SimpleMockConfig;
示例 - 模拟多次应答
const { dataFormat } = require('../helper/utils-date');
module.exports = (req, ws) => {
setTimeout(() => {
loadingFinished.data.mtime = dataFormat('yyyyMMddhhmmssS', new Date());
// 模拟通知数据已经发送完了
ws.broadcast(loadingFinished); // ws.send(loadingFinished);
}, 1000);
// 响应本次请求
return result;
};
// 登陆数据预加载:通知数据全部加载完成
const loadingFinished = {
data: {
data: [{ remark: 'loading.finished' }],
mid: 1979, mtype: '1', subid: 1, mtime: '20200520174135',
topic: 'loading.finished',
},
topic: 'gfpb.pub',
};
const result = {...};
示例 - 模拟多次应答
// customdata/44050.js
module.exports = (req, ws) => {
const jparams = req.data && req.data.jparams;
if (jparams && jparams.market) {
const path = require('path'); const fs = require('fs'); const funcid = jparams.funcid;
let market = jparams.market;
if (market === '1' && jparams.filterstktypes) market += '_filterstktypes';
const filePath = path.resolve(__dirname, `../customdata/autosave/proto_base_info_${funcid}_${market}.js`);
if (fs.existsSync(filePath)) setTimeout(() => ws.broadcast(require(filePath)), 2000);
}
return result;
};
const result = {
data: {
data: {
errmsg: '查询证券信息成功!', funcid: 44050, reqmsgid: '2da60eda55594dc9914a780753955a71',
},
topic: 'ans_kams',
},
topic: 'gfpb.pub',
};
示例 - 使用 MOCK CLIENT 发送消息
示例 - 使用 MOCK CLIENT 发送消息
其他注意事项或经验
- 灵活定义
config.customSaveFileName
逻辑,落地更多差异化的文件数据 - 灵活定义
config.handlerBeforeMockSend
处理或修正更多通用性的数据 slient: true
关闭日志输出 (大量日志打印cmd偶现卡住情况)- 支持 TypeScript 方式编写配置文件,但建议使用 js 方式(修改后编译内存溢出概率高)
- More...
FAQ
* * *