UNPKG

8.5 kBMarkdownView Raw
1# @36node/mock-server
2
3[![version][0]][1] [![downloads][2]][3]
4
5mock-server 基于 json-server, 为了更好的提供数据 mock 服务.
6
7## Install
8
9```bash
10$ yarn install @36node/mock-server
11```
12
13## Use
14
15### 1. 在 Nodejs 中使用
16
17```js
18#!/usr/bin/env node
19
20const mockServer = require("@36node/mock-server");
21
22const app = mockServer({
23 db: {
24 pets: [
25 { id: 1, name: "kitty", tag: "CAT", grade: 3 },
26 { id: 2, name: "pi", tag: "DOG", grade: 4 },
27 ],
28 },
29 rewrites: {
30 "/store/pets*": "/pets$1",
31 },
32 routers: [], // custom middle ware
33 aggregations: {
34 "/pets": {
35 grade: records => _.sumBy(records, "grade") / records.length,
36 count: records => records.length,
37 },
38 },
39});
40
41app.listen(3000, () => {
42 console.log("JSON Server is running on port 3000");
43});
44```
45
46### 2. 在 webpack develop server 中使用
47
48使用 react-app-rewired 时, 通过 config-overwrites.js 文件 配置 devServer
49
50```js
51const stopMock = process.env.MOCK === "false" || process.env.MOCK === "FALSE";
52const defaultServerOpts = { delay: 500 };
53const {
54 serverOpts = defaultServerOpts,
55 db: {
56 pets: [
57 { id: 1, name: "kitty", tag: "CAT", grade: 3 },
58 { id: 2, name: "pi", tag: "DOG", grade: 4 },
59 ],
60 },
61 rewrites: {
62 "/store/pets*": "/pets$1",
63 },
64 routers: [], // custom middle ware
65 aggregations: {
66 "/pets": {
67 grade: records => _.sumBy(records, "grade") / records.length,
68 count: records => records.length,
69 },
70 },
71} = someMockConfig;
72
73const mockServer = require("@36node/mock-server");
74
75module.exports = {
76 ...otherConfig,
77
78 devServer: function(configFunction) {
79 return function(proxy, allowedHost) {
80 const config = configFunction(proxy, allowedHost);
81
82 if (stopMock) {
83 return config;
84 }
85
86 /**
87 * mock server hoc
88 * @param {Express.Application} app
89 */
90 function configMock(app) {
91 // 根据 请求的 header.accept 的类型决定是正常渲染,还是进入mock-server
92 const shouldMockReq = req => {
93 return (
94 req.method !== "GET" ||
95 (req.headers.accept &&
96 req.headers.accept.indexOf("application/json") !== -1)
97 );
98 };
99
100 if (serverOpts.delay) {
101 app.use((req, res, next) => {
102 if (shouldMockReq(req)) {
103 return pause(serverOpts.delay)(req, res, next);
104 }
105 return next();
106 });
107 }
108
109 mockServer({ app, db, rewrites, routers, shouldMockReq });
110
111 return app;
112 }
113
114 const prev = config.before;
115
116 config.before = compose(
117 configMock,
118 app => {
119 prev(app);
120 return app;
121 }
122 );
123
124 return config;
125 };
126 },
127};
128```
129
130## Api
131
132mockServer(opts)
133
134params:
135
1361. opts: Object
137
138 db: // 同 json-server 的 db 配置 https://github.com/typicode/json-server#getting-started
139
140 rewrites (Optional): 同 json-server https://github.com/typicode/json-server#rewriter-example
141
142 routes (Optional): [Express.Middleware] 同 json-server custom-middle https://github.com/typicode/json-server#add-middlewares
143
144 aggregations (Optional): Object 见下文 Aggregation
145
146 app (Optional): Express.Application, 如果没有则自动新建
147
148 shouldMock (Optional): (req, res) => Boolean, 判断 request 是否使用 mock-server 的 中间件
149
150返回:Express.Appliction
151
152## Array
153
154使用标准 url query 格式传递数组数据
155
156```curl
157a=1&a=2
158```
159
160## Filter
161
162Use `.` to access deep properties
163
164```curl
165GET /posts?title=json-server&author=typicode
166GET /posts?id=1&id=2
167GET /comments?author.name=typicode
168```
169
170## Paginate
171
172Use `_offset` and optionally `_limit` to paginate returned data. (an `X-Total-Count` header is included in the response)
173
174In the `Link` header you'll get `first`, `prev`, `next` and `last` links.
175
176```curl
177GET /posts?_offset=10
178GET /posts?_offset=7&_limit=20
179```
180
181note: _10 items are returned by default_
182
183## Sort
184
185Add `_sort` and `_order` (ascending order by default)
186
187```curl
188# asc
189GET /posts?_sort=views
190
191# desc
192GET /posts/1/comments?_sort=-votes
193```
194
195note: _list posts by views ascending order and comments by votes descending order_
196
197For multiple fields, use the following format:
198
199```curl
200GET /posts/1/comments?_sort=-votes&_sort=likes
201```
202
203\_prefixing a path with `-` will flag that sort is descending order.
204When a path does not have the `-` prefix, it is ascending order.
205
206## Operators
207
208Add `_gt`, `_lt`, `_gte` or `_lte` for getting a range
209
210```curl
211GET /posts?views_gte=10&views_lte=20
212```
213
214Add `_ne` to exclude a value
215
216```curl
217GET /posts?id_ne=1
218```
219
220Add `_like` to filter (RegExp supported)
221
222`_like` support array
223
224```curl
225GET /posts?title_like=server
226```
227
228## Select
229
230Specifies which document fields to include or exclude
231
232```curl
233GET /posts?_select=title&_select=body
234GET /posts?_select=-comments&_select=-views
235```
236
237or
238
239```curl
240_select=title,body
241```
242
243_prefixing a path with `-` will flag that path as excluded._
244_When a path does not have the `-` prefix, it is included_
245_A projection must be either inclusive or exclusive._
246_In other words, you must either list the fields to include (which excludes all others),_
247_or list the fields to exclude (which implies all other fields are included)._
248
249## Aggregation
250
251聚合的 query 请求。聚合请求通过 \_group 和 \_select 参数来控制,通过 opts.aggregations 配置:
252
253比如对于一个 db 配置:
254
255```js
256const faker = require("faker");
257const _ = require("lodash");
258const moment = require("moment");
259
260const now = moment();
261
262const generate = count =>
263 _.range(count).map((val, index) => {
264 const birthAt = faker.date.between(
265 moment()
266 .subtract(10, "year")
267 .toDate(),
268 moment()
269 .subtract(1, "year")
270 .toDate()
271 );
272
273 const age = now.diff(moment(birthAt), "year");
274
275 return {
276 id: faker.random.uuid(), // pet id
277 name: faker.name.lastName(), // pet name
278 tag: faker.random.arrayElement(["CAT", "DOG"]), // pet tag
279 owner: faker.name.firstName(), // pet owner
280 grade: faker.random.number({ min: 1, max: 5 }), // pet grade
281 age, // pet age
282 birthAt: birthAt.toISOString(), // pet birth time
283 };
284 });
285
286const db = {
287 pets: generate(100),
288};
289```
290
291其中包括了 100 个 pets 的 mock 数据,可使用的路由有:
292
293```
294GET /pets
295GET /pets/{petId}
296POST /pets
297PUT /pets/{petId}
298PATCH /pets/{petId}
299DELETE /pets/{petId}
300```
301
302聚合只在 `GET /pets` 中有效
303
304### 简单分组
305
306如果需要统计 pets 中猫和狗的数量, 可以对 tag 分组
307
308配置 aggregations 参数
309
310```js
311 aggregations: {
312 "/pets": {
313 // records 是分组后的数据集合
314 count: records => records.length,
315 // 默认支持 两种聚合简写 求和 'sum' 和 平均 ‘avg'
316 grade: 'avg',
317 },
318 },
319```
320
321请求:
322
323```
324GET /pets?_group=tag
325```
326
327结果:
328
329```json
330[
331 {
332 "id": "tag=CAT",
333 "tag": "CAT",
334 "grade": 3.017857142857143,
335 "count": 56
336 },
337 {
338 "id": "tag=DOG",
339 "tag": "DOG",
340 "grade": 3.3863636363636362,
341 "count": 44
342 }
343]
344```
345
346### 按时间粒度分组
347
348如果需要统计每个月分别生了多少猫和狗, 可以按 tag 和 birthAt.month 分组
349
350对于时间的分组条件,可以采用不同粒度进行分组,query 的格式为 `birthAt.month` 表示在 birthAt 字段上 按照 月粒度进行分组。
351
352支持的粒度包括:
353
354```js
355[
356 "year", // 年
357 "quarter", // 季度
358 "month", // 月
359 "week", // 星期
360 "isoWeek", // iso 星期
361 "day", // 天
362 "hour", // 小时
363 "min", // 分钟
364 "second", // 秒
365];
366```
367
368请求:
369
370```
371GET /pets?_group=tag&_group=birthAt.month
372```
373
374结果:
375
376```json
377[
378 ...,
379 {
380 "id": "tag=CAT&birthAt=2012-12-31T16%3A00%3A00.000Z",
381 "tag": "CAT",
382 "birthAt": "2012-12-31T16:00:00.000Z",
383 "count": 6
384 },
385 {
386 "id": "tag=DOG&birthAt=2014-12-31T16%3A00%3A00.000Z",
387 "tag": "DOG",
388 "birthAt": "2014-12-31T16:00:00.000Z",
389 "count": 7
390 }
391]
392```
393
394Tips:
395
3961. 在返回结果中,birthAt 当前月的起始时间(UTC),如果使用其他粒度,则类似。
3972. 如果同时传入统一字段的多个时间粒度,比如 `_group=birthAt.year&_group=birthAt.month`, 则较小的时间粒度(month)会生效.
398
399[0]: https://img.shields.io/npm/v/@36node/mock-server.svg?style=flat
400[1]: https://npmjs.com/package/@36node/mock-server
401[2]: https://img.shields.io/npm/dm/@36node/mock-server.svg?style=flat
402[3]: https://npmjs.com/package/@36node/mock-server