UNPKG

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