whistle.mockbubu 1.0.0-dev.4 → 2.0.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 +436 -0
- package/GROUP_FEATURE_DESIGN.md +520 -0
- package/lib/const.js +19 -0
- package/lib/group-manager.js +491 -0
- package/lib/server.js +173 -33
- package/lib/uiServer/index.js +46 -4
- package/lib/uiServer/router/group-router.js +218 -0
- package/lib/uiServer/router/index.js +1393 -38
- package/lib/uiServer/router/version-router.js +131 -53
- package/lib/uiServer/util.js +64 -10
- package/lib/uiServer/validator.js +105 -0
- package/lib/utils.js +149 -27
- package/package.json +1 -1
- package/public/js/app.js +4541 -747
- package/public/js/app.js.map +1 -1
- package/public/js/chunk-vendors.js +12194 -6869
- package/public/js/chunk-vendors.js.map +1 -1
|
@@ -1,33 +1,41 @@
|
|
|
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
|
+
}), (ctx) => {
|
|
17
17
|
try {
|
|
18
|
-
const {
|
|
19
|
-
const { versionName, content = {}, name } = ctx.request.body || {}
|
|
18
|
+
const { groupManager } = ctx
|
|
19
|
+
const { versionName, content = {}, name, description = '' } = ctx.request.body || {}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})
|
|
21
|
+
console.log('[mockbubu] 创建版本:', { versionName, name, description })
|
|
22
|
+
|
|
23
|
+
// 获取当前组ID,使用组级别的版本管理
|
|
24
|
+
const currentGroupId = groupManager.getCurrentGroupId()
|
|
25
|
+
groupManager.addGroupVersion(currentGroupId, name, versionName, content, description)
|
|
27
26
|
|
|
28
|
-
|
|
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 || {
|
|
29
34
|
filename: versionName,
|
|
30
35
|
content,
|
|
36
|
+
description,
|
|
37
|
+
createTime: Date.now(),
|
|
38
|
+
updateTime: Date.now(),
|
|
31
39
|
}, '版本创建成功')
|
|
32
40
|
} catch (error) {
|
|
33
41
|
ctx.body = createErrorResponse(error.message)
|
|
@@ -35,23 +43,26 @@ module.exports = (router) => {
|
|
|
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
|
+
versionName: validateVersionName,
|
|
49
|
+
}), (ctx) => {
|
|
39
50
|
try {
|
|
40
|
-
const {
|
|
51
|
+
const { groupManager } = ctx
|
|
41
52
|
const { versionName, name } = ctx.request.body || {}
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
// 获取当前组ID和配置
|
|
55
|
+
const currentGroupId = groupManager.getCurrentGroupId()
|
|
56
|
+
const groupConfig = groupManager.getGroupFileConfig(currentGroupId, name)
|
|
44
57
|
|
|
45
58
|
// 如果删除的是当前mock版本,则清除mock版本设置
|
|
46
|
-
if (versionName === mockVersion) {
|
|
47
|
-
|
|
59
|
+
if (versionName === groupConfig.mockVersion) {
|
|
60
|
+
groupConfig.mockVersion = null
|
|
61
|
+
groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
|
|
48
62
|
}
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
filename: name,
|
|
53
|
-
versionName,
|
|
54
|
-
})
|
|
64
|
+
// 使用组级别的版本删除
|
|
65
|
+
groupManager.deleteGroupVersion(currentGroupId, name, versionName)
|
|
55
66
|
|
|
56
67
|
ctx.body = createSuccessResponse(null, '版本删除成功')
|
|
57
68
|
} catch (error) {
|
|
@@ -60,17 +71,18 @@ module.exports = (router) => {
|
|
|
60
71
|
})
|
|
61
72
|
|
|
62
73
|
// 更新版本内容
|
|
63
|
-
router.post('/cgi-bin/mockbubu/update-version-content', (
|
|
74
|
+
router.post('/cgi-bin/mockbubu/update-version-content', validate({
|
|
75
|
+
name: validateFilename,
|
|
76
|
+
versionName: validateVersionName,
|
|
77
|
+
content: validateMockData,
|
|
78
|
+
}), (ctx) => {
|
|
64
79
|
try {
|
|
65
|
-
const {
|
|
80
|
+
const { groupManager } = ctx
|
|
66
81
|
const { versionName, content, name } = ctx.request.body || {}
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
versionName,
|
|
72
|
-
content,
|
|
73
|
-
})
|
|
83
|
+
// 获取当前组ID,使用组级别的版本内容更新
|
|
84
|
+
const currentGroupId = groupManager.getCurrentGroupId()
|
|
85
|
+
groupManager.updateGroupVersionContent(currentGroupId, name, versionName, content)
|
|
74
86
|
|
|
75
87
|
ctx.body = createSuccessResponse(null, '版本内容更新成功')
|
|
76
88
|
} catch (error) {
|
|
@@ -78,18 +90,32 @@ module.exports = (router) => {
|
|
|
78
90
|
}
|
|
79
91
|
})
|
|
80
92
|
|
|
81
|
-
//
|
|
82
|
-
router.post('/cgi-bin/mockbubu/update-version-name', (
|
|
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) => {
|
|
83
99
|
try {
|
|
84
|
-
const {
|
|
100
|
+
const { groupManager } = ctx
|
|
85
101
|
const { versionName, name, newVersion } = ctx.request.body || {}
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
}
|
|
93
119
|
|
|
94
120
|
ctx.body = createSuccessResponse(null, '版本名称更新成功')
|
|
95
121
|
} catch (error) {
|
|
@@ -98,12 +124,21 @@ module.exports = (router) => {
|
|
|
98
124
|
})
|
|
99
125
|
|
|
100
126
|
// 获取版本列表
|
|
101
|
-
router.post('/cgi-bin/mockbubu/get-versions', (
|
|
127
|
+
router.post('/cgi-bin/mockbubu/get-versions', validate({
|
|
128
|
+
name: validateFilename,
|
|
129
|
+
}), (ctx) => {
|
|
102
130
|
try {
|
|
103
|
-
const {
|
|
131
|
+
const { groupManager } = ctx
|
|
104
132
|
const { name } = ctx.request.body || {}
|
|
105
133
|
|
|
106
|
-
|
|
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
|
+
}
|
|
107
142
|
|
|
108
143
|
ctx.body = createSuccessResponse(list, '获取版本列表成功')
|
|
109
144
|
} catch (error) {
|
|
@@ -112,16 +147,59 @@ module.exports = (router) => {
|
|
|
112
147
|
})
|
|
113
148
|
|
|
114
149
|
// 设置mock的版本
|
|
115
|
-
router.post('/cgi-bin/mockbubu/set-mock-version', (
|
|
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) => {
|
|
116
158
|
try {
|
|
117
|
-
const {
|
|
159
|
+
const { groupManager } = ctx
|
|
118
160
|
const { name, versionName } = ctx.request.body || {}
|
|
119
161
|
|
|
120
|
-
|
|
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)
|
|
121
169
|
|
|
122
170
|
ctx.body = createSuccessResponse(null, 'Mock版本设置成功')
|
|
123
171
|
} catch (error) {
|
|
124
172
|
ctx.body = createErrorResponse(error.message)
|
|
125
173
|
}
|
|
126
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
|
+
})
|
|
127
205
|
}
|
package/lib/uiServer/util.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const {
|
|
2
2
|
getApiList,
|
|
3
|
+
getGroupFileList,
|
|
3
4
|
getProperty,
|
|
4
5
|
} = require('../utils')
|
|
5
|
-
const { RangeFilterMap, RuleFilterMap, LockedFilterMap } = require('../const')
|
|
6
|
+
const { RangeFilterMap, RuleFilterMap, LockedFilterMap, MethodFilterMap, StatusFilterMap } = require('../const')
|
|
6
7
|
|
|
7
8
|
// 统一错误响应格式
|
|
8
9
|
const createErrorResponse = (message, code = 500) => ({
|
|
@@ -33,6 +34,13 @@ const execFilter = (result, filter) => {
|
|
|
33
34
|
return item[key] === value
|
|
34
35
|
case 'unequal':
|
|
35
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
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
return true
|
|
@@ -46,7 +54,7 @@ const handleFilterList = (list, filterOptions) => {
|
|
|
46
54
|
if (!Array.isArray(list) || !filterOptions) return []
|
|
47
55
|
|
|
48
56
|
const filters = []
|
|
49
|
-
const { name, rule, range, ruleValue, locked } = filterOptions
|
|
57
|
+
const { name, rule, range, ruleValue, locked, method, status } = filterOptions
|
|
50
58
|
if (name) {
|
|
51
59
|
filters.push({
|
|
52
60
|
key: 'name',
|
|
@@ -69,6 +77,10 @@ const handleFilterList = (list, filterOptions) => {
|
|
|
69
77
|
RuleFilterMap[ruleValue] && filters.push(RuleFilterMap[ruleValue])
|
|
70
78
|
// 锁定筛选
|
|
71
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])
|
|
72
84
|
|
|
73
85
|
// 执行筛选
|
|
74
86
|
const filteredList = filters.reduce((result, filter) => {
|
|
@@ -78,22 +90,64 @@ const handleFilterList = (list, filterOptions) => {
|
|
|
78
90
|
return filteredList
|
|
79
91
|
}
|
|
80
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
|
+
|
|
81
116
|
// 获取文件列表(包含文件属性数据)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 = []
|
|
85
132
|
|
|
86
|
-
// 合并属性数据
|
|
87
133
|
list.forEach((item) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
+
}
|
|
91
143
|
})
|
|
92
|
-
|
|
144
|
+
|
|
145
|
+
return result
|
|
93
146
|
}
|
|
94
147
|
|
|
95
148
|
exports.createErrorResponse = createErrorResponse
|
|
96
149
|
exports.createSuccessResponse = createSuccessResponse
|
|
97
150
|
exports.execFilter = execFilter
|
|
98
151
|
exports.handleFilterList = handleFilterList
|
|
152
|
+
exports.filterGroupConfig = filterGroupConfig
|
|
99
153
|
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
|
+
}
|