whistle.mockbubu 1.0.0-dev.4 → 2.0.0-beta.0
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/.gitignore +4 -0
- package/CHANGELOG_GROUP_FEATURE.md +468 -0
- package/CHANGELOG_P0_FIXES.md +412 -0
- package/CHANGELOG_P1_OPTIMIZATIONS.md +292 -0
- package/CLAUDE.md +469 -0
- package/GROUP_FEATURE_DESIGN.md +520 -0
- package/README.md +106 -0
- package/lib/archive-utils.js +332 -0
- package/lib/const.js +19 -0
- package/lib/group-manager.js +660 -0
- package/lib/migration-v3.js +321 -0
- package/lib/server.js +333 -60
- package/lib/storage-adapter.js +518 -0
- package/lib/storage-v3.js +1368 -0
- package/lib/uiServer/index.js +76 -5
- package/lib/uiServer/router/group-router.js +218 -0
- package/lib/uiServer/router/index.js +1074 -51
- package/lib/uiServer/router/version-router.js +208 -63
- package/lib/uiServer/util.js +74 -16
- package/lib/uiServer/validator.js +105 -0
- package/lib/utils.js +107 -171
- package/package.json +1 -1
- package/public/js/app.js +5216 -1379
- package/public/js/app.js.map +1 -1
- package/public/js/chunk-vendors.js +14179 -8217
- package/public/js/chunk-vendors.js.map +1 -1
- package/rules.txt +1 -1
- package//346/212/200/346/234/257/346/226/271/346/241/210.md +452 -0
- package//346/265/213/350/257/225/346/215/225/350/216/267/345/212/237/350/203/275/346/255/245/351/252/244.md +145 -0
package/lib/utils.js
CHANGED
|
@@ -1,124 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whistle.mockbubu 工具函数集
|
|
3
|
+
* V3 架构:简化版本,移除废弃的版本管理函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const zlib = require('zlib')
|
|
2
7
|
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
8
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
// ========================================
|
|
10
|
+
// 文件操作函数
|
|
11
|
+
// ========================================
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
// ✅ 按组获取文件列表
|
|
14
|
+
const getGroupFileList = async (storage, groupId) => {
|
|
15
|
+
if (!groupId) {
|
|
16
|
+
console.warn('[mockbubu] getGroupFileList: groupId 为空,返回空数组')
|
|
17
|
+
return []
|
|
26
18
|
}
|
|
27
|
-
}
|
|
28
|
-
const getPropertyAttr = (storage, property, attrName) => {
|
|
29
|
-
const props = storage.getProperty(property)
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
const
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
break
|
|
68
|
-
default:
|
|
69
|
-
content = body
|
|
20
|
+
try {
|
|
21
|
+
// 获取所有文件列表
|
|
22
|
+
// StorageAdapter 返回的格式已经是: groupId/filename (不需要解码)
|
|
23
|
+
const allFiles = await storage.getFileList() || []
|
|
24
|
+
const groupPrefix = `${groupId}/`
|
|
25
|
+
const result = []
|
|
26
|
+
|
|
27
|
+
// 过滤出属于该组的文件
|
|
28
|
+
allFiles.forEach((item) => {
|
|
29
|
+
try {
|
|
30
|
+
// StorageAdapter 返回的文件名格式: groupId/filename
|
|
31
|
+
// 例如: default/https://m.shein.com/us/bff-api/...
|
|
32
|
+
// 不需要任何解码,直接使用
|
|
33
|
+
const name = item.name
|
|
34
|
+
|
|
35
|
+
// 检查是否以 "groupId/" 开头
|
|
36
|
+
if (name.startsWith(groupPrefix)) {
|
|
37
|
+
// 提取纯文件名(去除 groupId/ 前缀)
|
|
38
|
+
const filename = name.substring(groupPrefix.length)
|
|
39
|
+
|
|
40
|
+
result.push({
|
|
41
|
+
name: filename, // 返回纯文件名,不包含 groupId 前缀
|
|
42
|
+
size: item.size || 0,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// 处理失败时跳过该文件
|
|
47
|
+
console.warn(`[mockbubu] 处理文件失败: ${item.name}`, err.message)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('[mockbubu] getGroupFileList 执行失败:', error.message)
|
|
54
|
+
console.error('[mockbubu] 错误堆栈:', error.stack)
|
|
55
|
+
return []
|
|
70
56
|
}
|
|
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
57
|
}
|
|
92
58
|
|
|
93
|
-
const updateFile = (storage, filename, body) => {
|
|
59
|
+
const updateFile = (storage, filename, body, groupId) => {
|
|
94
60
|
if (!filename) {
|
|
95
61
|
throw new Error('文件名不能为空')
|
|
96
62
|
}
|
|
97
|
-
// storage.writeFile(filename, JSON.stringify(body))
|
|
98
63
|
|
|
99
|
-
const
|
|
64
|
+
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
65
|
+
const sessionCache = storage.readFile(filePath)
|
|
100
66
|
if (sessionCache) {
|
|
101
67
|
const session = JSON.parse(sessionCache)
|
|
102
68
|
session.res.body = JSON.stringify(body)
|
|
103
|
-
storage.writeFile(
|
|
69
|
+
storage.writeFile(filePath, JSON.stringify(session))
|
|
104
70
|
}
|
|
105
71
|
}
|
|
106
72
|
|
|
107
|
-
const readFile = (storage, filename) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const sessionCache = storage.readFile(filename)
|
|
73
|
+
const readFile = async (storage, filename, groupId) => {
|
|
74
|
+
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
75
|
+
const sessionCache = await storage.readFile(filePath)
|
|
111
76
|
if (sessionCache) {
|
|
112
77
|
const resCache = JSON.parse(sessionCache)?.res
|
|
113
|
-
console.log(resCache)
|
|
114
78
|
return resCache.body
|
|
115
79
|
}
|
|
116
80
|
}
|
|
117
81
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
82
|
+
// 读取完整的 session 数据(包含 req 和 res)
|
|
83
|
+
const readSession = async (storage, filename, groupId) => {
|
|
84
|
+
const filePath = groupId ? `${groupId}/${filename}` : filename
|
|
85
|
+
const sessionCache = await storage.readFile(filePath)
|
|
86
|
+
if (sessionCache) {
|
|
87
|
+
return JSON.parse(sessionCache)
|
|
88
|
+
}
|
|
89
|
+
return null
|
|
121
90
|
}
|
|
91
|
+
|
|
92
|
+
// ========================================
|
|
93
|
+
// URL 和请求处理函数
|
|
94
|
+
// ========================================
|
|
95
|
+
|
|
122
96
|
// 获取mock文件名
|
|
123
97
|
const getFilename = (originalReq) => {
|
|
124
98
|
const { url, ruleValue, pattern } = originalReq
|
|
@@ -142,11 +116,12 @@ const getFilename = (originalReq) => {
|
|
|
142
116
|
return filename
|
|
143
117
|
}
|
|
144
118
|
|
|
145
|
-
const getApiListUpdated = (storage) => {
|
|
146
|
-
|
|
119
|
+
const getApiListUpdated = async (storage) => {
|
|
120
|
+
const value = await storage.getProperty('api-list-updated')
|
|
121
|
+
return !!value
|
|
147
122
|
}
|
|
148
|
-
const setApiListUpdated = (storage, updated) => {
|
|
149
|
-
return storage.setProperty('api-list-updated', updated)
|
|
123
|
+
const setApiListUpdated = async (storage, updated) => {
|
|
124
|
+
return await storage.setProperty('api-list-updated', updated)
|
|
150
125
|
}
|
|
151
126
|
const getRule = (originalReq) => {
|
|
152
127
|
const { pattern, ruleValue } = originalReq
|
|
@@ -155,102 +130,63 @@ const getRule = (originalReq) => {
|
|
|
155
130
|
}
|
|
156
131
|
|
|
157
132
|
const isJsonReq = (headers) => {
|
|
158
|
-
|
|
133
|
+
const result = (
|
|
159
134
|
(headers.accept && headers.accept.includes('application/json')) ||
|
|
160
135
|
headers['sec-fetch-dest'] === 'empty' ||
|
|
161
136
|
headers['Sec-Fetch-Dest'] === 'empty'
|
|
162
137
|
)
|
|
163
|
-
}
|
|
164
138
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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)
|
|
139
|
+
// 调试:所有 m.shein.com 的请求都输出日志
|
|
140
|
+
if (headers.host && headers.host === 'm.shein.com') {
|
|
141
|
+
const fullUrl = headers['x-whistle-full-url'] || 'unknown'
|
|
142
|
+
console.log(`[isJsonReq] m.shein.com 请求: ${decodeURIComponent(fullUrl)}`)
|
|
143
|
+
console.log(' - Accept:', headers.accept)
|
|
144
|
+
console.log(' - sec-fetch-dest:', headers['sec-fetch-dest'])
|
|
145
|
+
console.log(' - Sec-Fetch-Dest:', headers['Sec-Fetch-Dest'])
|
|
146
|
+
console.log(' - 判断结果:', result)
|
|
147
|
+
}
|
|
200
148
|
|
|
201
|
-
|
|
202
|
-
deleteVersion({ storage, filename, versionName })
|
|
149
|
+
return result
|
|
203
150
|
}
|
|
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
151
|
|
|
219
|
-
|
|
220
|
-
|
|
152
|
+
// ========================================
|
|
153
|
+
// 工具函数
|
|
154
|
+
// ========================================
|
|
221
155
|
|
|
222
156
|
const withTryCatch = (fn) => {
|
|
223
157
|
return async (...args) => {
|
|
224
158
|
try {
|
|
225
159
|
return await fn(...args)
|
|
226
160
|
} catch (e) {
|
|
227
|
-
|
|
161
|
+
// 仅在开发环境输出错误日志
|
|
162
|
+
if (process.env.NODE_ENV === 'development') {
|
|
163
|
+
console.error('[mockbubu] 全局捕获异常:', e)
|
|
164
|
+
}
|
|
165
|
+
throw e
|
|
228
166
|
}
|
|
229
167
|
}
|
|
230
168
|
}
|
|
231
169
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
exports.
|
|
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
|
|
170
|
+
// ========================================
|
|
171
|
+
// 导出 - V3 架构使用的函数
|
|
172
|
+
// ========================================
|
|
173
|
+
|
|
174
|
+
// 文件操作
|
|
175
|
+
exports.getGroupFileList = getGroupFileList
|
|
245
176
|
exports.updateFile = updateFile
|
|
246
177
|
exports.readFile = readFile
|
|
247
|
-
exports.
|
|
178
|
+
exports.readSession = readSession
|
|
179
|
+
|
|
180
|
+
// URL 和请求处理
|
|
248
181
|
exports.getFilename = getFilename
|
|
249
|
-
exports.getApiListUpdated = getApiListUpdated
|
|
250
|
-
exports.setApiListUpdated = setApiListUpdated
|
|
251
182
|
exports.getRule = getRule
|
|
252
183
|
exports.isJsonReq = isJsonReq
|
|
253
184
|
|
|
185
|
+
// API 列表更新标志
|
|
186
|
+
exports.getApiListUpdated = getApiListUpdated
|
|
187
|
+
exports.setApiListUpdated = setApiListUpdated
|
|
188
|
+
|
|
189
|
+
// 工具函数
|
|
254
190
|
exports.withTryCatch = withTryCatch
|
|
255
191
|
|
|
256
192
|
exports.handleBuffer2String = withTryCatch(({ body, encoding }) => {
|