whistle.mockbubu 2.0.0 → 2.1.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/README.md +38 -0
- package/index.js +3 -3
- package/lib/config/const.js +138 -0
- package/lib/config/rule-collector.js +81 -0
- package/lib/constants.js +62 -0
- package/lib/core/memory-buffer/index.js +207 -0
- package/lib/core/memory-buffer/shared-instance.js +15 -0
- package/lib/core/plugin-mode-manager.js +74 -0
- package/lib/core/resRulesServer.js +14 -0
- package/lib/core/rulesServer.js +31 -0
- package/lib/core/server-entry/capture-handler.js +191 -0
- package/lib/core/server-entry/request-interceptor.js +82 -0
- package/lib/core/server-entry/response-handler.js +147 -0
- package/lib/core/server-entry/server.js +230 -0
- package/lib/storage/group-manager.js +627 -0
- package/lib/storage/storage-adapter.js +712 -0
- package/lib/storage/storage-v3.js +1418 -0
- package/lib/uiServer/index.js +61 -24
- package/lib/uiServer/router/export/import-export-router.js +459 -0
- package/lib/uiServer/router/files/api-list-router.js +150 -0
- package/lib/uiServer/router/files/batch-operations-router.js +185 -0
- package/lib/uiServer/router/files/file-config-router.js +118 -0
- package/lib/uiServer/router/files/file-crud-router.js +212 -0
- package/lib/uiServer/router/files/file-save-router.js +146 -0
- package/lib/uiServer/router/files/version-router.js +260 -0
- package/lib/uiServer/router/global/plugin-control.js +135 -0
- package/lib/uiServer/router/global/system-info-router.js +386 -0
- package/lib/uiServer/router/{group-router.js → groups/group-router.js} +21 -20
- package/lib/uiServer/router/index.js +38 -1521
- package/lib/uiServer/utils/router-helpers.js +100 -0
- package/lib/uiServer/utils/util.js +172 -0
- package/lib/uiServer/{validator.js → utils/validator.js} +11 -6
- package/lib/utils/archive-utils.js +788 -0
- package/lib/utils/error-handler.js +173 -0
- package/lib/utils/logger.js +79 -0
- package/lib/utils/path-utils.js +147 -0
- package/lib/utils/performance.js +265 -0
- package/lib/utils/utils.js +541 -0
- package/package.json +2 -2
- package/public/js/app.js +3732 -1929
- package/public/js/app.js.map +1 -1
- package/public/js/chunk-vendors.js +5098 -3965
- package/public/js/chunk-vendors.js.map +1 -1
- package/rules.txt +1 -1
- package/CHANGELOG_GROUP_FEATURE.md +0 -468
- package/CHANGELOG_P0_FIXES.md +0 -412
- package/CHANGELOG_P1_OPTIMIZATIONS.md +0 -292
- package/CLAUDE.md +0 -436
- package/GROUP_FEATURE_DESIGN.md +0 -520
- package/lib/const.js +0 -47
- package/lib/group-manager.js +0 -491
- package/lib/resRulesServer.js +0 -9
- package/lib/server.js +0 -249
- package/lib/uiServer/router/version-router.js +0 -205
- package/lib/uiServer/util.js +0 -153
- package/lib/utils.js +0 -409
package/lib/server.js
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
setProperty,
|
|
3
|
-
getFilename,
|
|
4
|
-
getRule,
|
|
5
|
-
isJsonReq,
|
|
6
|
-
setApiListUpdated,
|
|
7
|
-
handleBuffer2String,
|
|
8
|
-
withTryCatch,
|
|
9
|
-
} = require('./utils')
|
|
10
|
-
const qs = require('qs')
|
|
11
|
-
const GroupManager = require('./group-manager')
|
|
12
|
-
|
|
13
|
-
module.exports = (server, { storage }) => {
|
|
14
|
-
// 初始化组管理器
|
|
15
|
-
const groupManager = new GroupManager(storage)
|
|
16
|
-
|
|
17
|
-
// 首次启动时迁移现有数据到默认组
|
|
18
|
-
groupManager.migrateExistingData()
|
|
19
|
-
|
|
20
|
-
server.on('request', (req, res) => {
|
|
21
|
-
const { originalReq, method } = req
|
|
22
|
-
const { headers, ruleValue, url, pattern } = originalReq
|
|
23
|
-
const filename = getFilename(originalReq)
|
|
24
|
-
const rule = getRule(originalReq)
|
|
25
|
-
|
|
26
|
-
// 非json请求直接透传
|
|
27
|
-
if (!isJsonReq(headers)) {
|
|
28
|
-
console.log(`[mockbubu] ⏭️ 非 JSON 请求已跳过 | File: ${filename}`)
|
|
29
|
-
console.log(`[mockbubu] 📋 Headers: Accept=${headers.accept}, Sec-Fetch-Dest=${headers['sec-fetch-dest'] || headers['Sec-Fetch-Dest']}`)
|
|
30
|
-
return req.passThrough()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 获取请求信息 - 这些写入到全局 properties
|
|
34
|
-
let str = ''
|
|
35
|
-
req.on('data', (chunk) => {
|
|
36
|
-
str += chunk
|
|
37
|
-
})
|
|
38
|
-
req.on('end', () => {
|
|
39
|
-
setProperty(storage, filename, { payload: str })
|
|
40
|
-
})
|
|
41
|
-
setProperty(storage, filename, {
|
|
42
|
-
query: qs.parse(new URL(url).searchParams.toString()),
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
// 获取当前激活的组ID
|
|
47
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
48
|
-
// 读取该组的配置
|
|
49
|
-
const groupConfig = groupManager.getGroupFileConfig(currentGroupId, filename)
|
|
50
|
-
const { mock, mockVersion } = groupConfig
|
|
51
|
-
|
|
52
|
-
// 添加调试日志 - 使用一行显示关键信息
|
|
53
|
-
console.log(`[mockbubu] 🔍 REQUEST | File: ${filename} | Group: ${currentGroupId} | Mock: ${mock} | Version: ${mockVersion || 'source'}`)
|
|
54
|
-
console.log('[mockbubu] 📝 Full Config:', JSON.stringify(groupConfig, null, 2))
|
|
55
|
-
|
|
56
|
-
// 从组目录读取缓存文件
|
|
57
|
-
const filePath = `${currentGroupId}/${filename}`
|
|
58
|
-
const sessionCache = storage.readFile(filePath)
|
|
59
|
-
console.log(`[mockbubu] 💾 Cache Status: ${sessionCache ? 'EXISTS' : 'NO CACHE'} | Path: ${filePath}`)
|
|
60
|
-
|
|
61
|
-
if (mock && sessionCache) {
|
|
62
|
-
// Mock: true 且有缓存,返回模拟数据
|
|
63
|
-
const resCache = JSON.parse(sessionCache)?.res
|
|
64
|
-
const { statusCode, statusMessage, headers } = resCache
|
|
65
|
-
|
|
66
|
-
headers['from-res-cache'] = 'true'
|
|
67
|
-
delete headers['content-encoding']
|
|
68
|
-
delete headers['content-length']
|
|
69
|
-
res.writeHead(statusCode, statusMessage, headers)
|
|
70
|
-
|
|
71
|
-
if (mockVersion) {
|
|
72
|
-
// 从当前组配置读取版本内容
|
|
73
|
-
console.log(`[mockbubu] ⚡ 使用版本数据 | Version: ${mockVersion}`)
|
|
74
|
-
const mockVersionContent = groupManager.getGroupVersionContent(currentGroupId, filename, mockVersion)
|
|
75
|
-
if (mockVersionContent) {
|
|
76
|
-
console.log(`[mockbubu] ✅ 版本数据存在,返回版本: ${mockVersion}`)
|
|
77
|
-
res.end(JSON.stringify(mockVersionContent))
|
|
78
|
-
} else {
|
|
79
|
-
// 版本不存在时降级到原始数据
|
|
80
|
-
console.log(`[mockbubu] ⚠️ 版本 ${mockVersion} 不存在,降级到原始数据`)
|
|
81
|
-
res.end(resCache.body)
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
console.log('[mockbubu] ⚡ 使用原始数据 (无版本)')
|
|
85
|
-
res.end(resCache.body)
|
|
86
|
-
}
|
|
87
|
-
} else if (mock && !sessionCache) {
|
|
88
|
-
// Mock: true 但无缓存,需要先捕获数据
|
|
89
|
-
console.log(`[mockbubu] ⚠️ Mock enabled but no cache for ${filename}, need to capture first`)
|
|
90
|
-
// 不要 return,继续往下执行捕获逻辑
|
|
91
|
-
// 无缓存,捕获最新的接口数据
|
|
92
|
-
console.log(`[mockbubu] 🎣 开始捕获(无缓存): ${filename}`)
|
|
93
|
-
const client = req.request((svrRes) => {
|
|
94
|
-
const encoding = svrRes.headers['content-encoding']
|
|
95
|
-
let body
|
|
96
|
-
|
|
97
|
-
svrRes.on('data', (data) => {
|
|
98
|
-
body = body ? Buffer.concat([body, data]) : data
|
|
99
|
-
})
|
|
100
|
-
svrRes.on('end', withTryCatch(async () => {
|
|
101
|
-
if (!body) return
|
|
102
|
-
|
|
103
|
-
const content = await handleBuffer2String({ body, encoding })
|
|
104
|
-
// 获取完整的抓包数据,要等待响应完成
|
|
105
|
-
req.getSession(async (session) => {
|
|
106
|
-
// 如果设置了 enable://hide 会获取到空数据
|
|
107
|
-
if (!session) {
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 完全隔离架构:将元数据和配置写入当前组
|
|
112
|
-
// 检查组配置是否真实存在(而不是默认返回值)
|
|
113
|
-
const configKey = groupManager.getGroupConfigKey(currentGroupId, filename)
|
|
114
|
-
const isFirstCapture = !storage.hasProperty(configKey)
|
|
115
|
-
|
|
116
|
-
if (isFirstCapture) {
|
|
117
|
-
// 首次捕获:创建完整配置(保持当前 mock 状态)
|
|
118
|
-
groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
119
|
-
method,
|
|
120
|
-
rule,
|
|
121
|
-
status: session.res.statusCode,
|
|
122
|
-
pattern,
|
|
123
|
-
ruleValue: ruleValue || 'pathname',
|
|
124
|
-
url,
|
|
125
|
-
date: Date.now(),
|
|
126
|
-
mock, // 使用当前的 mock 状态(true)
|
|
127
|
-
locked: false,
|
|
128
|
-
mockVersion: null,
|
|
129
|
-
mockTime: null,
|
|
130
|
-
})
|
|
131
|
-
console.log(`[mockbubu] ✅ 首次捕获文件到组 ${currentGroupId}: ${filename} | Mock: ${mock}`)
|
|
132
|
-
} else {
|
|
133
|
-
// 已存在配置,仅更新元数据(不覆盖 mock、locked 等用户配置)
|
|
134
|
-
const existingGroupConfig = groupManager.getGroupFileConfig(currentGroupId, filename)
|
|
135
|
-
groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
136
|
-
...existingGroupConfig,
|
|
137
|
-
method,
|
|
138
|
-
rule,
|
|
139
|
-
status: session.res.statusCode,
|
|
140
|
-
pattern,
|
|
141
|
-
ruleValue: ruleValue || 'pathname',
|
|
142
|
-
url,
|
|
143
|
-
date: Date.now(),
|
|
144
|
-
})
|
|
145
|
-
console.log(`[mockbubu] 🔄 更新文件元数据到组 ${currentGroupId}: ${filename}`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 将文件写入组目录
|
|
149
|
-
const tempSession = JSON.parse(JSON.stringify(session))
|
|
150
|
-
tempSession.res.body = content
|
|
151
|
-
const groupFilePath = `${currentGroupId}/${filename}`
|
|
152
|
-
storage.writeFile(groupFilePath, JSON.stringify(tempSession))
|
|
153
|
-
console.log(`[mockbubu] 💾 写入文件: ${groupFilePath}`)
|
|
154
|
-
|
|
155
|
-
setApiListUpdated(storage, true)
|
|
156
|
-
})
|
|
157
|
-
}))
|
|
158
|
-
|
|
159
|
-
// 将响应透传给客户端
|
|
160
|
-
svrRes.pipe(res)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
req.pipe(client)
|
|
164
|
-
} else {
|
|
165
|
-
// mock: false,已有缓存则直接透传,无缓存则捕获
|
|
166
|
-
if (sessionCache) {
|
|
167
|
-
console.log(`[mockbubu] 📤 直接透传(已有缓存): ${filename}`)
|
|
168
|
-
return req.passThrough()
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 无缓存,捕获最新的接口数据
|
|
172
|
-
console.log(`[mockbubu] 🎣 开始捕获(无缓存): ${filename}`)
|
|
173
|
-
const client = req.request((svrRes) => {
|
|
174
|
-
const encoding = svrRes.headers['content-encoding']
|
|
175
|
-
let body
|
|
176
|
-
|
|
177
|
-
svrRes.on('data', (data) => {
|
|
178
|
-
body = body ? Buffer.concat([body, data]) : data
|
|
179
|
-
})
|
|
180
|
-
svrRes.on('end', withTryCatch(async () => {
|
|
181
|
-
if (!body) return
|
|
182
|
-
|
|
183
|
-
const content = await handleBuffer2String({ body, encoding })
|
|
184
|
-
// 获取完整的抓包数据,要等待响应完成
|
|
185
|
-
req.getSession(async (session) => {
|
|
186
|
-
// 如果设置了 enable://hide 会获取到空数据
|
|
187
|
-
if (!session) {
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// 完全隔离架构:将元数据和配置写入当前组
|
|
192
|
-
// 检查组配置是否真实存在(而不是默认返回值)
|
|
193
|
-
const configKey = groupManager.getGroupConfigKey(currentGroupId, filename)
|
|
194
|
-
const isFirstCapture = !storage.hasProperty(configKey)
|
|
195
|
-
|
|
196
|
-
if (isFirstCapture) {
|
|
197
|
-
// 首次捕获:创建完整配置
|
|
198
|
-
groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
199
|
-
method,
|
|
200
|
-
rule,
|
|
201
|
-
status: session.res.statusCode,
|
|
202
|
-
pattern,
|
|
203
|
-
ruleValue: ruleValue || 'pathname',
|
|
204
|
-
url,
|
|
205
|
-
date: Date.now(),
|
|
206
|
-
mock: false,
|
|
207
|
-
locked: false,
|
|
208
|
-
mockVersion: null,
|
|
209
|
-
mockTime: null,
|
|
210
|
-
})
|
|
211
|
-
console.log(`[mockbubu] ✅ 首次捕获文件到组 ${currentGroupId}: ${filename}`)
|
|
212
|
-
} else {
|
|
213
|
-
// 已存在配置,仅更新元数据(不覆盖 mock、locked 等用户配置)
|
|
214
|
-
const existingGroupConfig = groupManager.getGroupFileConfig(currentGroupId, filename)
|
|
215
|
-
groupManager.setGroupFileConfig(currentGroupId, filename, {
|
|
216
|
-
...existingGroupConfig,
|
|
217
|
-
method,
|
|
218
|
-
rule,
|
|
219
|
-
status: session.res.statusCode,
|
|
220
|
-
pattern,
|
|
221
|
-
ruleValue: ruleValue || 'pathname',
|
|
222
|
-
url,
|
|
223
|
-
date: Date.now(),
|
|
224
|
-
})
|
|
225
|
-
console.log(`[mockbubu] 🔄 更新文件元数据到组 ${currentGroupId}: ${filename}`)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// 将文件写入组目录
|
|
229
|
-
const tempSession = JSON.parse(JSON.stringify(session))
|
|
230
|
-
tempSession.res.body = content
|
|
231
|
-
const groupFilePath = `${currentGroupId}/${filename}`
|
|
232
|
-
storage.writeFile(groupFilePath, JSON.stringify(tempSession))
|
|
233
|
-
console.log(`[mockbubu] 💾 写入文件: ${groupFilePath}`)
|
|
234
|
-
|
|
235
|
-
setApiListUpdated(storage, true)
|
|
236
|
-
})
|
|
237
|
-
}))
|
|
238
|
-
|
|
239
|
-
// 将响应透传给客户端
|
|
240
|
-
svrRes.pipe(res)
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
req.pipe(client)
|
|
244
|
-
}
|
|
245
|
-
} catch (error) {
|
|
246
|
-
req.passThrough()
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
const { createErrorResponse, createSuccessResponse } = require('../util')
|
|
2
|
-
const {
|
|
3
|
-
validateFilename,
|
|
4
|
-
validateVersionName,
|
|
5
|
-
validateMockData,
|
|
6
|
-
validate,
|
|
7
|
-
} = require('../validator')
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
module.exports = (router) => {
|
|
11
|
-
// 新增版本
|
|
12
|
-
router.post('/cgi-bin/mockbubu/add-new-version', validate({
|
|
13
|
-
name: validateFilename,
|
|
14
|
-
versionName: validateVersionName,
|
|
15
|
-
content: validateMockData,
|
|
16
|
-
}), (ctx) => {
|
|
17
|
-
try {
|
|
18
|
-
const { groupManager } = ctx
|
|
19
|
-
const { versionName, content = {}, name, description = '' } = ctx.request.body || {}
|
|
20
|
-
|
|
21
|
-
console.log('[mockbubu] 创建版本:', { versionName, name, description })
|
|
22
|
-
|
|
23
|
-
// 获取当前组ID,使用组级别的版本管理
|
|
24
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
25
|
-
groupManager.addGroupVersion(currentGroupId, name, versionName, content, description)
|
|
26
|
-
|
|
27
|
-
// 立即读取已保存的版本,确认时间戳等信息
|
|
28
|
-
const versions = groupManager.getGroupVersions(currentGroupId, name)
|
|
29
|
-
const newVersion = versions.find(v => v.filename === versionName)
|
|
30
|
-
|
|
31
|
-
console.log('[mockbubu] 创建后读取版本:', newVersion)
|
|
32
|
-
|
|
33
|
-
ctx.body = createSuccessResponse(newVersion || {
|
|
34
|
-
filename: versionName,
|
|
35
|
-
content,
|
|
36
|
-
description,
|
|
37
|
-
createTime: Date.now(),
|
|
38
|
-
updateTime: Date.now(),
|
|
39
|
-
}, '版本创建成功')
|
|
40
|
-
} catch (error) {
|
|
41
|
-
ctx.body = createErrorResponse(error.message)
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// 删除版本
|
|
46
|
-
router.post('/cgi-bin/mockbubu/delete-version', validate({
|
|
47
|
-
name: validateFilename,
|
|
48
|
-
versionName: validateVersionName,
|
|
49
|
-
}), (ctx) => {
|
|
50
|
-
try {
|
|
51
|
-
const { groupManager } = ctx
|
|
52
|
-
const { versionName, name } = ctx.request.body || {}
|
|
53
|
-
|
|
54
|
-
// 获取当前组ID和配置
|
|
55
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
56
|
-
const groupConfig = groupManager.getGroupFileConfig(currentGroupId, name)
|
|
57
|
-
|
|
58
|
-
// 如果删除的是当前mock版本,则清除mock版本设置
|
|
59
|
-
if (versionName === groupConfig.mockVersion) {
|
|
60
|
-
groupConfig.mockVersion = null
|
|
61
|
-
groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 使用组级别的版本删除
|
|
65
|
-
groupManager.deleteGroupVersion(currentGroupId, name, versionName)
|
|
66
|
-
|
|
67
|
-
ctx.body = createSuccessResponse(null, '版本删除成功')
|
|
68
|
-
} catch (error) {
|
|
69
|
-
ctx.body = createErrorResponse(error.message)
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// 更新版本内容
|
|
74
|
-
router.post('/cgi-bin/mockbubu/update-version-content', validate({
|
|
75
|
-
name: validateFilename,
|
|
76
|
-
versionName: validateVersionName,
|
|
77
|
-
content: validateMockData,
|
|
78
|
-
}), (ctx) => {
|
|
79
|
-
try {
|
|
80
|
-
const { groupManager } = ctx
|
|
81
|
-
const { versionName, content, name } = ctx.request.body || {}
|
|
82
|
-
|
|
83
|
-
// 获取当前组ID,使用组级别的版本内容更新
|
|
84
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
85
|
-
groupManager.updateGroupVersionContent(currentGroupId, name, versionName, content)
|
|
86
|
-
|
|
87
|
-
ctx.body = createSuccessResponse(null, '版本内容更新成功')
|
|
88
|
-
} catch (error) {
|
|
89
|
-
ctx.body = createErrorResponse(error.message)
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// 更新版本名称(已废弃,使用 update-version-meta 代替)
|
|
94
|
-
router.post('/cgi-bin/mockbubu/update-version-name', validate({
|
|
95
|
-
name: validateFilename,
|
|
96
|
-
versionName: validateVersionName,
|
|
97
|
-
newVersion: validateVersionName,
|
|
98
|
-
}), (ctx) => {
|
|
99
|
-
try {
|
|
100
|
-
const { groupManager } = ctx
|
|
101
|
-
const { versionName, name, newVersion } = ctx.request.body || {}
|
|
102
|
-
|
|
103
|
-
// 获取当前组ID,使用组级别的版本元信息更新
|
|
104
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
105
|
-
const groupConfig = groupManager.getGroupFileConfig(currentGroupId, name)
|
|
106
|
-
|
|
107
|
-
// 获取原描述
|
|
108
|
-
const versions = groupManager.getGroupVersions(currentGroupId, name)
|
|
109
|
-
const oldVersion = versions.find(v => v.filename === versionName)
|
|
110
|
-
const description = oldVersion?.description || ''
|
|
111
|
-
|
|
112
|
-
groupManager.updateGroupVersionMeta(currentGroupId, name, versionName, newVersion, description)
|
|
113
|
-
|
|
114
|
-
// 如果更新的是当前mock版本,需要更新mockVersion属性
|
|
115
|
-
if (versionName === groupConfig.mockVersion && versionName !== newVersion) {
|
|
116
|
-
groupConfig.mockVersion = newVersion
|
|
117
|
-
groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
ctx.body = createSuccessResponse(null, '版本名称更新成功')
|
|
121
|
-
} catch (error) {
|
|
122
|
-
ctx.body = createErrorResponse(error.message)
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
// 获取版本列表
|
|
127
|
-
router.post('/cgi-bin/mockbubu/get-versions', validate({
|
|
128
|
-
name: validateFilename,
|
|
129
|
-
}), (ctx) => {
|
|
130
|
-
try {
|
|
131
|
-
const { groupManager } = ctx
|
|
132
|
-
const { name } = ctx.request.body || {}
|
|
133
|
-
|
|
134
|
-
// 获取当前组ID,使用组级别的版本列表获取
|
|
135
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
136
|
-
const list = groupManager.getGroupVersions(currentGroupId, name) || []
|
|
137
|
-
|
|
138
|
-
console.log('[mockbubu] 获取版本列表:', name, 'group:', currentGroupId, 'count:', list.length)
|
|
139
|
-
if (list.length > 0) {
|
|
140
|
-
console.log('[mockbubu] 第一个版本示例:', list[0])
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
ctx.body = createSuccessResponse(list, '获取版本列表成功')
|
|
144
|
-
} catch (error) {
|
|
145
|
-
ctx.body = createErrorResponse(error.message)
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
// 设置mock的版本
|
|
150
|
-
router.post('/cgi-bin/mockbubu/set-mock-version', validate({
|
|
151
|
-
name: validateFilename,
|
|
152
|
-
versionName: (val) => {
|
|
153
|
-
// versionName 可以为空字符串(表示清除版本)
|
|
154
|
-
if (val === '') return null
|
|
155
|
-
return validateVersionName(val)
|
|
156
|
-
},
|
|
157
|
-
}), (ctx) => {
|
|
158
|
-
try {
|
|
159
|
-
const { groupManager } = ctx
|
|
160
|
-
const { name, versionName } = ctx.request.body || {}
|
|
161
|
-
|
|
162
|
-
// 获取当前组ID和配置
|
|
163
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
164
|
-
const groupConfig = groupManager.getGroupFileConfig(currentGroupId, name)
|
|
165
|
-
|
|
166
|
-
// 更新 mockVersion
|
|
167
|
-
groupConfig.mockVersion = versionName || null
|
|
168
|
-
groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
169
|
-
|
|
170
|
-
ctx.body = createSuccessResponse(null, 'Mock版本设置成功')
|
|
171
|
-
} catch (error) {
|
|
172
|
-
ctx.body = createErrorResponse(error.message)
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
// 更新版本元信息(名称和描述)
|
|
177
|
-
router.post('/cgi-bin/mockbubu/update-version-meta', validate({
|
|
178
|
-
name: validateFilename,
|
|
179
|
-
versionName: validateVersionName,
|
|
180
|
-
newVersionName: validateVersionName,
|
|
181
|
-
}), (ctx) => {
|
|
182
|
-
try {
|
|
183
|
-
const { groupManager } = ctx
|
|
184
|
-
const { name, versionName, newVersionName, description = '' } = ctx.request.body || {}
|
|
185
|
-
|
|
186
|
-
console.log('[mockbubu] 更新版本元信息:', { name, versionName, newVersionName, description })
|
|
187
|
-
|
|
188
|
-
// 获取当前组ID,使用组级别的版本元信息更新
|
|
189
|
-
const currentGroupId = groupManager.getCurrentGroupId()
|
|
190
|
-
groupManager.updateGroupVersionMeta(currentGroupId, name, versionName, newVersionName, description)
|
|
191
|
-
|
|
192
|
-
// 如果更新的是当前mock版本,需要更新mockVersion属性
|
|
193
|
-
const groupConfig = groupManager.getGroupFileConfig(currentGroupId, name)
|
|
194
|
-
if (versionName === groupConfig.mockVersion && versionName !== newVersionName) {
|
|
195
|
-
groupConfig.mockVersion = newVersionName
|
|
196
|
-
groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
ctx.body = createSuccessResponse(null, '版本信息更新成功')
|
|
200
|
-
} catch (error) {
|
|
201
|
-
console.error('[mockbubu] 更新版本元信息失败:', error)
|
|
202
|
-
ctx.body = createErrorResponse(error.message)
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
}
|
package/lib/uiServer/util.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
getApiList,
|
|
3
|
-
getGroupFileList,
|
|
4
|
-
getProperty,
|
|
5
|
-
} = require('../utils')
|
|
6
|
-
const { RangeFilterMap, RuleFilterMap, LockedFilterMap, MethodFilterMap, StatusFilterMap } = require('../const')
|
|
7
|
-
|
|
8
|
-
// 统一错误响应格式
|
|
9
|
-
const createErrorResponse = (message, code = 500) => ({
|
|
10
|
-
code,
|
|
11
|
-
msg: message || '操作失败',
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
// 统一成功响应格式
|
|
15
|
-
const createSuccessResponse = (data = null, message = '操作成功') => ({
|
|
16
|
-
code: 200,
|
|
17
|
-
msg: message,
|
|
18
|
-
...(data && { data }),
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// 执行筛选
|
|
22
|
-
const execFilter = (result, filter) => {
|
|
23
|
-
const { key, value, method } = filter
|
|
24
|
-
const list = result.filter((item) => {
|
|
25
|
-
const type = Object.prototype.toString.call(value)
|
|
26
|
-
|
|
27
|
-
switch (method) {
|
|
28
|
-
case 'indexOf':
|
|
29
|
-
if (type === '[object String]') {
|
|
30
|
-
return ~item[key].indexOf(value)
|
|
31
|
-
}
|
|
32
|
-
break
|
|
33
|
-
case 'equal':
|
|
34
|
-
return item[key] === value
|
|
35
|
-
case 'unequal':
|
|
36
|
-
return item[key] !== value
|
|
37
|
-
case 'range':
|
|
38
|
-
// 范围筛选,value 是 [min, max] 数组
|
|
39
|
-
if (Array.isArray(value) && value.length === 2) {
|
|
40
|
-
const itemValue = item[key]
|
|
41
|
-
return itemValue >= value[0] && itemValue <= value[1]
|
|
42
|
-
}
|
|
43
|
-
break
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return true
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
return list
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 处理筛选条件
|
|
53
|
-
const handleFilterList = (list, filterOptions) => {
|
|
54
|
-
if (!Array.isArray(list) || !filterOptions) return []
|
|
55
|
-
|
|
56
|
-
const filters = []
|
|
57
|
-
const { name, rule, range, ruleValue, locked, method, status } = filterOptions
|
|
58
|
-
if (name) {
|
|
59
|
-
filters.push({
|
|
60
|
-
key: 'name',
|
|
61
|
-
value: name.trim(),
|
|
62
|
-
method: 'indexOf',
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (rule) {
|
|
67
|
-
filters.push({
|
|
68
|
-
key: 'rule',
|
|
69
|
-
value: rule.trim(),
|
|
70
|
-
method: 'equal',
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 范围筛选
|
|
75
|
-
RangeFilterMap[range] && filters.push(RangeFilterMap[range])
|
|
76
|
-
// 规则值筛选
|
|
77
|
-
RuleFilterMap[ruleValue] && filters.push(RuleFilterMap[ruleValue])
|
|
78
|
-
// 锁定筛选
|
|
79
|
-
LockedFilterMap[locked] && filters.push(LockedFilterMap[locked])
|
|
80
|
-
// HTTP Method 筛选
|
|
81
|
-
MethodFilterMap[method] && filters.push(MethodFilterMap[method])
|
|
82
|
-
// 状态码筛选
|
|
83
|
-
StatusFilterMap[status] && filters.push(StatusFilterMap[status])
|
|
84
|
-
|
|
85
|
-
// 执行筛选
|
|
86
|
-
const filteredList = filters.reduce((result, filter) => {
|
|
87
|
-
return execFilter(result, filter)
|
|
88
|
-
}, list)
|
|
89
|
-
|
|
90
|
-
return filteredList
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 过滤组配置,只保留组级别的字段
|
|
95
|
-
* 移除全局元数据字段 (query, payload, method, rule, status, pattern, ruleValue, url, date)
|
|
96
|
-
*/
|
|
97
|
-
const filterGroupConfig = (groupConfig) => {
|
|
98
|
-
if (!groupConfig) return {}
|
|
99
|
-
const validKeys = ['mock', 'locked', 'mockVersion', 'mockTime']
|
|
100
|
-
const filtered = {}
|
|
101
|
-
|
|
102
|
-
Object.keys(groupConfig).forEach(key => {
|
|
103
|
-
// 保留基础配置字段
|
|
104
|
-
if (validKeys.includes(key)) {
|
|
105
|
-
filtered[key] = groupConfig[key]
|
|
106
|
-
} else if (key.startsWith('version.') || key.startsWith('versionMeta.')) {
|
|
107
|
-
// 保留版本相关字段
|
|
108
|
-
filtered[key] = groupConfig[key]
|
|
109
|
-
}
|
|
110
|
-
// 其他字段(如 query, payload 等全局元数据)被过滤掉
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
return filtered
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 获取文件列表(包含文件属性数据)
|
|
117
|
-
// 完全隔离架构:直接获取当前组的文件列表
|
|
118
|
-
const getFullDataList = (localStorage, groupManager, groupId) => {
|
|
119
|
-
if (!groupManager || !groupId) {
|
|
120
|
-
// 向后兼容:如果没有提供 groupManager,使用旧逻辑
|
|
121
|
-
const list = getApiList(localStorage) || []
|
|
122
|
-
return list.map((item) => {
|
|
123
|
-
const props = getProperty(localStorage, item.name) || {}
|
|
124
|
-
delete item.data
|
|
125
|
-
return { ...item, ...props }
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 新逻辑:直接获取当前组的文件列表
|
|
130
|
-
const list = getGroupFileList(localStorage, groupId) || []
|
|
131
|
-
const result = []
|
|
132
|
-
|
|
133
|
-
list.forEach((item) => {
|
|
134
|
-
// 读取组元数据(已合并全局元数据 + 组配置)
|
|
135
|
-
const config = groupManager.getGroupFileConfig(groupId, item.name)
|
|
136
|
-
|
|
137
|
-
if (config) {
|
|
138
|
-
result.push({
|
|
139
|
-
name: item.name,
|
|
140
|
-
...config, // 包含 url, method, date, status, rule, mock, locked 等
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
return result
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
exports.createErrorResponse = createErrorResponse
|
|
149
|
-
exports.createSuccessResponse = createSuccessResponse
|
|
150
|
-
exports.execFilter = execFilter
|
|
151
|
-
exports.handleFilterList = handleFilterList
|
|
152
|
-
exports.filterGroupConfig = filterGroupConfig
|
|
153
|
-
exports.getFullDataList = getFullDataList
|