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.
@@ -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', (ctx) => {
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 { localStorage } = ctx.req
19
- const { versionName, content = {}, name } = ctx.request.body || {}
18
+ const { groupManager } = ctx
19
+ const { versionName, content = {}, name, description = '' } = ctx.request.body || {}
20
20
 
21
- addNewVersion({
22
- storage: localStorage,
23
- filename: name,
24
- versionName,
25
- content,
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
- ctx.body = createSuccessResponse({
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', (ctx) => {
46
+ router.post('/cgi-bin/mockbubu/delete-version', validate({
47
+ name: validateFilename,
48
+ versionName: validateVersionName,
49
+ }), (ctx) => {
39
50
  try {
40
- const { localStorage } = ctx.req
51
+ const { groupManager } = ctx
41
52
  const { versionName, name } = ctx.request.body || {}
42
53
 
43
- const mockVersion = getPropertyAttr(localStorage, name, 'mockVersion')
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
- removePropertyAttr(localStorage, name, 'mockVersion')
59
+ if (versionName === groupConfig.mockVersion) {
60
+ groupConfig.mockVersion = null
61
+ groupManager.setGroupFileConfig(currentGroupId, name, groupConfig)
48
62
  }
49
63
 
50
- deleteVersion({
51
- storage: localStorage,
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', (ctx) => {
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 { localStorage } = ctx.req
80
+ const { groupManager } = ctx
66
81
  const { versionName, content, name } = ctx.request.body || {}
67
82
 
68
- updateVersionContent({
69
- storage: localStorage,
70
- filename: name,
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', (ctx) => {
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 { localStorage } = ctx.req
100
+ const { groupManager } = ctx
85
101
  const { versionName, name, newVersion } = ctx.request.body || {}
86
102
 
87
- updateVersionName({
88
- storage: localStorage,
89
- filename: name,
90
- versionName,
91
- newVersion,
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', (ctx) => {
127
+ router.post('/cgi-bin/mockbubu/get-versions', validate({
128
+ name: validateFilename,
129
+ }), (ctx) => {
102
130
  try {
103
- const { localStorage } = ctx.req
131
+ const { groupManager } = ctx
104
132
  const { name } = ctx.request.body || {}
105
133
 
106
- const list = getVersions({ storage: localStorage, filename: name }) || []
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', (ctx) => {
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 { localStorage } = ctx.req
159
+ const { groupManager } = ctx
118
160
  const { name, versionName } = ctx.request.body || {}
119
161
 
120
- setProperty(localStorage, name, { mockVersion: versionName })
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
  }
@@ -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
- const getFullDataList = (localStorage) => {
83
- // 获取接口列表
84
- const list = getApiList(localStorage) || []
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
- const props = getProperty(localStorage, item.name) || {}
89
- delete item.data
90
- Object.assign(item, props)
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
- return list
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
+ }