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/server.js
CHANGED
|
@@ -1,63 +1,180 @@
|
|
|
1
1
|
const {
|
|
2
|
-
getProperty,
|
|
3
|
-
setProperty,
|
|
4
2
|
getFilename,
|
|
5
3
|
getRule,
|
|
6
4
|
isJsonReq,
|
|
7
5
|
setApiListUpdated,
|
|
8
|
-
getVersionContent,
|
|
9
6
|
handleBuffer2String,
|
|
10
7
|
withTryCatch,
|
|
11
8
|
} = require('./utils')
|
|
12
|
-
const
|
|
9
|
+
const GroupManager = require('./group-manager')
|
|
10
|
+
const StorageAdapter = require('./storage-adapter')
|
|
13
11
|
|
|
14
12
|
module.exports = (server, { storage }) => {
|
|
15
|
-
|
|
13
|
+
console.log('[mockbubu] 🚀 server.js 模块已加载!')
|
|
14
|
+
|
|
15
|
+
// 使用 StorageAdapter 包装 Whistle storage
|
|
16
|
+
const actualBaseDir = storage.baseDir || storage._options?.baseDir
|
|
17
|
+
const storageAdapter = new StorageAdapter({ baseDir: actualBaseDir })
|
|
18
|
+
|
|
19
|
+
// 初始化组管理器(使用适配器)
|
|
20
|
+
const groupManager = new GroupManager(storageAdapter)
|
|
21
|
+
|
|
22
|
+
// ✅ 修复 Issue #3: 添加捕获中标志,防止并发重复捕获
|
|
23
|
+
// Key: `${groupId}/${filename}`, Value: Promise (捕获完成时 resolve)
|
|
24
|
+
const capturingFiles = new Map()
|
|
25
|
+
|
|
26
|
+
// 同步初始化:确保初始化完成后再处理请求
|
|
27
|
+
let isInitialized = false
|
|
28
|
+
storageAdapter.init().then(async () => {
|
|
29
|
+
console.log('[mockbubu] ✅ V3 Storage 初始化完成')
|
|
30
|
+
// 确保默认组存在
|
|
31
|
+
await groupManager.ensureDefaultGroup()
|
|
32
|
+
isInitialized = true
|
|
33
|
+
}).catch(err => {
|
|
34
|
+
console.error('[mockbubu] ❌ V3 Storage 初始化失败:', err)
|
|
35
|
+
isInitialized = true // 即使失败也标记为已初始化,避免阻塞
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
server.on('request', async (req, res) => {
|
|
39
|
+
// 等待初始化完成
|
|
40
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
41
|
+
while (!isInitialized) {
|
|
42
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
43
|
+
}
|
|
16
44
|
const { originalReq, method } = req
|
|
17
45
|
const { headers, ruleValue, url, pattern } = originalReq
|
|
18
46
|
const filename = getFilename(originalReq)
|
|
19
47
|
const rule = getRule(originalReq)
|
|
20
48
|
|
|
49
|
+
// 调试日志:记录所有请求
|
|
50
|
+
if (url.includes('trend_card')) {
|
|
51
|
+
console.log('[mockbubu] 🔍 [trend_card] 收到请求')
|
|
52
|
+
console.log(`[mockbubu] 🔍 [trend_card] URL: ${url}`)
|
|
53
|
+
console.log(`[mockbubu] 🔍 [trend_card] Accept: ${headers.accept}`)
|
|
54
|
+
console.log(`[mockbubu] 🔍 [trend_card] Sec-Fetch-Dest: ${headers['sec-fetch-dest'] || headers['Sec-Fetch-Dest']}`)
|
|
55
|
+
console.log('[mockbubu] 🔍 [trend_card] 完整 Headers:', JSON.stringify(headers, null, 2))
|
|
56
|
+
console.log(`[mockbubu] 🔍 [trend_card] isJsonReq 判断结果: ${isJsonReq(headers)}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
21
59
|
// 非json请求直接透传
|
|
22
|
-
if (!isJsonReq(headers))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
32
|
-
setProperty(storage, filename, {
|
|
33
|
-
query: qs.parse(new URL(url).searchParams.toString()),
|
|
34
|
-
})
|
|
60
|
+
if (!isJsonReq(headers)) {
|
|
61
|
+
console.log(`[mockbubu] ⏭️ 非 JSON 请求已跳过 | File: ${filename}`)
|
|
62
|
+
console.log(`[mockbubu] 📋 Headers: Accept=${headers.accept}, Sec-Fetch-Dest=${headers['sec-fetch-dest'] || headers['Sec-Fetch-Dest']}`)
|
|
63
|
+
return req.passThrough()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`[mockbubu] 🎯 JSON 请求,开始捕获 | URL: ${url}`)
|
|
67
|
+
|
|
68
|
+
// V3 架构:不再写入 query/payload 到 properties(无用数据)
|
|
35
69
|
|
|
36
70
|
try {
|
|
37
|
-
|
|
38
|
-
const
|
|
71
|
+
// 获取当前激活的组ID
|
|
72
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
73
|
+
// 读取该组的配置
|
|
74
|
+
const groupConfig = await groupManager.getGroupFileConfig(currentGroupId, filename)
|
|
75
|
+
const { mock, mockVersion } = groupConfig
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
// 从组目录读取缓存文件
|
|
78
|
+
const filePath = `${currentGroupId}/${filename}`
|
|
79
|
+
const sessionCache = await storageAdapter.readFile(filePath)
|
|
80
|
+
|
|
81
|
+
if (mock && sessionCache) {
|
|
82
|
+
// Mock: true 且有缓存,返回模拟数据
|
|
83
|
+
const resCache = JSON.parse(sessionCache)?.res
|
|
84
|
+
const { statusCode, statusMessage, headers } = resCache
|
|
85
|
+
|
|
86
|
+
headers['from-res-cache'] = 'true'
|
|
87
|
+
delete headers['content-encoding']
|
|
88
|
+
delete headers['content-length']
|
|
89
|
+
res.writeHead(statusCode, statusMessage, headers)
|
|
90
|
+
|
|
91
|
+
if (mockVersion) {
|
|
92
|
+
// 从当前组配置读取版本内容
|
|
93
|
+
const mockVersionContent = await groupManager.getGroupVersionContent(currentGroupId, filename, mockVersion)
|
|
94
|
+
if (mockVersionContent) {
|
|
51
95
|
res.end(JSON.stringify(mockVersionContent))
|
|
52
96
|
} else {
|
|
97
|
+
// 版本不存在时降级到原始数据
|
|
53
98
|
res.end(resCache.body)
|
|
54
99
|
}
|
|
100
|
+
} else {
|
|
101
|
+
res.end(resCache.body)
|
|
55
102
|
}
|
|
56
|
-
} else {
|
|
57
|
-
//
|
|
58
|
-
|
|
103
|
+
} else if (mock && !sessionCache) {
|
|
104
|
+
// Mock: true 但无缓存,需要先捕获数据
|
|
105
|
+
const captureKey = `${currentGroupId}/${filename}`
|
|
59
106
|
|
|
60
|
-
//
|
|
107
|
+
// ✅ 修复:检查文件是否已存在,如果存在则直接透传,不重新捕获
|
|
108
|
+
// 设计原则:同一个URL只捕获一次,用户如需更新数据,应先删除旧文件
|
|
109
|
+
const configKey = groupManager.getGroupConfigKey(currentGroupId, filename)
|
|
110
|
+
const hasConfig = await storageAdapter.hasFileInIndex(configKey)
|
|
111
|
+
|
|
112
|
+
if (hasConfig) {
|
|
113
|
+
// 文件已存在,直接透传请求,不重新捕获
|
|
114
|
+
console.log(`[mockbubu] ⏭️ 文件已存在,跳过捕获并透传: ${filename}`)
|
|
115
|
+
return req.passThrough()
|
|
116
|
+
} else {
|
|
117
|
+
// 文件不存在 - 首次捕获
|
|
118
|
+
console.log(`[mockbubu] 🎣 首次捕获: ${filename}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ✅ 修复 Issue #3: 检查是否正在捕获中
|
|
122
|
+
if (capturingFiles.has(captureKey)) {
|
|
123
|
+
// 正在捕获中,等待捕获完成
|
|
124
|
+
console.log(`[mockbubu] ⏳ 检测到并发请求,等待捕获完成: ${filename}`)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// 等待第一个请求捕获完成(最多等待10秒)
|
|
128
|
+
await Promise.race([
|
|
129
|
+
capturingFiles.get(captureKey),
|
|
130
|
+
new Promise((_resolve, reject) => setTimeout(() => reject(new Error('捕获超时')), 10000)),
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
// 捕获完成,重新读取缓存
|
|
134
|
+
const freshCache = await storageAdapter.readFile(filePath)
|
|
135
|
+
if (freshCache) {
|
|
136
|
+
console.log(`[mockbubu] ✅ 等待成功,返回捕获的数据: ${filename}`)
|
|
137
|
+
const resCache = JSON.parse(freshCache)?.res
|
|
138
|
+
const { statusCode, statusMessage = '', headers } = resCache
|
|
139
|
+
|
|
140
|
+
headers['from-res-cache'] = 'true'
|
|
141
|
+
delete headers['content-encoding']
|
|
142
|
+
delete headers['content-length']
|
|
143
|
+
res.writeHead(statusCode, statusMessage, headers)
|
|
144
|
+
|
|
145
|
+
// 检查是否需要使用版本数据
|
|
146
|
+
if (mockVersion) {
|
|
147
|
+
const mockVersionContent = await groupManager.getGroupVersionContent(currentGroupId, filename, mockVersion)
|
|
148
|
+
if (mockVersionContent) {
|
|
149
|
+
res.end(JSON.stringify(mockVersionContent))
|
|
150
|
+
} else {
|
|
151
|
+
res.end(resCache.body)
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
res.end(resCache.body)
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
} else {
|
|
158
|
+
// 等待后仍无缓存,降级到透传
|
|
159
|
+
console.warn(`[mockbubu] ⚠️ 等待后仍无缓存,降级透传: ${filename}`)
|
|
160
|
+
return req.passThrough()
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
// 等待超时或失败,降级到透传
|
|
164
|
+
console.error(`[mockbubu] ❌ 等待捕获失败,降级透传: ${filename}`, err.message)
|
|
165
|
+
return req.passThrough()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ✅ 修复 Issue #3: 第一个请求,设置捕获标志
|
|
170
|
+
let resolveCapture
|
|
171
|
+
const capturePromise = new Promise(resolve => { resolveCapture = resolve })
|
|
172
|
+
capturingFiles.set(captureKey, capturePromise)
|
|
173
|
+
|
|
174
|
+
// 传递 hasConfig 标志到捕获逻辑中,避免重复检查
|
|
175
|
+
const isFirstCapture = !hasConfig
|
|
176
|
+
|
|
177
|
+
// 无缓存,捕获最新的接口数据
|
|
61
178
|
const client = req.request((svrRes) => {
|
|
62
179
|
const encoding = svrRes.headers['content-encoding']
|
|
63
180
|
let body
|
|
@@ -66,34 +183,190 @@ module.exports = (server, { storage }) => {
|
|
|
66
183
|
body = body ? Buffer.concat([body, data]) : data
|
|
67
184
|
})
|
|
68
185
|
svrRes.on('end', withTryCatch(async () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
186
|
+
try {
|
|
187
|
+
if (!body) return
|
|
188
|
+
|
|
189
|
+
const content = await handleBuffer2String({ body, encoding })
|
|
190
|
+
// 获取完整的抓包数据,要等待响应完成
|
|
191
|
+
req.getSession(async (session) => {
|
|
192
|
+
try {
|
|
193
|
+
// 如果设置了 enable://hide 会获取到空数据
|
|
194
|
+
if (!session) {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 完全隔离架构:将元数据和配置写入当前组
|
|
199
|
+
// ✅ 修复 Issue #4: 使用外层已检查的 isFirstCapture,避免重复检查导致逻辑不一致
|
|
200
|
+
|
|
201
|
+
if (isFirstCapture) {
|
|
202
|
+
// 首次捕获:创建完整配置(保持当前 mock 状态)
|
|
203
|
+
const configToSet = {
|
|
204
|
+
method,
|
|
205
|
+
rule,
|
|
206
|
+
status: session.res.statusCode,
|
|
207
|
+
pattern,
|
|
208
|
+
ruleValue: ruleValue || 'pathname',
|
|
209
|
+
url: filename, // ✅ 修复:使用 filename(不带查询参数),而不是完整的 url
|
|
210
|
+
date: Date.now(),
|
|
211
|
+
mock, // 使用当前的 mock 状态(true)
|
|
212
|
+
locked: false,
|
|
213
|
+
mockVersion: null,
|
|
214
|
+
mockTime: null,
|
|
215
|
+
}
|
|
216
|
+
await groupManager.setGroupFileConfig(currentGroupId, filename, configToSet)
|
|
217
|
+
} else {
|
|
218
|
+
// 已存在配置,仅更新元数据(不覆盖 mock、locked 等用户配置)
|
|
219
|
+
const existingGroupConfig = await groupManager.getGroupFileConfig(currentGroupId, filename)
|
|
220
|
+
await groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
221
|
+
...existingGroupConfig,
|
|
222
|
+
method,
|
|
223
|
+
rule,
|
|
224
|
+
status: session.res.statusCode,
|
|
225
|
+
pattern,
|
|
226
|
+
ruleValue: ruleValue || 'pathname',
|
|
227
|
+
url: filename, // ✅ 修复:使用 filename(不带查询参数),而不是完整的 url
|
|
228
|
+
date: Date.now(),
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 将文件写入组目录
|
|
233
|
+
const tempSession = JSON.parse(JSON.stringify(session))
|
|
234
|
+
tempSession.res.body = content
|
|
235
|
+
const groupFilePath = `${currentGroupId}/${filename}`
|
|
236
|
+
await storageAdapter.writeFile(groupFilePath, JSON.stringify(tempSession))
|
|
237
|
+
|
|
238
|
+
await setApiListUpdated(storage, true)
|
|
239
|
+
|
|
240
|
+
console.log(`[mockbubu] ✅ 捕获完成: ${filename}`)
|
|
241
|
+
} finally {
|
|
242
|
+
// ✅ 修复 Issue #3: 捕获完成,清除标志并通知等待的请求
|
|
243
|
+
resolveCapture()
|
|
244
|
+
capturingFiles.delete(captureKey)
|
|
245
|
+
}
|
|
89
246
|
})
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.error(`[mockbubu] ❌ 捕获过程出错: ${filename}`, err)
|
|
249
|
+
// 出错也要清除标志
|
|
250
|
+
resolveCapture()
|
|
251
|
+
capturingFiles.delete(captureKey)
|
|
252
|
+
}
|
|
253
|
+
}))
|
|
254
|
+
|
|
255
|
+
// 将响应透传给客户端
|
|
256
|
+
svrRes.pipe(res)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
req.pipe(client)
|
|
260
|
+
} else {
|
|
261
|
+
// mock: false,已有缓存则直接透传,无缓存则捕获
|
|
262
|
+
if (sessionCache) {
|
|
263
|
+
return req.passThrough()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ✅ 修复 Issue #3: mock=false 时也要避免重复捕获
|
|
267
|
+
const captureKey = `${currentGroupId}/${filename}`
|
|
268
|
+
|
|
269
|
+
// ✅ 修复:检查文件是否已存在,如果存在则直接透传,不重新捕获
|
|
270
|
+
// 设计原则:同一个URL只捕获一次,用户如需更新数据,应先删除旧文件
|
|
271
|
+
const configKey = groupManager.getGroupConfigKey(currentGroupId, filename)
|
|
272
|
+
const hasConfig = await storageAdapter.hasFileInIndex(configKey)
|
|
273
|
+
|
|
274
|
+
if (hasConfig) {
|
|
275
|
+
// 文件已存在,直接透传请求,不重新捕获
|
|
276
|
+
console.log(`[mockbubu] ⏭️ 文件已存在(mock=false),跳过捕获并透传: ${filename}`)
|
|
277
|
+
return req.passThrough()
|
|
278
|
+
} else {
|
|
279
|
+
// 文件不存在 - 首次捕获
|
|
280
|
+
console.log(`[mockbubu] 🎣 首次捕获(mock=false): ${filename}`)
|
|
281
|
+
}
|
|
90
282
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
283
|
+
if (capturingFiles.has(captureKey)) {
|
|
284
|
+
// 正在捕获中,直接透传(mock=false 不需要等待)
|
|
285
|
+
console.log(`[mockbubu] ⏳ 检测到并发请求(mock=false),直接透传: ${filename}`)
|
|
286
|
+
return req.passThrough()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ✅ 修复 Issue #3: 设置捕获标志
|
|
290
|
+
let resolveCapture
|
|
291
|
+
const capturePromise = new Promise(resolve => { resolveCapture = resolve })
|
|
292
|
+
capturingFiles.set(captureKey, capturePromise)
|
|
293
|
+
|
|
294
|
+
// 传递 hasConfig 标志到捕获逻辑中,避免重复检查
|
|
295
|
+
const isFirstCapture = !hasConfig
|
|
94
296
|
|
|
95
|
-
|
|
96
|
-
|
|
297
|
+
// 无缓存,捕获最新的接口数据
|
|
298
|
+
const client = req.request((svrRes) => {
|
|
299
|
+
const encoding = svrRes.headers['content-encoding']
|
|
300
|
+
let body
|
|
301
|
+
|
|
302
|
+
svrRes.on('data', (data) => {
|
|
303
|
+
body = body ? Buffer.concat([body, data]) : data
|
|
304
|
+
})
|
|
305
|
+
svrRes.on('end', withTryCatch(async () => {
|
|
306
|
+
try {
|
|
307
|
+
if (!body) return
|
|
308
|
+
|
|
309
|
+
const content = await handleBuffer2String({ body, encoding })
|
|
310
|
+
// 获取完整的抓包数据,要等待响应完成
|
|
311
|
+
req.getSession(async (session) => {
|
|
312
|
+
try {
|
|
313
|
+
// 如果设置了 enable://hide 会获取到空数据
|
|
314
|
+
if (!session) {
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 完全隔离架构:将元数据和配置写入当前组
|
|
319
|
+
// ✅ 修复 Issue #4: 使用外层已检查的 isFirstCapture,避免重复检查导致逻辑不一致
|
|
320
|
+
|
|
321
|
+
if (isFirstCapture) {
|
|
322
|
+
// 首次捕获:创建完整配置
|
|
323
|
+
await groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
324
|
+
method,
|
|
325
|
+
rule,
|
|
326
|
+
status: session.res.statusCode,
|
|
327
|
+
pattern,
|
|
328
|
+
ruleValue: ruleValue || 'pathname',
|
|
329
|
+
url: filename, // ✅ 修复:使用 filename(不带查询参数),而不是完整的 url
|
|
330
|
+
date: Date.now(),
|
|
331
|
+
mock: false,
|
|
332
|
+
locked: false,
|
|
333
|
+
mockVersion: null,
|
|
334
|
+
mockTime: null,
|
|
335
|
+
})
|
|
336
|
+
} else {
|
|
337
|
+
// 已存在配置,仅更新元数据(不覆盖 mock、locked 等用户配置)
|
|
338
|
+
const existingGroupConfig = await groupManager.getGroupFileConfig(currentGroupId, filename)
|
|
339
|
+
await groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
340
|
+
...existingGroupConfig,
|
|
341
|
+
method,
|
|
342
|
+
rule,
|
|
343
|
+
status: session.res.statusCode,
|
|
344
|
+
pattern,
|
|
345
|
+
ruleValue: ruleValue || 'pathname',
|
|
346
|
+
url: filename, // ✅ 修复:使用 filename(不带查询参数),而不是完整的 url
|
|
347
|
+
date: Date.now(),
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 将文件写入组目录
|
|
352
|
+
const tempSession = JSON.parse(JSON.stringify(session))
|
|
353
|
+
tempSession.res.body = content
|
|
354
|
+
const groupFilePath = `${currentGroupId}/${filename}`
|
|
355
|
+
await storageAdapter.writeFile(groupFilePath, JSON.stringify(tempSession))
|
|
356
|
+
|
|
357
|
+
await setApiListUpdated(storage, true)
|
|
358
|
+
} finally {
|
|
359
|
+
// ✅ 修复 Issue #3: 捕获完成,清除标志
|
|
360
|
+
resolveCapture()
|
|
361
|
+
capturingFiles.delete(captureKey)
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
} catch (err) {
|
|
365
|
+
console.error(`[mockbubu] ❌ 捕获过程出错: ${filename}`, err)
|
|
366
|
+
// 出错也要清除标志
|
|
367
|
+
resolveCapture()
|
|
368
|
+
capturingFiles.delete(captureKey)
|
|
369
|
+
}
|
|
97
370
|
}))
|
|
98
371
|
|
|
99
372
|
// 将响应透传给客户端
|