@36node/mock-server 中文文档教程
@36node/mock-server
mock-server 基于 json-server, 为了更好的提供数据 mock 服务.
Install
$ yarn install @36node/mock-server
Use
1. 在 Nodejs 中使用
#!/usr/bin/env node
const mockServer = require("@36node/mock-server");
const app = mockServer({
db: {
pets: [
{ id: 1, name: "kitty", tag: "CAT", grade: 3 },
{ id: 2, name: "pi", tag: "DOG", grade: 4 },
],
},
rewrites: {
"/store/pets*": "/pets$1",
},
routers: [], // custom middle ware
aggregations: {
"/pets": {
grade: records => _.sumBy(records, "grade") / records.length,
count: records => records.length,
},
},
});
app.listen(3000, () => {
console.log("JSON Server is running on port 3000");
});
2. 在 webpack develop server 中使用
使用 react-app-rewired 时, 通过 config-overwrites.js 文件 配置 devServer
const stopMock = process.env.MOCK === "false" || process.env.MOCK === "FALSE";
const defaultServerOpts = { delay: 500 };
const {
serverOpts = defaultServerOpts,
db: {
pets: [
{ id: 1, name: "kitty", tag: "CAT", grade: 3 },
{ id: 2, name: "pi", tag: "DOG", grade: 4 },
],
},
rewrites: {
"/store/pets*": "/pets$1",
},
routers: [], // custom middle ware
aggregations: {
"/pets": {
grade: records => _.sumBy(records, "grade") / records.length,
count: records => records.length,
},
},
} = someMockConfig;
const mockServer = require("@36node/mock-server");
module.exports = {
...otherConfig,
devServer: function(configFunction) {
return function(proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
if (stopMock) {
return config;
}
/**
* mock server hoc
* @param {Express.Application} app
*/
function configMock(app) {
// 根据 请求的 header.accept 的类型决定是正常渲染,还是进入mock-server
const shouldMockReq = req => {
return (
req.method !== "GET" ||
(req.headers.accept &&
req.headers.accept.indexOf("application/json") !== -1)
);
};
if (serverOpts.delay) {
app.use((req, res, next) => {
if (shouldMockReq(req)) {
return pause(serverOpts.delay)(req, res, next);
}
return next();
});
}
mockServer({ app, db, rewrites, routers, shouldMockReq });
return app;
}
const prev = config.before;
config.before = compose(
configMock,
app => {
prev(app);
return app;
}
);
return config;
};
},
};
Api
mockServer(opts)
params:
opts: Object
db: // 同 json-server 的 db 配置 https://github.com/typicode/json-server#getting-started
rewrites (Optional): 同 json-server https://github.com/typicode/json-server#rewriter-example
routes (Optional): [Express.Middleware] 同 json-server custom-middle https://github.com/typicode/json-server#add-middlewares
aggregations (Optional): Object 见下文 Aggregation
app (Optional): Express.Application, 如果没有则自动新建
shouldMock (Optional): (req, res) => Boolean, 判断 request 是否使用 mock-server 的 中间件
返回:Express.Appliction
Array
使用标准 url query 格式传递数组数据
a=1&a=2
Filter
Use .
to access deep properties
GET /posts?title=json-server&author=typicode
GET /posts?id=1&id=2
GET /comments?author.name=typicode
Paginate
Use _offset
and optionally _limit
to paginate returned data. (an X-Total-Count
header is included in the response)
In the Link
header you'll get first
, prev
, next
and last
links.
GET /posts?_offset=10
GET /posts?_offset=7&_limit=20
note: 10 items are returned by default
Sort
Add _sort
and _order
(ascending order by default)
# asc
GET /posts?_sort=views
# desc
GET /posts/1/comments?_sort=-votes
note: list posts by views ascending order and comments by votes descending order
For multiple fields, use the following format:
GET /posts/1/comments?_sort=-votes&_sort=likes
_prefixing a path with -
will flag that sort is descending order. When a path does not have the -
prefix, it is ascending order.
Operators
Add _gt
, _lt
, _gte
or _lte
for getting a range
GET /posts?views_gte=10&views_lte=20
Add _ne
to exclude a value
GET /posts?id_ne=1
Add _like
to filter (RegExp supported)
_like
support array
GET /posts?title_like=server
Select
Specifies which document fields to include or exclude
GET /posts?_select=title&_select=body
GET /posts?_select=-comments&_select=-views
or
_select=title,body
prefixing a path with -
will flag that path as excluded. When a path does not have the -
prefix, it is included A projection must be either inclusive or exclusive. In other words, you must either list the fields to include (which excludes all others), or list the fields to exclude (which implies all other fields are included).
Aggregation
聚合的 query 请求。聚合请求通过 _group 和 _select 参数来控制,通过 opts.aggregations 配置:
比如对于一个 db 配置:
const faker = require("faker");
const _ = require("lodash");
const moment = require("moment");
const now = moment();
const generate = count =>
_.range(count).map((val, index) => {
const birthAt = faker.date.between(
moment()
.subtract(10, "year")
.toDate(),
moment()
.subtract(1, "year")
.toDate()
);
const age = now.diff(moment(birthAt), "year");
return {
id: faker.random.uuid(), // pet id
name: faker.name.lastName(), // pet name
tag: faker.random.arrayElement(["CAT", "DOG"]), // pet tag
owner: faker.name.firstName(), // pet owner
grade: faker.random.number({ min: 1, max: 5 }), // pet grade
age, // pet age
birthAt: birthAt.toISOString(), // pet birth time
};
});
const db = {
pets: generate(100),
};
其中包括了 100 个 pets 的 mock 数据,可使用的路由有:
GET /pets
GET /pets/{petId}
POST /pets
PUT /pets/{petId}
PATCH /pets/{petId}
DELETE /pets/{petId}
聚合只在 GET /pets
中有效
简单分组
如果需要统计 pets 中猫和狗的数量, 可以对 tag 分组
配置 aggregations 参数
aggregations: {
"/pets": {
// records 是分组后的数据集合
count: records => records.length,
// 默认支持 两种聚合简写 求和 'sum' 和 平均 ‘avg'
grade: 'avg',
},
},
请求:
GET /pets?_group=tag
结果:
[
{
"id": "tag=CAT",
"tag": "CAT",
"grade": 3.017857142857143,
"count": 56
},
{
"id": "tag=DOG",
"tag": "DOG",
"grade": 3.3863636363636362,
"count": 44
}
]
按时间粒度分组
如果需要统计每个月分别生了多少猫和狗, 可以按 tag 和 birthAt.month 分组
对于时间的分组条件,可以采用不同粒度进行分组,query 的格式为 birthAt.month
表示在 birthAt 字段上 按照 月粒度进行分组。
支持的粒度包括:
[
"year", // 年
"quarter", // 季度
"month", // 月
"week", // 星期
"isoWeek", // iso 星期
"day", // 天
"hour", // 小时
"min", // 分钟
"second", // 秒
];
请求:
GET /pets?_group=tag&_group=birthAt.month
结果:
[
...,
{
"id": "tag=CAT&birthAt=2012-12-31T16%3A00%3A00.000Z",
"tag": "CAT",
"birthAt": "2012-12-31T16:00:00.000Z",
"count": 6
},
{
"id": "tag=DOG&birthAt=2014-12-31T16%3A00%3A00.000Z",
"tag": "DOG",
"birthAt": "2014-12-31T16:00:00.000Z",
"count": 7
}
]
Tips:
- 在返回结果中,birthAt 当前月的起始时间(UTC),如果使用其他粒度,则类似。
- 如果同时传入统一字段的多个时间粒度,比如
_group=birthAt.year&_group=birthAt.month
, 则较小的时间粒度(month)会生效.