mocks项目复盘
# mocks项目复盘
# 项目简介
- 前端 mock 方案
- mock数据保存在前端
- 支持根据 ts 类型 生成对应的mock数据
- 文件变化,自动加载最新的数据
# 问题
# Access-Control-Allow-Credentials
问题
- 项目中使用到了cookie,所以启用了这个属性,axios 开启了
withCredentials = true
(CORS请求默认不发送Cookie和HTTP认证信息,前端请求开启这个属性,请求默认发送cookie) - 启用后会报 cros 问题,但实际上是开启了允许 cros,响应内容不会返回给请求的发起者 (opens new window)
- 此时不能将响应headers 的 origin 设置为
*
,必须为实际的 origin(request请求时的origin)阮一峰的跨域资源共享 CORS 详解 (opens new window)
// koa
function (ctx: any) {
let origin = '*'
if (ctx.request.header && ctx.request.header.origin) (origin = ctx.request.header.origin)
ctx.set({
'origin': origin
})
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# fs.watch 问题
- fs.watch 监听文件夹,如果修改了某个文件名,将会触发两次
rename
事件 - 第一次回调改名前的文件相对路径
- 第二次回调改名后的文件相对路径
- 这样还需要自己去辨别是删除了文件,还是仅仅修改了文件名
- 使用 chokidar (opens new window) 插件 代替
fs.watch
, - chokidar
- 删除:unlink
- 添加:add
- 改变:change
const chokidar = require('chokidar');
chokidar.watch('.').on('all', (event, path) => {
console.log(event, path);
});
1
2
3
4
5
2
3
4
5
# json-schema-faker
+ typescript-json-schema
+ typescript
mock数据
- json-schema-faker (opens new window),原是基于
faker
生成mock数据,但faker
原作者因某些原因已将该库删除 - typescript-json-schema (opens new window)
// 创建 ts 编译程序
// file:需要编译哪个文件
const program = ts.createProgram([file], {
noEmit: true, // 不输出文件(write)
allowJs: true, // 允许js
emitDecoratorMetadata: true, // 为源文件中的修饰声明发出设计类型元数据
experimentalDecorators: true, // 启用对 TC39 第 2 阶段草稿装饰器的实验性支持
target: ts.ScriptTarget.ES5, // 为发出的 JavaScript 设置 JavaScript 语言版本并包含兼容的库声明
module: ts.ModuleKind.CommonJS, // 指定生成什么模块代码
allowUnusedLabels: true, // 禁用未使用标签的错误报告
diagnostics: true, // 构建后输出编译器性能信息
explainFiles: true, // 打印在编译期间读取的文件,包括包含它的原因
resolveJsonModule: true, // 启用导入 .json 文件
});
// symbol 是需要从 file 产生 mock 数据的 interface 等
const schema = this.jsonSchema.generateSchema(program, symbol, {
required: true, // 不加上,最后生成的数据,会有缺失、额外字段
});
const result = await jsf.generate(schema); // jsonValue to string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
eg:
// user/index.ts
type MyName = SchamName;
interface SchamName {
school: string;
}
export interface Mime {
age: number;
name: string;
info: MyName;
sex?: string;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
// 创建 ts 编译程序
// file:需要编译哪个文件
const program = ts.createProgram(['./user/index.ts'], {
noEmit: true, // 不输出文件(write)
allowJs: true, // 允许js
emitDecoratorMetadata: true, // 为源文件中的修饰声明发出设计类型元数据
experimentalDecorators: true, // 启用对 TC39 第 2 阶段草稿装饰器的实验性支持
target: ts.ScriptTarget.ES5, // 为发出的 JavaScript 设置 JavaScript 语言版本并包含兼容的库声明
module: ts.ModuleKind.CommonJS, // 指定生成什么模块代码
allowUnusedLabels: true, // 禁用未使用标签的错误报告
diagnostics: true, // 构建后输出编译器性能信息
explainFiles: true, // 打印在编译期间读取的文件,包括包含它的原因
resolveJsonModule: true, // 启用导入 .json 文件
});
// symbol 是需要从 file 产生 mock 数据的 interface 等
const schema = this.jsonSchema.generateSchema(program, 'Mime', {
required: true, // 不加上,最后生成的数据,会有缺失、额外字段
});
const result = await jsf.generate(schema); // jsonValue to string
// 输出
// {
// "age": 11019161.318964705,
// "info": {
// "school": "enim magna reprehenderit voluptate esse"
// },
// "name": "adipisicing sit dolor",
// "sex": "in quis"
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 从一个 JSON 字符串中解析出 某个属性
- 针对包含特殊字符的 json 字符串, 例如:属性名有
$_+
等
/**
* 在一个json字符串中,解析出需要的那个字段
* @param str 需要解析的源字符串
* @param target 需要解析的属性
* @returns 解析后获取到的 target JSON 对象
*/
export function getJsonFromStr(str: string, target: string) {
let file = str;
let idx = file.indexOf(target);
if (idx > -1) {
file = file.slice(idx + target.length + 1);
idx = file.indexOf('{');
const stack = [];
const fileStack = [];
if (idx > -1) {
stack.push('{');
fileStack.push('{');
file = file.slice(idx + 1);
while (stack.length > 0) {
if (file.indexOf('{') > -1 || file.indexOf('}') > -1) {
if (file.indexOf('{') > -1 && file.indexOf('{') < file.indexOf('}')) {
// 先匹配到的是 {
// 存入 stack
idx = file.indexOf('{');
fileStack.push(file.slice(0, idx));
file = file.slice(idx + 1);
fileStack.push('{');
stack.push('{');
} else if (file.indexOf('}') === -1) {
// 未匹配到 } 直接 break
break;
} else {
// 此时匹配到的是 } 用 { 去抵消
// 取出 stack
let char = stack.pop();
if (char !== '{') {
// 无法匹配,此时说明解析错误,直接推出
throw new Error('replace解析错误');
} else {
idx = file.indexOf('}');
fileStack.push(file.slice(0, idx));
file = file.slice(idx + 1);
fileStack.push('}');
}
}
} else break;
}
// 解析完成,尝试拼接
const finallyStr = fileStack.join('');
try {
const finallyJson = new Function('return' + finallyStr)(); // 转换成 json
if (typeof finallyJson === 'object') {
return finallyJson;
}
return '';
} catch (error) {
throw new Error('循环解析文本出错');
}
}
}
return '';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
eg:
let str = `
{
replace: {
$$_THOR_HOST_$$: {
'dev-daily': '//h5.dev.weidian.com:7000',
// 'dev-daily': '//thor.daily.weidian.com',
'dev-pre': '//thor.pre.weidian.com',
'dev-prod': '//thor.weidian.com'
},
$$_ENVIRONMENT_$$: {
'dev-daily': 1,
'dev-pre': 2,
'dev-prod': 3
}
}
}
`
console.log(getJsonFromStr(str, 'replace:')) // 最终就能获得 replace 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# http、https 创建代理服务器
- 本来应该在 mock 服务中 再使用代理服务器的(初衷是用来请求真实地址)
- 发现跨域,所以耽搁了下来,顺便记录下
- https 需要用到 ssl,所以这里用了 devcert (opens new window)
- 获取可用端口号使用了 portfinder (opens new window)
import Koa from 'koa';
import http from 'http';
import https from 'https';
import portfinder from 'portfinder';
const app = new Koa()
// isHttps 代表创建 https服务器,这里简化了
// 实际得定义 isHttps 变量
if (isHttps) {
require('devcert')
.certificateFor('coderly.cn', { getCaPath: true })
.then((ssl: any) => {
const { key, cert } = ssl;
https.createServer({ key, cert }, app.callback()).listen(443);
});
} else {
getFreePort().then((port) => http.createServer(app.callback()).listen(port));
}
/**
* 获取可用端口号
* @param port 查找可用端口号起始 端口,默认 3000
* @returns
*/
export function getFreePort(port: number = 3000) {
portfinder.basePort = port;
return new Promise<number>((resolve, reject) => {
portfinder
.getPortPromise()
.then((port: number) => {
resolve(port);
})
.catch(reject);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
上次更新: 2022/03/29, 11:07:22