whistle.mockbubu 1.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +18 -0
- package/.gitignore +63 -0
- package/README.md +80 -0
- package/index.js +19 -0
- package/lib/const.js +28 -0
- package/lib/resRulesServer.js +9 -0
- package/lib/server.js +107 -0
- package/lib/uiServer/index.js +35 -0
- package/lib/uiServer/router/index.js +171 -0
- package/lib/uiServer/router/version-router.js +127 -0
- package/lib/uiServer/util.js +99 -0
- package/lib/utils.js +287 -0
- package/package.json +44 -0
- package/public/fonts/element-icons.f1a45d74.ttf +0 -0
- package/public/fonts/element-icons.ff18efd1.woff +0 -0
- package/public/img/jsoneditor-icons.94cc3007.svg +749 -0
- package/public/index.html +12 -0
- package/public/js/app.js +3537 -0
- package/public/js/app.js.map +1 -0
- package/public/js/chunk-vendors.js +39851 -0
- package/public/js/chunk-vendors.js.map +1 -0
- package/rules.txt +2 -0
- package/step1.png +0 -0
- package/step2.png +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getApiList,
|
|
3
|
+
getProperty,
|
|
4
|
+
} = require('../utils')
|
|
5
|
+
const { RangeFilterMap, RuleFilterMap, LockedFilterMap } = require('../const')
|
|
6
|
+
|
|
7
|
+
// 统一错误响应格式
|
|
8
|
+
const createErrorResponse = (message, code = 500) => ({
|
|
9
|
+
code,
|
|
10
|
+
msg: message || '操作失败',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// 统一成功响应格式
|
|
14
|
+
const createSuccessResponse = (data = null, message = '操作成功') => ({
|
|
15
|
+
code: 200,
|
|
16
|
+
msg: message,
|
|
17
|
+
...(data && { data }),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// 执行筛选
|
|
21
|
+
const execFilter = (result, filter) => {
|
|
22
|
+
const { key, value, method } = filter
|
|
23
|
+
const list = result.filter((item) => {
|
|
24
|
+
const type = Object.prototype.toString.call(value)
|
|
25
|
+
|
|
26
|
+
switch (method) {
|
|
27
|
+
case 'indexOf':
|
|
28
|
+
if (type === '[object String]') {
|
|
29
|
+
return ~item[key].indexOf(value)
|
|
30
|
+
}
|
|
31
|
+
break
|
|
32
|
+
case 'equal':
|
|
33
|
+
return item[key] === value
|
|
34
|
+
case 'unequal':
|
|
35
|
+
return item[key] !== value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return true
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return list
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 处理筛选条件
|
|
45
|
+
const handleFilterList = (list, filterOptions) => {
|
|
46
|
+
if (!Array.isArray(list) || !filterOptions) return []
|
|
47
|
+
|
|
48
|
+
const filters = []
|
|
49
|
+
const { name, rule, range, ruleValue, locked } = filterOptions
|
|
50
|
+
if (name) {
|
|
51
|
+
filters.push({
|
|
52
|
+
key: 'name',
|
|
53
|
+
value: name.trim(),
|
|
54
|
+
method: 'indexOf',
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (rule) {
|
|
59
|
+
filters.push({
|
|
60
|
+
key: 'rule',
|
|
61
|
+
value: rule.trim(),
|
|
62
|
+
method: 'equal',
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 范围筛选
|
|
67
|
+
RangeFilterMap[range] && filters.push(RangeFilterMap[range])
|
|
68
|
+
// 规则值筛选
|
|
69
|
+
RuleFilterMap[ruleValue] && filters.push(RuleFilterMap[ruleValue])
|
|
70
|
+
// 锁定筛选
|
|
71
|
+
LockedFilterMap[locked] && filters.push(LockedFilterMap[locked])
|
|
72
|
+
|
|
73
|
+
// 执行筛选
|
|
74
|
+
const filteredList = filters.reduce((result, filter) => {
|
|
75
|
+
return execFilter(result, filter)
|
|
76
|
+
}, list)
|
|
77
|
+
|
|
78
|
+
return filteredList
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 获取文件列表(包含文件属性数据)
|
|
82
|
+
const getFullDataList = (localStorage) => {
|
|
83
|
+
// 获取接口列表
|
|
84
|
+
const list = getApiList(localStorage) || []
|
|
85
|
+
|
|
86
|
+
// 合并属性数据
|
|
87
|
+
list.forEach((item) => {
|
|
88
|
+
const props = getProperty(localStorage, item.name) || {}
|
|
89
|
+
delete item.data
|
|
90
|
+
Object.assign(item, props)
|
|
91
|
+
})
|
|
92
|
+
return list
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exports.createErrorResponse = createErrorResponse
|
|
96
|
+
exports.createSuccessResponse = createSuccessResponse
|
|
97
|
+
exports.execFilter = execFilter
|
|
98
|
+
exports.handleFilterList = handleFilterList
|
|
99
|
+
exports.getFullDataList = getFullDataList
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
const zlib = require('zlib')
|
|
2
|
+
const { RuleValueMap } = require('./const')
|
|
3
|
+
// const URL = require('url')
|
|
4
|
+
const setProperty = (storage, property, val) => {
|
|
5
|
+
if (storage.hasProperty(property)) {
|
|
6
|
+
const oldVal = storage.getProperty(property)
|
|
7
|
+
|
|
8
|
+
if (typeof oldVal === 'object') {
|
|
9
|
+
storage.setProperty(property, Object.assign(oldVal, val))
|
|
10
|
+
} else {
|
|
11
|
+
storage.setProperty(property, val)
|
|
12
|
+
}
|
|
13
|
+
} else {
|
|
14
|
+
storage.setProperty(property, val)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const removePropertyAttr = (storage, property, attrName) => {
|
|
19
|
+
if (storage.hasProperty(property)) {
|
|
20
|
+
const oldVal = storage.getProperty(property)
|
|
21
|
+
|
|
22
|
+
if (typeof oldVal === 'object') {
|
|
23
|
+
delete oldVal[attrName]
|
|
24
|
+
storage.setProperty(property, oldVal)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const getPropertyAttr = (storage, property, attrName) => {
|
|
29
|
+
const props = storage.getProperty(property)
|
|
30
|
+
|
|
31
|
+
return props[attrName]
|
|
32
|
+
}
|
|
33
|
+
const getProperty = (storage, property) => {
|
|
34
|
+
return storage.getProperty(property)
|
|
35
|
+
}
|
|
36
|
+
const removeProperty = (storage, property) => {
|
|
37
|
+
return storage.removeProperty(property)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const writeFile = ({ storage, filename, body, properties, encoding }) => {
|
|
41
|
+
switch (encoding) {
|
|
42
|
+
case 'gzip':
|
|
43
|
+
body = zlib.gunzipSync(body)
|
|
44
|
+
break
|
|
45
|
+
case 'deflate':
|
|
46
|
+
body = zlib.inflateSync(body)
|
|
47
|
+
break
|
|
48
|
+
case 'br':
|
|
49
|
+
body = zlib.brotliDecompressSync(body)
|
|
50
|
+
break
|
|
51
|
+
default:
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
setProperty(storage, filename, properties || {})
|
|
55
|
+
|
|
56
|
+
const type = Object.prototype.toString.call(body)
|
|
57
|
+
let content
|
|
58
|
+
switch (type) {
|
|
59
|
+
case '[object String]':
|
|
60
|
+
content = body
|
|
61
|
+
break
|
|
62
|
+
case '[object Object]':
|
|
63
|
+
content = JSON.stringify(body)
|
|
64
|
+
break
|
|
65
|
+
case '[object Uint8Array]':
|
|
66
|
+
content = body.toString()
|
|
67
|
+
break
|
|
68
|
+
default:
|
|
69
|
+
content = body
|
|
70
|
+
}
|
|
71
|
+
storage.writeFile(filename, content)
|
|
72
|
+
setApiListUpdated(storage, true)
|
|
73
|
+
console.log('%c [ content ]-58', 'font-size:13px; background:pink; color:#bf2c9f;', content)
|
|
74
|
+
// switch (type) {
|
|
75
|
+
// case '[object String]':
|
|
76
|
+
// storage.writeFile(filename, body)
|
|
77
|
+
// break
|
|
78
|
+
// case '[object Object]':
|
|
79
|
+
// storage.writeFile(filename, JSON.stringify(body))
|
|
80
|
+
// break
|
|
81
|
+
// case '[object Uint8Array]':
|
|
82
|
+
// storage.writeFile(filename, body.toString())
|
|
83
|
+
// break
|
|
84
|
+
// default:
|
|
85
|
+
// storage.writeFile(filename, body) // TODO:
|
|
86
|
+
// }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const getApiList = (storage) => {
|
|
90
|
+
return storage.getFileList()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const updateFile = (storage, filename, body) => {
|
|
94
|
+
if (!filename) {
|
|
95
|
+
throw new Error('文件名不能为空')
|
|
96
|
+
}
|
|
97
|
+
// storage.writeFile(filename, JSON.stringify(body))
|
|
98
|
+
|
|
99
|
+
const sessionCache = storage.readFile(filename)
|
|
100
|
+
if (sessionCache) {
|
|
101
|
+
const session = JSON.parse(sessionCache)
|
|
102
|
+
session.res.body = JSON.stringify(body)
|
|
103
|
+
storage.writeFile(filename, JSON.stringify(session))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const readFile = (storage, filename) => {
|
|
108
|
+
// return storage.readFile(filename)
|
|
109
|
+
|
|
110
|
+
const sessionCache = storage.readFile(filename)
|
|
111
|
+
if (sessionCache) {
|
|
112
|
+
const resCache = JSON.parse(sessionCache)?.res
|
|
113
|
+
console.log(resCache)
|
|
114
|
+
return resCache.body
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const removeFile = (storage, name) => {
|
|
119
|
+
storage.removeFile(name)
|
|
120
|
+
removeProperty(storage, name)
|
|
121
|
+
}
|
|
122
|
+
// 获取mock文件名
|
|
123
|
+
const getFilename = (originalReq) => {
|
|
124
|
+
const { url, ruleValue, pattern } = originalReq
|
|
125
|
+
const u = new URL(url)
|
|
126
|
+
let filename = url
|
|
127
|
+
|
|
128
|
+
switch (ruleValue) {
|
|
129
|
+
case RuleValueMap.href:
|
|
130
|
+
filename = u.href
|
|
131
|
+
break
|
|
132
|
+
case RuleValueMap.pathname:
|
|
133
|
+
filename = u.origin + u.pathname
|
|
134
|
+
break
|
|
135
|
+
case RuleValueMap.pattern:
|
|
136
|
+
filename = pattern
|
|
137
|
+
break
|
|
138
|
+
default:
|
|
139
|
+
filename = u.origin + u.pathname
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return filename
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const getApiListUpdated = (storage) => {
|
|
146
|
+
return !!storage.getProperty('api-list-updated')
|
|
147
|
+
}
|
|
148
|
+
const setApiListUpdated = (storage, updated) => {
|
|
149
|
+
return storage.setProperty('api-list-updated', updated)
|
|
150
|
+
}
|
|
151
|
+
const getRule = (originalReq) => {
|
|
152
|
+
const { pattern, ruleValue } = originalReq
|
|
153
|
+
|
|
154
|
+
return `${pattern} mockbubu://${ruleValue}`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const isJsonReq = (headers) => {
|
|
158
|
+
return (
|
|
159
|
+
(headers.accept && headers.accept.includes('application/json')) ||
|
|
160
|
+
headers['sec-fetch-dest'] === 'empty' ||
|
|
161
|
+
headers['Sec-Fetch-Dest'] === 'empty'
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getVersionName(name) {
|
|
166
|
+
return 'version.' + name
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* mock版本管理
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
// 新增版本
|
|
174
|
+
const addNewVersion = ({ storage, filename, versionName, content }) => {
|
|
175
|
+
const name = getVersionName(versionName)
|
|
176
|
+
|
|
177
|
+
setProperty(storage, filename, { [name]: content })
|
|
178
|
+
}
|
|
179
|
+
// 更新版本文件内容
|
|
180
|
+
const updateVersionContent = ({ storage, filename, versionName, content }) => {
|
|
181
|
+
const name = getVersionName(versionName)
|
|
182
|
+
|
|
183
|
+
setProperty(storage, filename, { [name]: content })
|
|
184
|
+
}
|
|
185
|
+
// 获取版本文件内容
|
|
186
|
+
const getVersionContent = ({ storage, filename, versionName }) => {
|
|
187
|
+
const name = getVersionName(versionName)
|
|
188
|
+
|
|
189
|
+
return getPropertyAttr(storage, filename, name)
|
|
190
|
+
}
|
|
191
|
+
// 删除版本
|
|
192
|
+
const deleteVersion = ({ storage, filename, versionName }) => {
|
|
193
|
+
const name = getVersionName(versionName)
|
|
194
|
+
|
|
195
|
+
removePropertyAttr(storage, filename, name)
|
|
196
|
+
}
|
|
197
|
+
// 更新版本名称
|
|
198
|
+
const updateVersionName = ({ storage, filename, versionName, newVersion }) => {
|
|
199
|
+
const file = getVersionContent(versionName)
|
|
200
|
+
|
|
201
|
+
addNewVersion(storage, filename, { versionName: newVersion, file })
|
|
202
|
+
deleteVersion({ storage, filename, versionName })
|
|
203
|
+
}
|
|
204
|
+
const getVersions = ({ storage, filename }) => {
|
|
205
|
+
const props = getProperty(storage, filename)
|
|
206
|
+
const list = []
|
|
207
|
+
|
|
208
|
+
Object.keys(props).forEach((key) => {
|
|
209
|
+
const matchs = key.match(/^version\.{1}(.+)/)
|
|
210
|
+
|
|
211
|
+
if (matchs && matchs[1]) {
|
|
212
|
+
list.push({
|
|
213
|
+
filename: matchs[1],
|
|
214
|
+
content: props[key],
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return list
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const withTryCatch = (fn) => {
|
|
223
|
+
return async (...args) => {
|
|
224
|
+
try {
|
|
225
|
+
return await fn(...args)
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error('全局捕获异常:', e)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
exports.addNewVersion = addNewVersion
|
|
233
|
+
exports.updateVersionContent = updateVersionContent
|
|
234
|
+
exports.deleteVersion = deleteVersion
|
|
235
|
+
exports.updateVersionName = updateVersionName
|
|
236
|
+
exports.getVersions = getVersions
|
|
237
|
+
exports.setProperty = setProperty
|
|
238
|
+
exports.getProperty = getProperty
|
|
239
|
+
exports.getPropertyAttr = getPropertyAttr
|
|
240
|
+
exports.getVersionContent = getVersionContent
|
|
241
|
+
exports.removeProperty = removeProperty
|
|
242
|
+
exports.removePropertyAttr = removePropertyAttr
|
|
243
|
+
exports.writeFile = writeFile
|
|
244
|
+
exports.getApiList = getApiList
|
|
245
|
+
exports.updateFile = updateFile
|
|
246
|
+
exports.readFile = readFile
|
|
247
|
+
exports.removeFile = removeFile
|
|
248
|
+
exports.getFilename = getFilename
|
|
249
|
+
exports.getApiListUpdated = getApiListUpdated
|
|
250
|
+
exports.setApiListUpdated = setApiListUpdated
|
|
251
|
+
exports.getRule = getRule
|
|
252
|
+
exports.isJsonReq = isJsonReq
|
|
253
|
+
|
|
254
|
+
exports.withTryCatch = withTryCatch
|
|
255
|
+
|
|
256
|
+
exports.handleBuffer2String = withTryCatch(({ body, encoding }) => {
|
|
257
|
+
switch (encoding) {
|
|
258
|
+
case 'gzip':
|
|
259
|
+
body = zlib.gunzipSync(body)
|
|
260
|
+
break
|
|
261
|
+
case 'deflate':
|
|
262
|
+
body = zlib.inflateSync(body)
|
|
263
|
+
break
|
|
264
|
+
case 'br':
|
|
265
|
+
body = zlib.brotliDecompressSync(body)
|
|
266
|
+
break
|
|
267
|
+
default:
|
|
268
|
+
break
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const type = Object.prototype.toString.call(body)
|
|
272
|
+
let content
|
|
273
|
+
switch (type) {
|
|
274
|
+
case '[object String]':
|
|
275
|
+
content = body
|
|
276
|
+
break
|
|
277
|
+
case '[object Object]':
|
|
278
|
+
content = JSON.stringify(body)
|
|
279
|
+
break
|
|
280
|
+
case '[object Uint8Array]':
|
|
281
|
+
content = body.toString()
|
|
282
|
+
break
|
|
283
|
+
default:
|
|
284
|
+
content = body
|
|
285
|
+
}
|
|
286
|
+
return content
|
|
287
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "whistle.mockbubu",
|
|
3
|
+
"version": "1.0.0-dev.1",
|
|
4
|
+
"description": "mock response data",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"lint": "eslint . --ext .js",
|
|
7
|
+
"lint:fix": "eslint . --ext .js --fix",
|
|
8
|
+
"lint:check": "eslint . --ext .js --max-warnings 0"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"koa": "^2.7.0",
|
|
12
|
+
"koa-bodyparser": "^4.2.1",
|
|
13
|
+
"koa-onerror": "^4.1.0",
|
|
14
|
+
"koa-router": "^7.4.0",
|
|
15
|
+
"koa-static": "^5.0.0",
|
|
16
|
+
"koa2-cors": "^2.0.6",
|
|
17
|
+
"qs": "^6.10.3",
|
|
18
|
+
"string_decoder": "^1.3.0"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib/*",
|
|
22
|
+
"public/*",
|
|
23
|
+
"rules.txt",
|
|
24
|
+
".editorconfig",
|
|
25
|
+
".gitignore",
|
|
26
|
+
"index.js",
|
|
27
|
+
"package.json",
|
|
28
|
+
"README.md",
|
|
29
|
+
"step1.png",
|
|
30
|
+
"step2.png"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"proxy",
|
|
34
|
+
"mock",
|
|
35
|
+
"whistle"
|
|
36
|
+
],
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"eslint": "^8.0.0",
|
|
39
|
+
"eslint-config-standard": "^17.0.0",
|
|
40
|
+
"eslint-plugin-import": "^2.27.0",
|
|
41
|
+
"eslint-plugin-node": "^11.1.0",
|
|
42
|
+
"eslint-plugin-promise": "^6.1.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
Binary file
|
|
Binary file
|