whistle.mockbubu 2.0.0 → 2.1.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.
Files changed (56) hide show
  1. package/README.md +38 -0
  2. package/index.js +3 -3
  3. package/lib/config/const.js +138 -0
  4. package/lib/config/rule-collector.js +81 -0
  5. package/lib/constants.js +62 -0
  6. package/lib/core/memory-buffer/index.js +207 -0
  7. package/lib/core/memory-buffer/shared-instance.js +15 -0
  8. package/lib/core/plugin-mode-manager.js +74 -0
  9. package/lib/core/resRulesServer.js +14 -0
  10. package/lib/core/rulesServer.js +31 -0
  11. package/lib/core/server-entry/capture-handler.js +191 -0
  12. package/lib/core/server-entry/request-interceptor.js +82 -0
  13. package/lib/core/server-entry/response-handler.js +147 -0
  14. package/lib/core/server-entry/server.js +230 -0
  15. package/lib/storage/group-manager.js +627 -0
  16. package/lib/storage/storage-adapter.js +712 -0
  17. package/lib/storage/storage-v3.js +1418 -0
  18. package/lib/uiServer/index.js +61 -24
  19. package/lib/uiServer/router/export/import-export-router.js +459 -0
  20. package/lib/uiServer/router/files/api-list-router.js +150 -0
  21. package/lib/uiServer/router/files/batch-operations-router.js +185 -0
  22. package/lib/uiServer/router/files/file-config-router.js +118 -0
  23. package/lib/uiServer/router/files/file-crud-router.js +212 -0
  24. package/lib/uiServer/router/files/file-save-router.js +146 -0
  25. package/lib/uiServer/router/files/version-router.js +260 -0
  26. package/lib/uiServer/router/global/plugin-control.js +135 -0
  27. package/lib/uiServer/router/global/system-info-router.js +386 -0
  28. package/lib/uiServer/router/{group-router.js → groups/group-router.js} +21 -20
  29. package/lib/uiServer/router/index.js +38 -1521
  30. package/lib/uiServer/utils/router-helpers.js +100 -0
  31. package/lib/uiServer/utils/util.js +172 -0
  32. package/lib/uiServer/{validator.js → utils/validator.js} +11 -6
  33. package/lib/utils/archive-utils.js +788 -0
  34. package/lib/utils/error-handler.js +173 -0
  35. package/lib/utils/logger.js +79 -0
  36. package/lib/utils/path-utils.js +147 -0
  37. package/lib/utils/performance.js +265 -0
  38. package/lib/utils/utils.js +541 -0
  39. package/package.json +2 -2
  40. package/public/js/app.js +3707 -1922
  41. package/public/js/app.js.map +1 -1
  42. package/public/js/chunk-vendors.js +5098 -3965
  43. package/public/js/chunk-vendors.js.map +1 -1
  44. package/rules.txt +1 -1
  45. package/CHANGELOG_GROUP_FEATURE.md +0 -468
  46. package/CHANGELOG_P0_FIXES.md +0 -412
  47. package/CHANGELOG_P1_OPTIMIZATIONS.md +0 -292
  48. package/CLAUDE.md +0 -436
  49. package/GROUP_FEATURE_DESIGN.md +0 -520
  50. package/lib/const.js +0 -47
  51. package/lib/group-manager.js +0 -491
  52. package/lib/resRulesServer.js +0 -9
  53. package/lib/server.js +0 -249
  54. package/lib/uiServer/router/version-router.js +0 -205
  55. package/lib/uiServer/util.js +0 -153
  56. package/lib/utils.js +0 -409
@@ -0,0 +1,146 @@
1
+ /**
2
+ * 文件名: file-save-router.js
3
+ * 功能: 首次保存路由(前端缓存 → 文件系统)
4
+ * 依赖: storage-adapter, group-manager
5
+ *
6
+ * 职责:
7
+ * - 接收前端发送的完整会话数据
8
+ * - 写入 V3 文件系统 (file.json + source version)
9
+ * - 创建/更新文件配置 (Layer 3 - properties)
10
+ *
11
+ * 路由:
12
+ * - POST /cgi-bin/mockbubu/file-save - 首次保存文件
13
+ */
14
+
15
+ const { LOG_PREFIX } = require('../../../constants')
16
+ const { createSuccessResponse, createErrorResponse } = require('../../utils/util')
17
+ const { wrapRouteHandler } = require('../../utils/router-helpers')
18
+
19
+ module.exports = (router) => {
20
+ /**
21
+ * 首次保存文件(前端缓存 → 文件系统)
22
+ *
23
+ * 功能:
24
+ * - 接收前端传递的完整会话数据
25
+ * - 写入 V3 文件存储(file.json + versions/source.json)
26
+ * - 创建文件配置(Layer 3 - properties)
27
+ *
28
+ * 请求体:
29
+ * ```javascript
30
+ * {
31
+ * url: 'https://api.example.com/users',
32
+ * method: 'GET',
33
+ * status: 200,
34
+ * session: {...} // 完整会话数据
35
+ * }
36
+ * ```
37
+ *
38
+ * 响应:
39
+ * ```javascript
40
+ * {
41
+ * code: 0,
42
+ * data: { success: true },
43
+ * msg: 'success'
44
+ * }
45
+ * ```
46
+ */
47
+ router.post('/cgi-bin/mockbubu/file-save', wrapRouteHandler(async (ctx) => {
48
+ console.log(`${LOG_PREFIX.FILE_SAVE} ========== 路由被调用 ==========`)
49
+ console.log(`${LOG_PREFIX.FILE_SAVE} 请求体:`, JSON.stringify(ctx.request.body, null, 2))
50
+
51
+ const { storageAdapter, groupManager } = ctx
52
+ const { url, method, status, session, config } = ctx.request.body
53
+
54
+ // 参数校验
55
+ if (!url || !method || !status || !session) {
56
+ console.error(`${LOG_PREFIX.FILE_SAVE} 参数不完整`, {
57
+ hasUrl: !!url,
58
+ hasMethod: !!method,
59
+ hasStatus: !!status,
60
+ hasSession: !!session,
61
+ })
62
+ ctx.body = createErrorResponse('参数不完整')
63
+ return
64
+ }
65
+
66
+ console.log(`${LOG_PREFIX.FILE_SAVE} 开始保存文件 | URL: ${url}`, config ? `| 配置: mock=${config.mock}, locked=${config.locked}` : '')
67
+
68
+ try {
69
+ const currentGroupId = await groupManager.getCurrentGroupId()
70
+ const filename = url // 使用 URL 作为 filename
71
+ const v3Storage = storageAdapter.v3Storage
72
+
73
+ // 检查文件是否已存在(通过索引文件)
74
+ const indexData = await v3Storage.getIndex(currentGroupId)
75
+ const existingFileEntry = indexData.files.find(f => f.url === filename)
76
+
77
+ console.log(`${LOG_PREFIX.FILE_SAVE} 检查文件是否存在: ${filename}, 结果:`, existingFileEntry ? '已存在' : '不存在')
78
+
79
+ // 如果文件已存在(在索引中找到了)
80
+ if (existingFileEntry) {
81
+ console.log(`${LOG_PREFIX.FILE_SAVE} 文件已存在,跳过保存: ${filename}`)
82
+ ctx.body = createSuccessResponse({ success: true, skipped: true })
83
+ return
84
+ }
85
+
86
+ // 生成新的文件ID
87
+ const newFileId = v3Storage.generateFileId()
88
+ console.log(`${LOG_PREFIX.FILE_SAVE} 生成文件ID: ${newFileId}`)
89
+
90
+ // 使用 V3 Storage 的 createFile 方法创建文件
91
+ console.log(`${LOG_PREFIX.FILE_SAVE} 准备调用 v3Storage.createFile, groupId=${currentGroupId}, fileId=${newFileId}`)
92
+ const fileData = {
93
+ url: filename,
94
+ method,
95
+ status,
96
+ headers: session.res?.headers || {},
97
+ session, // 完整 session 数据
98
+ config: {
99
+ mock: config?.mock || false,
100
+ locked: config?.locked || false,
101
+ mockVersion: 'source', // 新文件默认使用 source 版本
102
+ mockTime: config?.mock ? Date.now() : null,
103
+ },
104
+ }
105
+ console.log(`${LOG_PREFIX.FILE_SAVE} fileData keys:`, Object.keys(fileData))
106
+
107
+ await v3Storage.createFile(currentGroupId, newFileId, fileData)
108
+
109
+ console.log(`${LOG_PREFIX.FILE_SAVE} ✅ V3 Storage createFile 执行完成: ${filename}`)
110
+
111
+ // 注意:session 数据已保存在 file.json 中,无需创建 versions/source.json
112
+ // 所有读取操作都从 file.json 的 session 字段读取
113
+
114
+ // ⚠️ 关键修复:再次调用 setGroupFileConfig 确保 config 正确写入
115
+ const configToSet = {
116
+ mock: config?.mock !== undefined ? config.mock : false,
117
+ locked: config?.locked !== undefined ? config.locked : false,
118
+ mockVersion: config?.mock ? 'source' : null,
119
+ mockTime: config?.mock ? Date.now() : null,
120
+ }
121
+ console.log(`${LOG_PREFIX.FILE_SAVE} 准备更新配置:`, configToSet)
122
+ await groupManager.setGroupFileConfig(currentGroupId, filename, configToSet)
123
+ console.log(`${LOG_PREFIX.FILE_SAVE} ✅ 配置更新完成`)
124
+
125
+ // 返回保存后的完整配置(供前端更新使用)
126
+ ctx.body = createSuccessResponse({
127
+ success: true,
128
+ file: {
129
+ id: newFileId,
130
+ url: filename,
131
+ method,
132
+ status,
133
+ date: Date.now(),
134
+ ...configToSet,
135
+ rule: session.req?.rule || '',
136
+ pattern: session.req?.pattern || '',
137
+ ruleValue: session.req?.ruleValue || 'pathname',
138
+ name: filename,
139
+ },
140
+ })
141
+ } catch (error) {
142
+ console.error(`${LOG_PREFIX.FILE_SAVE} 保存失败:`, error)
143
+ ctx.body = createErrorResponse(`保存失败: ${error.message}`)
144
+ }
145
+ }, '首次保存文件'))
146
+ }
@@ -0,0 +1,260 @@
1
+ /**
2
+ * 文件名: version-router.js
3
+ * 功能: 版本管理路由(创建、删除、更新版本)
4
+ * 依赖: router-helpers.js, util.js, validator.js
5
+ */
6
+
7
+ const { createSuccessResponse } = require('../../utils/util')
8
+ const {
9
+ validateFilename,
10
+ validateVersionName,
11
+ validateMockData,
12
+ validate,
13
+ } = require('../../utils/validator')
14
+ const {
15
+ getFileIdByUrl,
16
+ wrapRouteHandler,
17
+ validateRequiredParams,
18
+ } = require('../../utils/router-helpers')
19
+
20
+ module.exports = (router) => {
21
+ // 新增版本
22
+ router.post('/cgi-bin/mockbubu/add-new-version', validate({
23
+ versionName: validateVersionName,
24
+ content: validateMockData,
25
+ }), wrapRouteHandler(async (ctx) => {
26
+ const { storageAdapter, groupManager } = ctx
27
+ const { versionName, content = {}, fileId, description = '' } = ctx.request.body || {}
28
+
29
+ validateRequiredParams({ fileId }, ['fileId'])
30
+
31
+ const currentGroupId = await groupManager.getCurrentGroupId()
32
+ const v3Storage = storageAdapter.v3Storage
33
+
34
+ const newVersion = await v3Storage.createVersion(currentGroupId, fileId, versionName, {
35
+ content,
36
+ description,
37
+ })
38
+
39
+ ctx.body = createSuccessResponse(newVersion, '版本创建成功')
40
+ }, '创建版本'))
41
+
42
+ // 删除版本
43
+ router.post('/cgi-bin/mockbubu/delete-version', wrapRouteHandler(async (ctx) => {
44
+ const { storageAdapter, groupManager } = ctx
45
+ const { versionId, fileId, url } = ctx.request.body || {}
46
+
47
+ validateRequiredParams({ versionId, fileId, url }, ['versionId', 'fileId', 'url'])
48
+
49
+ const currentGroupId = await groupManager.getCurrentGroupId()
50
+ const v3Storage = storageAdapter.v3Storage
51
+
52
+ // 获取文件配置(检查是否是当前mock版本)
53
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, url)
54
+ const version = await v3Storage.getVersionById(currentGroupId, fileId, versionId)
55
+
56
+ if (version && version.name === fileConfig.mockVersion) {
57
+ // 清除mock版本设置
58
+ fileConfig.mockVersion = null
59
+ await groupManager.setGroupFileConfig(currentGroupId, url, fileConfig)
60
+ }
61
+
62
+ await v3Storage.deleteVersion(currentGroupId, fileId, versionId)
63
+
64
+ ctx.body = createSuccessResponse(null, '版本删除成功')
65
+ }, '删除版本'))
66
+
67
+ // 更新版本内容
68
+ router.post('/cgi-bin/mockbubu/update-version-content', validate({
69
+ content: validateMockData,
70
+ }), wrapRouteHandler(async (ctx) => {
71
+ const { storageAdapter, groupManager } = ctx
72
+ const { versionId, content, fileId } = ctx.request.body || {}
73
+
74
+ validateRequiredParams({ versionId, fileId }, ['versionId', 'fileId'])
75
+
76
+ const currentGroupId = await groupManager.getCurrentGroupId()
77
+ const v3Storage = storageAdapter.v3Storage
78
+
79
+ await v3Storage.updateVersion(currentGroupId, fileId, versionId, { content })
80
+
81
+ ctx.body = createSuccessResponse(null, '版本内容更新成功')
82
+ }, '更新版本内容'))
83
+
84
+ // 获取版本列表
85
+ router.post('/cgi-bin/mockbubu/get-versions', wrapRouteHandler(async (ctx) => {
86
+ const { storageAdapter, groupManager } = ctx
87
+ const { fileId } = ctx.request.body || {}
88
+
89
+ validateRequiredParams({ fileId }, ['fileId'])
90
+
91
+ const currentGroupId = await groupManager.getCurrentGroupId()
92
+ const v3Storage = storageAdapter.v3Storage
93
+
94
+ // 获取文件数据(用于构建 source)
95
+ const fileData = await v3Storage.getFile(currentGroupId, fileId)
96
+ if (!fileData) {
97
+ throw new Error('文件不存在')
98
+ }
99
+
100
+ // 获取历史版本列表
101
+ const versions = await v3Storage.listVersions(currentGroupId, fileId)
102
+ console.log(`[version-router] get-versions: fileId=${fileId}, versions count=${versions.length}`)
103
+
104
+ // 构建统一数据结构
105
+ const result = []
106
+
107
+ // 1. 添加 source 数据
108
+ // source 数据存储在 file.json 的 session.res.body 字段中
109
+ // 这是捕获请求时保存的原始响应数据,用于版本对比和回退
110
+ let sourceContent = {}
111
+
112
+ if (fileData.session && fileData.session.res && fileData.session.res.body) {
113
+ try {
114
+ const bodyData = fileData.session.res.body
115
+
116
+ if (typeof bodyData === 'string') {
117
+ try {
118
+ sourceContent = JSON.parse(bodyData)
119
+ } catch (parseError) {
120
+ // 不是 JSON 格式(可能是 HTML/图片/文本),使用空对象
121
+ console.warn('[version-router] source 数据不是有效的 JSON,使用空对象')
122
+ sourceContent = {}
123
+ }
124
+ } else if (bodyData && typeof bodyData === 'object') {
125
+ sourceContent = bodyData
126
+ } else {
127
+ // 其他情况(如 undefined/null),使用空对象
128
+ sourceContent = {}
129
+ }
130
+ } catch (error) {
131
+ console.error('[version-router] source 数据解析失败,使用空对象:', error)
132
+ sourceContent = {}
133
+ }
134
+ } else {
135
+ console.warn('[version-router] file.json 中缺少 session.res.body 数据,使用空对象')
136
+ sourceContent = {}
137
+ }
138
+
139
+ result.push({
140
+ type: 'source',
141
+ name: 'source',
142
+ content: sourceContent,
143
+ updateTime: fileData.updateTime || fileData.createTime,
144
+ })
145
+
146
+ // 2. 添加历史版本数据
147
+ // 过滤掉名称为 "source" 的版本(避免与 source 数据冲突)
148
+ versions
149
+ .filter(version => version.name !== 'source')
150
+ .forEach((version) => {
151
+ let versionContent = {}
152
+ try {
153
+ // 如果 content 是字符串则解析为 JSON
154
+ versionContent = typeof version.content === 'string'
155
+ ? JSON.parse(version.content)
156
+ : (version.content || {})
157
+ } catch (error) {
158
+ console.error(`[version-router] 版本 ${version.name} 数据解析失败:`, error)
159
+ versionContent = {}
160
+ }
161
+
162
+ result.push({
163
+ type: 'history',
164
+ name: version.name,
165
+ content: versionContent,
166
+ updateTime: version.updateTime,
167
+ // 保留其他有用字段
168
+ id: version.id,
169
+ description: version.description,
170
+ createTime: version.createTime,
171
+ })
172
+ })
173
+
174
+ console.log(`[version-router] get-versions: 返回 ${result.length} 条数据`)
175
+ ctx.body = createSuccessResponse(result, '获取版本列表成功')
176
+ }, '获取版本列表'))
177
+
178
+ // 设置mock的版本
179
+ router.post('/cgi-bin/mockbubu/set-mock-version', wrapRouteHandler(async (ctx) => {
180
+ const { groupManager } = ctx
181
+ const { url, versionName } = ctx.request.body || {}
182
+
183
+ validateRequiredParams({ url }, ['url'])
184
+
185
+ const currentGroupId = await groupManager.getCurrentGroupId()
186
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, url)
187
+
188
+ // 更新 mockVersion (使用版本name,不是ID)
189
+ fileConfig.mockVersion = versionName || null
190
+ await groupManager.setGroupFileConfig(currentGroupId, url, fileConfig)
191
+
192
+ ctx.body = createSuccessResponse(null, 'Mock版本设置成功')
193
+ }, '设置Mock版本'))
194
+
195
+ // 更新版本元信息(名称和描述)
196
+ router.post('/cgi-bin/mockbubu/update-version-meta', validate({
197
+ newVersionName: validateVersionName,
198
+ }), wrapRouteHandler(async (ctx) => {
199
+ const { storageAdapter, groupManager } = ctx
200
+ const { url, fileId, versionId, newVersionName, description = '' } = ctx.request.body || {}
201
+
202
+ validateRequiredParams({ url, fileId, versionId }, ['url', 'fileId', 'versionId'])
203
+
204
+ const currentGroupId = await groupManager.getCurrentGroupId()
205
+ const v3Storage = storageAdapter.v3Storage
206
+
207
+ // 获取旧版本信息
208
+ const oldVersion = await v3Storage.getVersionById(currentGroupId, fileId, versionId)
209
+ if (!oldVersion) {
210
+ throw new Error('版本不存在')
211
+ }
212
+
213
+ // 更新版本元信息
214
+ await v3Storage.updateVersion(currentGroupId, fileId, versionId, {
215
+ name: newVersionName,
216
+ description,
217
+ })
218
+
219
+ // 如果更新的是当前mock版本的名称,需要同步更新 mockVersion
220
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, url)
221
+ if (oldVersion.name === fileConfig.mockVersion && oldVersion.name !== newVersionName) {
222
+ fileConfig.mockVersion = newVersionName
223
+ await groupManager.setGroupFileConfig(currentGroupId, url, fileConfig)
224
+ }
225
+
226
+ ctx.body = createSuccessResponse(null, '版本信息更新成功')
227
+ }, '更新版本元信息'))
228
+
229
+ // 兼容旧路由:更新版本名称(已废弃)
230
+ router.post('/cgi-bin/mockbubu/update-version-name', validate({
231
+ name: validateFilename,
232
+ versionName: validateVersionName,
233
+ newVersion: validateVersionName,
234
+ }), wrapRouteHandler(async (ctx) => {
235
+ const { groupManager } = ctx
236
+ const { versionName, name, newVersion } = ctx.request.body || {}
237
+
238
+ const { fileId, currentGroupId, v3Storage } = await getFileIdByUrl(ctx, name)
239
+
240
+ // 使用 V3 Storage 通过名称找到版本ID
241
+ const version = await v3Storage.getVersionByName(currentGroupId, fileId, versionName)
242
+ if (!version) {
243
+ throw new Error('版本不存在')
244
+ }
245
+
246
+ // 更新版本名称
247
+ await v3Storage.updateVersion(currentGroupId, fileId, version.id, {
248
+ name: newVersion,
249
+ })
250
+
251
+ // 如果更新的是当前mock版本,需要更新mockVersion属性
252
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
253
+ if (versionName === fileConfig.mockVersion && versionName !== newVersion) {
254
+ fileConfig.mockVersion = newVersion
255
+ await groupManager.setGroupFileConfig(currentGroupId, name, fileConfig)
256
+ }
257
+
258
+ ctx.body = createSuccessResponse(null, '版本名称更新成功')
259
+ }, '更新版本名称'))
260
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * 插件控制路由
3
+ * 管理插件的工作模式
4
+ */
5
+
6
+ const { PLUGIN_MODE_KEY, PLUGIN_MODE, DEFAULT_PLUGIN_MODE } = require('../../../config/const')
7
+ const pluginModeManager = require('../../../core/plugin-mode-manager')
8
+
9
+ module.exports = (router, options) => {
10
+ const storage = options.storage
11
+
12
+ /**
13
+ * 获取插件工作模式
14
+ */
15
+ router.post('/cgi-bin/mockbubu/get-plugin-mode', async (ctx) => {
16
+ try {
17
+ // 从模式管理器获取当前模式(内存缓存,与请求拦截器保持一致)
18
+ const mode = pluginModeManager.getMode()
19
+
20
+ ctx.body = {
21
+ success: true,
22
+ mode,
23
+ }
24
+ } catch (error) {
25
+ console.error('[mockbubu] ❌ 获取插件模式失败:', error)
26
+ ctx.body = {
27
+ success: false,
28
+ message: error.message,
29
+ }
30
+ }
31
+ })
32
+
33
+ /**
34
+ * 兼容旧接口:获取插件启用状态
35
+ * @deprecated 使用 get-plugin-mode 替代
36
+ */
37
+ router.post('/cgi-bin/mockbubu/get-plugin-enabled', async (ctx) => {
38
+ try {
39
+ const mode = await storage.getProperty(PLUGIN_MODE_KEY) || DEFAULT_PLUGIN_MODE
40
+ // 任何模式都算"启用",因为都会处理 Mock
41
+ const enabled = true
42
+
43
+ ctx.body = {
44
+ success: true,
45
+ enabled,
46
+ }
47
+ } catch (error) {
48
+ console.error('[mockbubu] ❌ 获取插件状态失败:', error)
49
+ ctx.body = {
50
+ success: false,
51
+ message: error.message,
52
+ }
53
+ }
54
+ })
55
+
56
+ /**
57
+ * 切换插件工作模式
58
+ */
59
+ router.post('/cgi-bin/mockbubu/set-plugin-mode', async (ctx) => {
60
+ try {
61
+ const { mode } = ctx.request.body
62
+
63
+ // 验证模式参数
64
+ if (!Object.values(PLUGIN_MODE).includes(mode)) {
65
+ ctx.body = {
66
+ success: false,
67
+ message: `mode 参数必须是 ${Object.values(PLUGIN_MODE).join(' 或 ')}`,
68
+ }
69
+ return
70
+ }
71
+
72
+ // 使用模式管理器更新模式(会同时更新 storage 和内存缓存)
73
+ await pluginModeManager.setMode(mode)
74
+
75
+ // 通知 Whistle 更新规则(触发 rulesServer 钩子重新执行)
76
+ if (typeof storage.updateRules === 'function') {
77
+ storage.updateRules()
78
+ }
79
+
80
+ const modeNames = {
81
+ [PLUGIN_MODE.DEFAULT]: '默认模式',
82
+ [PLUGIN_MODE.MOCK_ONLY]: '仅Mock模式',
83
+ }
84
+ console.log(`[mockbubu] ✅ 已切换到: ${modeNames[mode]}`)
85
+
86
+ ctx.body = {
87
+ success: true,
88
+ mode,
89
+ }
90
+ } catch (error) {
91
+ console.error('[mockbubu] ❌ 切换插件模式失败:', error)
92
+ ctx.body = {
93
+ success: false,
94
+ message: error.message,
95
+ }
96
+ }
97
+ })
98
+
99
+ /**
100
+ * 兼容旧接口:切换插件启用/禁用状态
101
+ * @deprecated 使用 set-plugin-mode 替代
102
+ */
103
+ router.post('/cgi-bin/mockbubu/toggle-plugin-enabled', async (ctx) => {
104
+ try {
105
+ const { enabled } = ctx.request.body
106
+
107
+ // 启用 → capture 模式,禁用 → 不支持(返回错误)
108
+ if (!enabled) {
109
+ ctx.body = {
110
+ success: false,
111
+ message: '该功能已废弃,请使用模式切换功能',
112
+ }
113
+ return
114
+ }
115
+
116
+ // 启用 → 设置为 default 模式
117
+ await storage.setProperty(PLUGIN_MODE_KEY, PLUGIN_MODE.DEFAULT)
118
+
119
+ if (typeof storage.updateRules === 'function') {
120
+ storage.updateRules()
121
+ }
122
+
123
+ ctx.body = {
124
+ success: true,
125
+ enabled: true,
126
+ }
127
+ } catch (error) {
128
+ console.error('[mockbubu] ❌ 切换插件状态失败:', error)
129
+ ctx.body = {
130
+ success: false,
131
+ message: error.message,
132
+ }
133
+ }
134
+ })
135
+ }