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
|
@@ -1,126 +1,271 @@
|
|
|
1
|
-
const {
|
|
2
|
-
addNewVersion,
|
|
3
|
-
updateVersionContent,
|
|
4
|
-
deleteVersion,
|
|
5
|
-
updateVersionName,
|
|
6
|
-
getVersions,
|
|
7
|
-
setProperty,
|
|
8
|
-
getPropertyAttr,
|
|
9
|
-
removePropertyAttr,
|
|
10
|
-
} = require('../../utils')
|
|
11
1
|
const { createErrorResponse, createSuccessResponse } = require('../util')
|
|
2
|
+
const {
|
|
3
|
+
validateFilename,
|
|
4
|
+
validateVersionName,
|
|
5
|
+
validateMockData,
|
|
6
|
+
validate,
|
|
7
|
+
} = require('../validator')
|
|
12
8
|
|
|
13
9
|
|
|
14
10
|
module.exports = (router) => {
|
|
15
11
|
// 新增版本
|
|
16
|
-
router.post('/cgi-bin/mockbubu/add-new-version', (
|
|
12
|
+
router.post('/cgi-bin/mockbubu/add-new-version', validate({
|
|
13
|
+
name: validateFilename,
|
|
14
|
+
versionName: validateVersionName,
|
|
15
|
+
content: validateMockData,
|
|
16
|
+
}), async (ctx) => {
|
|
17
17
|
try {
|
|
18
|
-
const {
|
|
19
|
-
const { versionName, content = {}, name } = ctx.request.body || {}
|
|
18
|
+
const { groupManager, storageAdapter } = ctx
|
|
19
|
+
const { versionName, content = {}, name, description = '' } = ctx.request.body || {}
|
|
20
|
+
|
|
21
|
+
// 获取当前组ID
|
|
22
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
23
|
+
|
|
24
|
+
// 使用 V3 Storage 从索引查找文件ID
|
|
25
|
+
const v3Storage = storageAdapter.v3Storage
|
|
26
|
+
const index = await v3Storage.getIndex(currentGroupId)
|
|
27
|
+
const fileEntry = index.files.find(f => f.url === name)
|
|
28
|
+
if (!fileEntry) {
|
|
29
|
+
throw new Error('文件不存在')
|
|
30
|
+
}
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
filename: name,
|
|
24
|
-
versionName,
|
|
32
|
+
// 使用 V3 Storage 创建版本
|
|
33
|
+
const newVersion = await v3Storage.createVersion(currentGroupId, fileEntry.id, versionName, {
|
|
25
34
|
content,
|
|
35
|
+
description,
|
|
26
36
|
})
|
|
27
37
|
|
|
28
|
-
ctx.body = createSuccessResponse(
|
|
29
|
-
filename: versionName,
|
|
30
|
-
content,
|
|
31
|
-
}, '版本创建成功')
|
|
38
|
+
ctx.body = createSuccessResponse(newVersion, '版本创建成功')
|
|
32
39
|
} catch (error) {
|
|
40
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 创建版本失败:', error.message)
|
|
33
41
|
ctx.body = createErrorResponse(error.message)
|
|
34
42
|
}
|
|
35
43
|
})
|
|
36
44
|
|
|
37
45
|
// 删除版本
|
|
38
|
-
router.post('/cgi-bin/mockbubu/delete-version', (
|
|
46
|
+
router.post('/cgi-bin/mockbubu/delete-version', validate({
|
|
47
|
+
name: validateFilename,
|
|
48
|
+
}), async (ctx) => {
|
|
39
49
|
try {
|
|
40
|
-
const {
|
|
41
|
-
const {
|
|
50
|
+
const { groupManager, storageAdapter } = ctx
|
|
51
|
+
const { versionId, name } = ctx.request.body || {}
|
|
52
|
+
|
|
53
|
+
if (!versionId) {
|
|
54
|
+
throw new Error('缺少版本ID参数')
|
|
55
|
+
}
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
// 获取当前组ID
|
|
58
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
44
59
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
// 使用 V3 Storage 从索引查找文件ID
|
|
61
|
+
const v3Storage = storageAdapter.v3Storage
|
|
62
|
+
const index = await v3Storage.getIndex(currentGroupId)
|
|
63
|
+
const fileEntry = index.files.find(f => f.url === name)
|
|
64
|
+
if (!fileEntry) {
|
|
65
|
+
throw new Error('文件不存在')
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
// 获取组文件配置(检查是否是当前mock版本)
|
|
69
|
+
const groupConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
|
|
70
|
+
|
|
71
|
+
// 如果删除的是当前mock版本,先获取版本name进行比较
|
|
72
|
+
const version = await v3Storage.getVersionById(currentGroupId, fileEntry.id, versionId)
|
|
73
|
+
|
|
74
|
+
if (version && version.name === groupConfig.mockVersion) {
|
|
75
|
+
// 清除mock版本设置
|
|
76
|
+
groupConfig.mockVersion = null
|
|
77
|
+
await groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 使用 V3 Storage 删除版本
|
|
81
|
+
await v3Storage.deleteVersion(currentGroupId, fileEntry.id, versionId)
|
|
55
82
|
|
|
56
83
|
ctx.body = createSuccessResponse(null, '版本删除成功')
|
|
57
84
|
} catch (error) {
|
|
85
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 删除版本失败:', error.message)
|
|
58
86
|
ctx.body = createErrorResponse(error.message)
|
|
59
87
|
}
|
|
60
88
|
})
|
|
61
89
|
|
|
62
90
|
// 更新版本内容
|
|
63
|
-
router.post('/cgi-bin/mockbubu/update-version-content', (
|
|
91
|
+
router.post('/cgi-bin/mockbubu/update-version-content', validate({
|
|
92
|
+
name: validateFilename,
|
|
93
|
+
content: validateMockData,
|
|
94
|
+
}), async (ctx) => {
|
|
64
95
|
try {
|
|
65
|
-
const {
|
|
66
|
-
const {
|
|
96
|
+
const { groupManager, storageAdapter } = ctx
|
|
97
|
+
const { versionId, content, name } = ctx.request.body || {}
|
|
98
|
+
|
|
99
|
+
if (!versionId) {
|
|
100
|
+
throw new Error('缺少版本ID参数')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 获取当前组ID
|
|
104
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
67
105
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
106
|
+
// 使用 V3 Storage 从索引查找文件ID
|
|
107
|
+
const v3Storage = storageAdapter.v3Storage
|
|
108
|
+
const index = await v3Storage.getIndex(currentGroupId)
|
|
109
|
+
const fileEntry = index.files.find(f => f.url === name)
|
|
110
|
+
if (!fileEntry) {
|
|
111
|
+
throw new Error('文件不存在')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 使用 V3 Storage 更新版本
|
|
115
|
+
await v3Storage.updateVersion(currentGroupId, fileEntry.id, versionId, {
|
|
72
116
|
content,
|
|
73
117
|
})
|
|
74
118
|
|
|
75
119
|
ctx.body = createSuccessResponse(null, '版本内容更新成功')
|
|
76
120
|
} catch (error) {
|
|
121
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 更新版本内容失败:', error.message)
|
|
77
122
|
ctx.body = createErrorResponse(error.message)
|
|
78
123
|
}
|
|
79
124
|
})
|
|
80
125
|
|
|
81
|
-
//
|
|
82
|
-
router.post('/cgi-bin/mockbubu/
|
|
126
|
+
// 获取版本列表
|
|
127
|
+
router.post('/cgi-bin/mockbubu/get-versions', validate({
|
|
128
|
+
name: validateFilename,
|
|
129
|
+
}), async (ctx) => {
|
|
83
130
|
try {
|
|
84
|
-
const {
|
|
85
|
-
const {
|
|
131
|
+
const { groupManager, storageAdapter } = ctx
|
|
132
|
+
const { name } = ctx.request.body || {}
|
|
86
133
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
filename: name,
|
|
90
|
-
versionName,
|
|
91
|
-
newVersion,
|
|
92
|
-
})
|
|
134
|
+
// 获取当前组ID
|
|
135
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
93
136
|
|
|
94
|
-
|
|
137
|
+
// 使用 V3 Storage 从索引查找文件ID
|
|
138
|
+
const v3Storage = storageAdapter.v3Storage
|
|
139
|
+
const index = await v3Storage.getIndex(currentGroupId)
|
|
140
|
+
const fileEntry = index.files.find(f => f.url === name)
|
|
141
|
+
if (!fileEntry) {
|
|
142
|
+
throw new Error('文件不存在')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 使用 V3 Storage 获取版本列表
|
|
146
|
+
const versions = await v3Storage.listVersions(currentGroupId, fileEntry.id)
|
|
147
|
+
|
|
148
|
+
ctx.body = createSuccessResponse(versions, '获取版本列表成功')
|
|
95
149
|
} catch (error) {
|
|
150
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 获取版本列表失败:', error.message)
|
|
96
151
|
ctx.body = createErrorResponse(error.message)
|
|
97
152
|
}
|
|
98
153
|
})
|
|
99
154
|
|
|
100
|
-
//
|
|
101
|
-
router.post('/cgi-bin/mockbubu/
|
|
155
|
+
// 设置mock的版本
|
|
156
|
+
router.post('/cgi-bin/mockbubu/set-mock-version', validate({
|
|
157
|
+
name: validateFilename,
|
|
158
|
+
}), async (ctx) => {
|
|
102
159
|
try {
|
|
103
|
-
const {
|
|
104
|
-
const { name } = ctx.request.body || {}
|
|
160
|
+
const { groupManager } = ctx
|
|
161
|
+
const { name, versionName } = ctx.request.body || {}
|
|
105
162
|
|
|
106
|
-
|
|
163
|
+
// 获取当前组ID和配置
|
|
164
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
165
|
+
const groupConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
|
|
107
166
|
|
|
108
|
-
|
|
167
|
+
// 更新 mockVersion (使用版本name,不是ID)
|
|
168
|
+
groupConfig.mockVersion = versionName || null
|
|
169
|
+
await groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
170
|
+
|
|
171
|
+
ctx.body = createSuccessResponse(null, 'Mock版本设置成功')
|
|
109
172
|
} catch (error) {
|
|
173
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 设置Mock版本失败:', error.message)
|
|
110
174
|
ctx.body = createErrorResponse(error.message)
|
|
111
175
|
}
|
|
112
176
|
})
|
|
113
177
|
|
|
114
|
-
//
|
|
115
|
-
router.post('/cgi-bin/mockbubu/
|
|
178
|
+
// 更新版本元信息(名称和描述)
|
|
179
|
+
router.post('/cgi-bin/mockbubu/update-version-meta', validate({
|
|
180
|
+
name: validateFilename,
|
|
181
|
+
newVersionName: validateVersionName,
|
|
182
|
+
}), async (ctx) => {
|
|
116
183
|
try {
|
|
117
|
-
const {
|
|
118
|
-
const { name,
|
|
184
|
+
const { groupManager, storageAdapter } = ctx
|
|
185
|
+
const { name, versionId, newVersionName, description = '' } = ctx.request.body || {}
|
|
119
186
|
|
|
120
|
-
|
|
187
|
+
if (!versionId) {
|
|
188
|
+
throw new Error('缺少版本ID参数')
|
|
189
|
+
}
|
|
121
190
|
|
|
122
|
-
|
|
191
|
+
// 获取当前组ID
|
|
192
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
193
|
+
|
|
194
|
+
// 使用 V3 Storage 从索引查找文件ID
|
|
195
|
+
const v3Storage = storageAdapter.v3Storage
|
|
196
|
+
const index = await v3Storage.getIndex(currentGroupId)
|
|
197
|
+
const fileEntry = index.files.find(f => f.url === name)
|
|
198
|
+
if (!fileEntry) {
|
|
199
|
+
throw new Error('文件不存在')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 使用 V3 Storage 更新版本元信息
|
|
203
|
+
const oldVersion = await v3Storage.getVersionById(currentGroupId, fileEntry.id, versionId)
|
|
204
|
+
if (!oldVersion) {
|
|
205
|
+
throw new Error('版本不存在')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await v3Storage.updateVersion(currentGroupId, fileEntry.id, versionId, {
|
|
209
|
+
name: newVersionName,
|
|
210
|
+
description,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// 如果更新的是当前mock版本的名称,需要同步更新 mockVersion
|
|
214
|
+
const groupConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
|
|
215
|
+
if (oldVersion.name === groupConfig.mockVersion && oldVersion.name !== newVersionName) {
|
|
216
|
+
groupConfig.mockVersion = newVersionName
|
|
217
|
+
await groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
ctx.body = createSuccessResponse(null, '版本信息更新成功')
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 更新版本元信息失败:', error.message)
|
|
223
|
+
ctx.body = createErrorResponse(error.message)
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// 兼容旧路由:更新版本名称(已废弃)
|
|
228
|
+
router.post('/cgi-bin/mockbubu/update-version-name', validate({
|
|
229
|
+
name: validateFilename,
|
|
230
|
+
versionName: validateVersionName,
|
|
231
|
+
newVersion: validateVersionName,
|
|
232
|
+
}), async (ctx) => {
|
|
233
|
+
try {
|
|
234
|
+
const { groupManager, storageAdapter } = ctx
|
|
235
|
+
const { versionName, name, newVersion } = ctx.request.body || {}
|
|
236
|
+
|
|
237
|
+
// 获取当前组ID
|
|
238
|
+
const currentGroupId = await groupManager.getCurrentGroupId()
|
|
239
|
+
|
|
240
|
+
// 使用 V3 Storage 从索引查找文件ID
|
|
241
|
+
const v3Storage = storageAdapter.v3Storage
|
|
242
|
+
const index = await v3Storage.getIndex(currentGroupId)
|
|
243
|
+
const fileEntry = index.files.find(f => f.url === name)
|
|
244
|
+
if (!fileEntry) {
|
|
245
|
+
throw new Error('文件不存在')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 使用 V3 Storage 通过名称找到版本ID
|
|
249
|
+
const version = await v3Storage.getVersionByName(currentGroupId, fileEntry.id, versionName)
|
|
250
|
+
if (!version) {
|
|
251
|
+
throw new Error('版本不存在')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 更新版本名称
|
|
255
|
+
await v3Storage.updateVersion(currentGroupId, fileEntry.id, version.id, {
|
|
256
|
+
name: newVersion,
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// 如果更新的是当前mock版本,需要更新mockVersion属性
|
|
260
|
+
const groupConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
|
|
261
|
+
if (versionName === groupConfig.mockVersion && versionName !== newVersion) {
|
|
262
|
+
groupConfig.mockVersion = newVersion
|
|
263
|
+
await groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
ctx.body = createSuccessResponse(null, '版本名称更新成功')
|
|
123
267
|
} catch (error) {
|
|
268
|
+
console.error('[mockbubu 2025/11/19 21:30:00] 更新版本名称失败:', error.message)
|
|
124
269
|
ctx.body = createErrorResponse(error.message)
|
|
125
270
|
}
|
|
126
271
|
})
|
package/lib/uiServer/util.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
|
-
|
|
3
|
-
getProperty,
|
|
2
|
+
getGroupFileList,
|
|
4
3
|
} = require('../utils')
|
|
5
|
-
const { RangeFilterMap, RuleFilterMap, LockedFilterMap } = require('../const')
|
|
4
|
+
const { RangeFilterMap, RuleFilterMap, LockedFilterMap, MethodFilterMap, StatusFilterMap } = require('../const')
|
|
6
5
|
|
|
7
6
|
// 统一错误响应格式
|
|
8
7
|
const createErrorResponse = (message, code = 500) => ({
|
|
@@ -14,7 +13,7 @@ const createErrorResponse = (message, code = 500) => ({
|
|
|
14
13
|
const createSuccessResponse = (data = null, message = '操作成功') => ({
|
|
15
14
|
code: 200,
|
|
16
15
|
msg: message,
|
|
17
|
-
...(data && { data }),
|
|
16
|
+
...(data !== null && data !== undefined && { data }),
|
|
18
17
|
})
|
|
19
18
|
|
|
20
19
|
// 执行筛选
|
|
@@ -33,6 +32,13 @@ const execFilter = (result, filter) => {
|
|
|
33
32
|
return item[key] === value
|
|
34
33
|
case 'unequal':
|
|
35
34
|
return item[key] !== value
|
|
35
|
+
case 'range':
|
|
36
|
+
// 范围筛选,value 是 [min, max] 数组
|
|
37
|
+
if (Array.isArray(value) && value.length === 2) {
|
|
38
|
+
const itemValue = item[key]
|
|
39
|
+
return itemValue >= value[0] && itemValue <= value[1]
|
|
40
|
+
}
|
|
41
|
+
break
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
return true
|
|
@@ -46,7 +52,7 @@ const handleFilterList = (list, filterOptions) => {
|
|
|
46
52
|
if (!Array.isArray(list) || !filterOptions) return []
|
|
47
53
|
|
|
48
54
|
const filters = []
|
|
49
|
-
const { name, rule, range, ruleValue, locked } = filterOptions
|
|
55
|
+
const { name, rule, range, ruleValue, locked, method, status } = filterOptions
|
|
50
56
|
if (name) {
|
|
51
57
|
filters.push({
|
|
52
58
|
key: 'name',
|
|
@@ -69,6 +75,10 @@ const handleFilterList = (list, filterOptions) => {
|
|
|
69
75
|
RuleFilterMap[ruleValue] && filters.push(RuleFilterMap[ruleValue])
|
|
70
76
|
// 锁定筛选
|
|
71
77
|
LockedFilterMap[locked] && filters.push(LockedFilterMap[locked])
|
|
78
|
+
// HTTP Method 筛选
|
|
79
|
+
MethodFilterMap[method] && filters.push(MethodFilterMap[method])
|
|
80
|
+
// 状态码筛选
|
|
81
|
+
StatusFilterMap[status] && filters.push(StatusFilterMap[status])
|
|
72
82
|
|
|
73
83
|
// 执行筛选
|
|
74
84
|
const filteredList = filters.reduce((result, filter) => {
|
|
@@ -78,22 +88,70 @@ const handleFilterList = (list, filterOptions) => {
|
|
|
78
88
|
return filteredList
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
/**
|
|
92
|
+
* 过滤组配置,只保留组级别的字段
|
|
93
|
+
* 移除全局元数据字段 (query, payload, method, rule, status, pattern, ruleValue, url, date)
|
|
94
|
+
* 移除版本字段 (version.*, versionMeta.*) - 版本通过单独的 versions 数组导出
|
|
95
|
+
*/
|
|
96
|
+
const filterGroupConfig = (groupConfig) => {
|
|
97
|
+
if (!groupConfig) return {}
|
|
98
|
+
// 保留基础配置 + 元数据字段
|
|
99
|
+
// 元数据字段(url, method, date, status等)对于文件列表显示是必需的
|
|
100
|
+
const validKeys = ['mock', 'locked', 'mockVersion', 'mockTime', 'url', 'method', 'date', 'status', 'rule', 'ruleValue']
|
|
101
|
+
const filtered = {}
|
|
102
|
+
|
|
103
|
+
Object.keys(groupConfig).forEach(key => {
|
|
104
|
+
// 仅保留基础配置字段和元数据字段
|
|
105
|
+
if (validKeys.includes(key)) {
|
|
106
|
+
filtered[key] = groupConfig[key]
|
|
107
|
+
}
|
|
108
|
+
// 版本字段通过 versions 数组单独导出,不在 groupConfig 中保留
|
|
91
109
|
})
|
|
92
|
-
|
|
110
|
+
|
|
111
|
+
return filtered
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 获取文件列表(包含文件属性数据)
|
|
115
|
+
// 完全隔离架构:直接获取当前组的文件列表
|
|
116
|
+
const getFullDataList = async (storageAdapter, groupManager, groupId) => {
|
|
117
|
+
// 获取组的物理文件列表并合并配置
|
|
118
|
+
const list = await getGroupFileList(storageAdapter, groupId) || []
|
|
119
|
+
const result = []
|
|
120
|
+
const v3Storage = storageAdapter.getV3Storage()
|
|
121
|
+
|
|
122
|
+
// ⚠️ 强制刷新索引缓存
|
|
123
|
+
v3Storage.invalidateIndex(groupId)
|
|
124
|
+
|
|
125
|
+
// 获取索引以查找真实的 fileId
|
|
126
|
+
const index = await v3Storage.getIndex(groupId)
|
|
127
|
+
|
|
128
|
+
for (let idx = 0; idx < list.length; idx++) {
|
|
129
|
+
const item = list[idx]
|
|
130
|
+
const config = await groupManager.getGroupFileConfig(groupId, item.name)
|
|
131
|
+
|
|
132
|
+
if (config && config.url) {
|
|
133
|
+
// 从索引查找真实的 fileId(而不是生成)
|
|
134
|
+
const indexEntry = index.files.find(f => f.url === item.name)
|
|
135
|
+
const fileId = indexEntry ? indexEntry.id : null
|
|
136
|
+
|
|
137
|
+
result.push({
|
|
138
|
+
name: item.name,
|
|
139
|
+
id: fileId, // 添加 fileId 字段
|
|
140
|
+
...config,
|
|
141
|
+
// ✨ Phase 3.1: 从索引获取增强字段
|
|
142
|
+
domain: indexEntry?.domain || null,
|
|
143
|
+
pathname: indexEntry?.pathname || null,
|
|
144
|
+
urlHash: indexEntry?.urlHash || null,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result
|
|
93
150
|
}
|
|
94
151
|
|
|
95
152
|
exports.createErrorResponse = createErrorResponse
|
|
96
153
|
exports.createSuccessResponse = createSuccessResponse
|
|
97
154
|
exports.execFilter = execFilter
|
|
98
155
|
exports.handleFilterList = handleFilterList
|
|
156
|
+
exports.filterGroupConfig = filterGroupConfig
|
|
99
157
|
exports.getFullDataList = getFullDataList
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参数验证工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 验证文件名
|
|
6
|
+
const validateFilename = (name) => {
|
|
7
|
+
if (!name || typeof name !== 'string') {
|
|
8
|
+
return '文件名不能为空'
|
|
9
|
+
}
|
|
10
|
+
if (name.length > 2048) {
|
|
11
|
+
return '文件名长度不能超过 2048 字符'
|
|
12
|
+
}
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 验证版本名
|
|
17
|
+
const validateVersionName = (versionName) => {
|
|
18
|
+
if (!versionName || typeof versionName !== 'string') {
|
|
19
|
+
return '版本名不能为空'
|
|
20
|
+
}
|
|
21
|
+
if (versionName.length > 20) {
|
|
22
|
+
return '版本名长度不能超过 20 字符'
|
|
23
|
+
}
|
|
24
|
+
// 不允许可能导致存储问题的特殊字符
|
|
25
|
+
// 允许: 中文、字母、数字、下划线、连字符、空格
|
|
26
|
+
// 不允许: / \ : * ? " < > | 等文件系统保留字符
|
|
27
|
+
// eslint-disable-next-line no-useless-escape
|
|
28
|
+
if (/[\/\\:*?"<>|]/.test(versionName)) {
|
|
29
|
+
return '版本名不能包含特殊字符 / \\ : * ? " < > |'
|
|
30
|
+
}
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 验证 mock 数据内容
|
|
35
|
+
const validateMockData = (data) => {
|
|
36
|
+
// 允许 undefined (使用默认值),但不允许 null
|
|
37
|
+
if (data === null) {
|
|
38
|
+
return 'Mock 数据不能为 null'
|
|
39
|
+
}
|
|
40
|
+
// 如果 data 是 undefined,跳过大小检查(会使用默认值)
|
|
41
|
+
if (data === undefined) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
// 限制大小为 5MB
|
|
45
|
+
const dataStr = typeof data === 'string' ? data : JSON.stringify(data)
|
|
46
|
+
if (dataStr.length > 5 * 1024 * 1024) {
|
|
47
|
+
return 'Mock 数据大小不能超过 5MB'
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 验证布尔值
|
|
53
|
+
const validateBoolean = (value, fieldName) => {
|
|
54
|
+
if (typeof value !== 'boolean') {
|
|
55
|
+
return `${fieldName} 必须是布尔值`
|
|
56
|
+
}
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 验证 ruleValue
|
|
61
|
+
const validateRuleValue = (ruleValue) => {
|
|
62
|
+
const validValues = ['pathname', 'href', 'pattern']
|
|
63
|
+
if (!ruleValue || !validValues.includes(ruleValue)) {
|
|
64
|
+
return `ruleValue 必须是 ${validValues.join(', ')} 之一`
|
|
65
|
+
}
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 创建验证中间件
|
|
70
|
+
const validate = (schema) => {
|
|
71
|
+
return async (ctx, next) => {
|
|
72
|
+
const body = ctx.request.body || {}
|
|
73
|
+
const errors = []
|
|
74
|
+
|
|
75
|
+
Object.keys(schema).forEach((key) => {
|
|
76
|
+
const validator = schema[key]
|
|
77
|
+
const value = body[key]
|
|
78
|
+
const error = validator(value)
|
|
79
|
+
if (error) {
|
|
80
|
+
errors.push({ field: key, message: error })
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
if (errors.length > 0) {
|
|
85
|
+
ctx.status = 400
|
|
86
|
+
ctx.body = {
|
|
87
|
+
code: 400,
|
|
88
|
+
msg: '参数验证失败',
|
|
89
|
+
data: errors,
|
|
90
|
+
}
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await next()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
validateFilename,
|
|
100
|
+
validateVersionName,
|
|
101
|
+
validateMockData,
|
|
102
|
+
validateBoolean,
|
|
103
|
+
validateRuleValue,
|
|
104
|
+
validate,
|
|
105
|
+
}
|