mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-05 19:41:57 +08:00
feat: 新增mock插件
This commit is contained in:
parent
c29f705bd8
commit
5d2271bcd3
@ -41,6 +41,9 @@
|
|||||||
"vue-loader": "^16.1.2",
|
"vue-loader": "^16.1.2",
|
||||||
"webpack-bundle-analyzer": "4.3.0",
|
"webpack-bundle-analyzer": "4.3.0",
|
||||||
"cli-highlight": "^2.1.4",
|
"cli-highlight": "^2.1.4",
|
||||||
"webpack-chain": "6.5.1"
|
"webpack-chain": "6.5.1",
|
||||||
|
"body-parser": "^1.19.0",
|
||||||
|
"cookie-parser": "^1.4.5",
|
||||||
|
"mockjs": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,10 @@ export default function () {
|
|||||||
require.resolve('./plugins/commands/dev'),
|
require.resolve('./plugins/commands/dev'),
|
||||||
require.resolve('./plugins/commands/help'),
|
require.resolve('./plugins/commands/help'),
|
||||||
require.resolve('./plugins/commands/info'),
|
require.resolve('./plugins/commands/info'),
|
||||||
require.resolve('./plugins/commands/webpack')
|
require.resolve('./plugins/commands/webpack'),
|
||||||
|
|
||||||
|
// mock
|
||||||
|
require.resolve('./plugins/commands/mock')
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
174
packages/fes-preset-built-in/src/plugins/commands/mock/index.js
Normal file
174
packages/fes-preset-built-in/src/plugins/commands/mock/index.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { existsSync, readFileSync } from 'fs';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { chokidar, lodash } from '@umijs/utils';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
|
import Mock from 'mockjs';
|
||||||
|
|
||||||
|
|
||||||
|
export default (api) => {
|
||||||
|
let mockFlag = false; // mock 开关flag
|
||||||
|
let mockPrefix = '/'; // mock 过滤前缀
|
||||||
|
let mockFile = ''; // mock 文件
|
||||||
|
let loadMock = ''; // mock 对象
|
||||||
|
|
||||||
|
api.describe({
|
||||||
|
key: 'mock',
|
||||||
|
config: {
|
||||||
|
schema(joi) {
|
||||||
|
return joi.alternatives(joi.boolean(), joi.object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const createMock = () => {
|
||||||
|
// 判断是否为 Object,仅 {}
|
||||||
|
function isObject(value) {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]';
|
||||||
|
}
|
||||||
|
// 对 array、object 遍历处理
|
||||||
|
function traversalHandler(val, callback) {
|
||||||
|
if (lodash.isArray(val)) {
|
||||||
|
val.forEach(callback);
|
||||||
|
}
|
||||||
|
if (isObject(val)) {
|
||||||
|
Object.keys(val).forEach((key) => { callback(val[key], key); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 根据参数个数获取配置
|
||||||
|
function getOption(arg) {
|
||||||
|
const len = arg.length;
|
||||||
|
// 默认配置
|
||||||
|
const option = {
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
},
|
||||||
|
statusCode: 200,
|
||||||
|
cookies: [],
|
||||||
|
timeout: 0
|
||||||
|
};
|
||||||
|
if (len === 0) return option;
|
||||||
|
if (len === 1) {
|
||||||
|
const newOption = arg[0];
|
||||||
|
if (isObject(newOption)) {
|
||||||
|
traversalHandler(newOption, (value, key) => {
|
||||||
|
if (key === 'headers') {
|
||||||
|
traversalHandler(newOption.headers, (headervalue, headerkey) => {
|
||||||
|
option.headers[headerkey] = newOption.headers[headerkey];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
option[key] = newOption[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
option.url = arg[0];
|
||||||
|
option.result = arg[1];
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
// 把基于 cgiMockfile 的相对绝对转成绝对路径
|
||||||
|
function parsePath(value) {
|
||||||
|
const PROJECT_DIR = process.env.PWD || process.cwd();
|
||||||
|
return resolve(PROJECT_DIR, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestList = [];
|
||||||
|
const cgiMock = (...arg) => {
|
||||||
|
const option = getOption(arg);
|
||||||
|
if (!option.url || !option.result) return;
|
||||||
|
requestList.push(option);
|
||||||
|
};
|
||||||
|
cgiMock.file = function (file) {
|
||||||
|
return readFileSync(parsePath(file));
|
||||||
|
};
|
||||||
|
|
||||||
|
// mock是否打开
|
||||||
|
mockFlag = isObject(api.config.mock) ? true : api.config.mock;
|
||||||
|
if (!mockFlag) return;
|
||||||
|
// mock打开情况下,配置的过滤前缀
|
||||||
|
mockPrefix = api.config.mock.prefix || mockPrefix;
|
||||||
|
// mock文件处理
|
||||||
|
mockFile = parsePath('./mock.js');
|
||||||
|
if (!existsSync(mockFile)) {
|
||||||
|
api.logger.info('mock.js File does not exist, please check'); return;
|
||||||
|
}
|
||||||
|
// 清除require的缓存,保证 mock 文件修改后拿到最新的 mock.js
|
||||||
|
if (require.cache[mockFile]) {
|
||||||
|
delete require.cache[mockFile];
|
||||||
|
}
|
||||||
|
const projectMock = require(mockFile);
|
||||||
|
if (!lodash.isFunction(projectMock)) {
|
||||||
|
api.logger.info('mock.js should export Function'); return;
|
||||||
|
}
|
||||||
|
// mock对象与 mock.js 结合
|
||||||
|
projectMock(cgiMock, Mock);
|
||||||
|
|
||||||
|
return (req, res, next) => {
|
||||||
|
// 如果请求不是以 cgiMock.prefix 开头,直接 next `${mockPrefix}/`
|
||||||
|
if (!req.path.startsWith(`${mockPrefix}/`)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求以 cgiMock.prefix 开头,匹配处理
|
||||||
|
const matchRequet = requestList.filter(item => req.path.search(item.url) !== -1)[0];
|
||||||
|
if (!matchRequet) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set header
|
||||||
|
res.set(matchRequet.headers);
|
||||||
|
// set Content-Type
|
||||||
|
matchRequet.type && res.type(matchRequet.type);
|
||||||
|
// set status code
|
||||||
|
res.status(matchRequet.statusCode);
|
||||||
|
// set cookie
|
||||||
|
traversalHandler(matchRequet.cookies, (item) => {
|
||||||
|
const name = item.name;
|
||||||
|
const value = item.value;
|
||||||
|
delete item.name;
|
||||||
|
delete item.value;
|
||||||
|
res.cookie(name, value, item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// do result
|
||||||
|
if (lodash.isFunction(matchRequet.result)) {
|
||||||
|
matchRequet.result(req, res);
|
||||||
|
} else if (
|
||||||
|
lodash.isArray(matchRequet.result) || isObject(matchRequet.result)
|
||||||
|
) {
|
||||||
|
!matchRequet.type && res.type('json');
|
||||||
|
res.json(matchRequet.result);
|
||||||
|
} else {
|
||||||
|
!matchRequet.type && res.type('text');
|
||||||
|
res.send(matchRequet.result.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
api.onStart(() => {
|
||||||
|
loadMock = createMock();
|
||||||
|
if (!mockFlag) return;
|
||||||
|
|
||||||
|
chokidar.watch(mockFile, {
|
||||||
|
ignoreInitial: true
|
||||||
|
}).on('change', () => {
|
||||||
|
api.logger.info('mock.js changed,reload');
|
||||||
|
loadMock = createMock();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
api.addBeforeMiddlewares(() => {
|
||||||
|
if (!mockFlag) return [];
|
||||||
|
return [
|
||||||
|
bodyParser.json(),
|
||||||
|
bodyParser.urlencoded({
|
||||||
|
extended: false
|
||||||
|
}),
|
||||||
|
cookieParser()
|
||||||
|
];
|
||||||
|
});
|
||||||
|
api.addBeforeMiddlewares(() => (req, res, next) => {
|
||||||
|
if (!mockFlag) return next();
|
||||||
|
loadMock(req, res, next);
|
||||||
|
});
|
||||||
|
};
|
@ -12,6 +12,15 @@ export default {
|
|||||||
admin: ["/", "/onepiece"]
|
admin: ["/", "/onepiece"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mock: {
|
||||||
|
prefix: '/v2'
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
'/v2': {
|
||||||
|
'target': 'https://api.douban.com/',
|
||||||
|
'changeOrigin': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
layout: {
|
layout: {
|
||||||
title: "Fes.js",
|
title: "Fes.js",
|
||||||
footer: 'Created by MumbelFe',
|
footer: 'Created by MumbelFe',
|
||||||
|
130
packages/fes-template/mock.js
Normal file
130
packages/fes-template/mock.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
module.exports = (cgiMock, Mock) => {
|
||||||
|
const { Random } = Mock;
|
||||||
|
|
||||||
|
// 测试 proxy 与 mock 用例集合
|
||||||
|
cgiMock('/movie/in_theaters_mock', (req, res) => {
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
code: '0',
|
||||||
|
msg: '',
|
||||||
|
result: {
|
||||||
|
text: 'movie: movie/in_theaters_mock ~~~~~'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
cgiMock('/movie/test_mock', (req, res) => {
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
code: '0',
|
||||||
|
msg: '',
|
||||||
|
result: {
|
||||||
|
text: 'mock: movie/test_mock'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试用例: mock.js change,重现请求,需要能拉最新的数据
|
||||||
|
cgiMock('/watchtest', (req, res) => {
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
code: '0',
|
||||||
|
msg: '',
|
||||||
|
result: {
|
||||||
|
text: '通过 register 测试 mock watch: 初始状态'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回一个数字
|
||||||
|
// cgiMock('/number', 666);
|
||||||
|
cgiMock('/number', 999);
|
||||||
|
|
||||||
|
// 返回一个json
|
||||||
|
cgiMock({
|
||||||
|
url: '/json',
|
||||||
|
result: {
|
||||||
|
code: '400101', msg: "不合法的请求:Missing cookie 'wb_app_id' for method parameter of type String", transactionTime: '20170309171146', success: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 利用 mock.js 产生随机文本
|
||||||
|
cgiMock('/text', Random.cparagraph());
|
||||||
|
|
||||||
|
// 返回一个字符串 利用 mock.js 产生随机字符
|
||||||
|
cgiMock('/random', Mock.mock({
|
||||||
|
'string|1-10': '★'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 正则匹配url, 返回一个字符串
|
||||||
|
cgiMock(/\/abc|\/xyz/, 'regexp test!');
|
||||||
|
|
||||||
|
// option.result 参数如果是一个函数, 可以实现自定义返回内容, 接收的参数是是经过 express 封装的 req 和 res 对象.
|
||||||
|
cgiMock(/\/function$/, (req, res) => {
|
||||||
|
res.send('function test');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回文本 readFileSync
|
||||||
|
cgiMock('/file', cgiMock.file('./package.json'));
|
||||||
|
|
||||||
|
// 更复杂的规则配置
|
||||||
|
cgiMock({
|
||||||
|
url: /\/who/,
|
||||||
|
method: 'GET',
|
||||||
|
result(req, res) {
|
||||||
|
if (req.query.name === 'kwan') {
|
||||||
|
res.json({ kwan: '孤独患者' });
|
||||||
|
} else {
|
||||||
|
res.send('Nooooooooooo');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
'Content-Length': '123',
|
||||||
|
ETag: '12345'
|
||||||
|
},
|
||||||
|
cookies: [
|
||||||
|
{
|
||||||
|
name: 'myname', value: 'kwan', maxAge: 900000, httpOnly: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 携带参数的请求
|
||||||
|
cgiMock('/v2/audit/list', (req, res) => {
|
||||||
|
const {
|
||||||
|
currentPage, pageSize, isAudited
|
||||||
|
} = req.body;
|
||||||
|
res.send({
|
||||||
|
code: '0',
|
||||||
|
msg: '',
|
||||||
|
data: {
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
totalPage: 2,
|
||||||
|
totalCount: 12,
|
||||||
|
pageData: Array.from({ length: pageSize }, () => ({
|
||||||
|
title: Random.title(),
|
||||||
|
authorName: Random.cname(),
|
||||||
|
authorId: Random.name(),
|
||||||
|
createTime: Date.now(),
|
||||||
|
updateTime: Date.now(),
|
||||||
|
readCount: Random.integer(60, 1000),
|
||||||
|
favoriteCount: Random.integer(1, 50),
|
||||||
|
postId: '12323',
|
||||||
|
serviceTag: '业务类型',
|
||||||
|
productTag: '产品类型',
|
||||||
|
requestTag: '需求类型',
|
||||||
|
handleTag: '已采纳',
|
||||||
|
postType: 'voice',
|
||||||
|
postStatus: isAudited ? 'pass' : 'auditing',
|
||||||
|
auditStatus: 'audit1'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// multipart/form-data 类型
|
||||||
|
cgiMock('/v2/upload', (req, res) => {
|
||||||
|
res.send({
|
||||||
|
code: '0',
|
||||||
|
msg: '文件上传成功'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
@ -54,6 +54,7 @@
|
|||||||
"@webank/fes-plugin-enums": "^2.0.0-alpha.0",
|
"@webank/fes-plugin-enums": "^2.0.0-alpha.0",
|
||||||
"@webank/fes-plugin-jest": "^2.0.0-alpha.0",
|
"@webank/fes-plugin-jest": "^2.0.0-alpha.0",
|
||||||
"@webank/fes-plugin-vuex": "^2.0.0-alpha.0",
|
"@webank/fes-plugin-vuex": "^2.0.0-alpha.0",
|
||||||
|
"@webank/fes-plugin-request": "2.0.0-alpha.1",
|
||||||
"ant-design-vue": "2.0.0-rc.3",
|
"ant-design-vue": "2.0.0-rc.3",
|
||||||
"vue": "3.0.5",
|
"vue": "3.0.5",
|
||||||
"vuex": "^4.0.0-rc.2"
|
"vuex": "^4.0.0-rc.2"
|
||||||
|
@ -22,11 +22,9 @@
|
|||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import {
|
import {
|
||||||
access, useAccess, useRouter, useI18n, locale, enums
|
access, useAccess, useRouter, useI18n, locale, enums, request
|
||||||
} from '@webank/fes';
|
} from '@webank/fes';
|
||||||
|
|
||||||
console.log(__DEV__);
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setup() {
|
setup() {
|
||||||
const fes = ref('fes upgrade to vue3');
|
const fes = ref('fes upgrade to vue3');
|
||||||
@ -81,6 +79,25 @@ export default {
|
|||||||
accessId.value = '11';
|
accessId.value = '11';
|
||||||
}, 4000);
|
}, 4000);
|
||||||
// router.push('/onepiece');
|
// router.push('/onepiece');
|
||||||
|
|
||||||
|
console.log('测试 mock!!');
|
||||||
|
request('/v2/file').then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
request('/v2/movie/in_theaters_mock').then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('测试 proxy!!');
|
||||||
|
request('/v2/movie/in_theaters_proxy').then((resp) => {
|
||||||
|
console.log(resp);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
accessId,
|
accessId,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user