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,150 @@
1
+ /**
2
+ * 文件名: api-list-router.js
3
+ * 功能: API 列表路由(轮询查询)
4
+ * 依赖: utils.js, util.js
5
+ *
6
+ * 职责:
7
+ * - 增量轮询查询(从内存缓冲区获取新数据)
8
+ * - 首次轮询查询(返回完整列表)
9
+ *
10
+ * 路由:
11
+ * - POST /cgi-bin/mockbubu/api-list - 增量轮询查询
12
+ * - GET /cgi-bin/mockbubu/check-api-list - 检测更新状态(保留兼容性)
13
+ *
14
+ * 轮询说明:
15
+ * - 前端定时轮询 /api-list,传递 startTime
16
+ * - startTime=0 时返回完整列表(第一次轮询)
17
+ * - startTime>0 时返回增量数据(仅新捕获的数据)
18
+ * - 返回的数据已从暂存区清空(取出即清空)
19
+ */
20
+
21
+ const { POLLING_CONFIG, LOG_PREFIX } = require('../../../constants')
22
+ const {
23
+ getApiListUpdated,
24
+ } = require('../../../utils/utils')
25
+ const {
26
+ createSuccessResponse,
27
+ handleFilterList,
28
+ getFullDataList,
29
+ } = require('../../utils/util')
30
+ const { wrapRouteHandler } = require('../../utils/router-helpers')
31
+
32
+ module.exports = (router) => {
33
+ /**
34
+ * 获取已保存文件列表(首屏加载)
35
+ *
36
+ * 功能:
37
+ * - 只返回已保存的文件(从文件系统加载)
38
+ * - 不包含暂存区数据
39
+ * - 首屏加载时调用一次
40
+ *
41
+ * 响应:
42
+ * ```javascript
43
+ * {
44
+ * code: 200,
45
+ * data: [
46
+ * {
47
+ * id: 'fileId',
48
+ * url: 'https://api.example.com/users',
49
+ * method: 'GET',
50
+ * status: 200,
51
+ * mock: true,
52
+ * locked: false,
53
+ * session: {...} // 完整会话数据
54
+ * }
55
+ * ],
56
+ * msg: '操作成功'
57
+ * }
58
+ * ```
59
+ */
60
+ router.post('/cgi-bin/mockbubu/api-list-saved', wrapRouteHandler(async (ctx) => {
61
+ const { storageAdapter, groupManager } = ctx
62
+ const currentGroupId = await groupManager.getCurrentGroupId()
63
+
64
+ console.log(`${LOG_PREFIX.API_LIST} 获取已保存文件列表 | groupId: ${currentGroupId}`)
65
+
66
+ const savedFiles = await getFullDataList(storageAdapter, groupManager, currentGroupId)
67
+ console.log(`${LOG_PREFIX.API_LIST} 已保存文件: ${savedFiles.length} 条`)
68
+
69
+ const filteredList = handleFilterList(savedFiles, ctx.request.body)
70
+ ctx.body = createSuccessResponse(filteredList || [])
71
+ }, '获取已保存文件列表'))
72
+
73
+ /**
74
+ * 增量轮询查询(只返回暂存区数据)
75
+ *
76
+ * 功能:
77
+ * - 只返回暂存区的增量数据(未保存的捕获数据)
78
+ * - 不返回已保存文件(已保存文件通过 /api-list-saved 获取)
79
+ * - 取出即清空(从暂存区删除已返回的数据)
80
+ *
81
+ * 请求体:
82
+ * ```javascript
83
+ * {
84
+ * startTime: number, // 必填,轮询起始时间戳(0 表示首次)
85
+ * keyword: 'string', // 可选,搜索关键词
86
+ * mock: boolean, // 可选,筛选 mock 状态
87
+ * locked: boolean // 可选,筛选锁定状态
88
+ * }
89
+ * ```
90
+ *
91
+ * 响应:
92
+ * ```javascript
93
+ * {
94
+ * code: 200,
95
+ * data: [
96
+ * {
97
+ * url: 'https://api.example.com/users',
98
+ * method: 'GET',
99
+ * status: 200,
100
+ * session: {...} // 完整会话数据
101
+ * }
102
+ * ],
103
+ * msg: '操作成功'
104
+ * }
105
+ * ```
106
+ */
107
+ router.post('/cgi-bin/mockbubu/api-list', wrapRouteHandler(async (ctx) => {
108
+ const { groupManager, memoryBuffer } = ctx
109
+ const currentGroupId = await groupManager.getCurrentGroupId()
110
+ const { startTime = POLLING_CONFIG.INITIAL_START_TIME } = ctx.request.body
111
+
112
+ // console.log(`${LOG_PREFIX.API_LIST} 轮询查询(暂存区) | startTime: ${startTime} | groupId: ${currentGroupId}`)
113
+
114
+ // 从内存缓冲区获取增量数据(取出即清空)
115
+ const incrementalData = memoryBuffer.getAndClear(currentGroupId, startTime)
116
+
117
+ // console.log(`${LOG_PREFIX.API_LIST} 暂存区返回 ${incrementalData.length} 条数据`)
118
+
119
+ // 只返回暂存区数据,不包含已保存文件
120
+ ctx.body = createSuccessResponse(incrementalData || [])
121
+ }, '增量轮询查询(暂存区)'))
122
+
123
+ /**
124
+ * 检测 API 列表是否有更新
125
+ *
126
+ * 功能:
127
+ * - 检查是否有新的 API 被捕获
128
+ * - 前端轮询此接口,决定是否刷新列表
129
+ *
130
+ * 响应:
131
+ * ```javascript
132
+ * {
133
+ * code: 0,
134
+ * data: true, // true 表示有更新,false 表示无更新
135
+ * msg: 'success'
136
+ * }
137
+ * ```
138
+ *
139
+ * 工作原理:
140
+ * - server.js 捕获新 API 时,设置更新标志为 true
141
+ * - 前端调用 /api-list 后,标志被重置为 false
142
+ * - 前端定时轮询此接口,检测是否需要刷新
143
+ */
144
+ router.get('/cgi-bin/mockbubu/check-api-list', wrapRouteHandler(async (ctx) => {
145
+ const { storageAdapter } = ctx
146
+ const updated = await getApiListUpdated(storageAdapter)
147
+
148
+ ctx.body = createSuccessResponse(updated)
149
+ }, '检查API列表更新'))
150
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * 文件名: batch-operations-router.js
3
+ * 功能: 批量操作路由
4
+ * 依赖: utils.js, util.js, validator.js
5
+ *
6
+ * 职责:
7
+ * - 批量更新 Mock 状态(智能保存:缓存数据会先保存)
8
+ * - 批量删除文件(智能删除:区分已保存和暂存区)
9
+ *
10
+ * 路由:
11
+ * - POST /cgi-bin/mockbubu/batch-update-mock - 批量更新 Mock 状态
12
+ * - POST /cgi-bin/mockbubu/batch-delete-by-names - 批量删除文件
13
+ */
14
+
15
+ const {
16
+ createErrorResponse,
17
+ createSuccessResponse,
18
+ } = require('../../utils/util')
19
+ const {
20
+ validateArray,
21
+ validateBoolean,
22
+ validate,
23
+ } = require('../../utils/validator')
24
+
25
+ module.exports = (router) => {
26
+ /**
27
+ * 批量更新 Mock 状态
28
+ *
29
+ * 智能处理:
30
+ * - 已保存文件:直接更新配置
31
+ * - 暂存区数据:前端需先调用 save-from-cache 保存后再调用此接口
32
+ *
33
+ * 请求体:
34
+ * ```javascript
35
+ * {
36
+ * urls: ['url1', 'url2', ...], // 文件 URL 数组(只包含已保存的)
37
+ * mock: true // 新的 mock 状态
38
+ * }
39
+ * ```
40
+ *
41
+ * 响应:
42
+ * ```javascript
43
+ * {
44
+ * code: 0,
45
+ * data: {
46
+ * success: ['url1', 'url2'], // 成功的文件
47
+ * failed: ['url3'], // 失败的文件
48
+ * skipped: ['url4'] // 跳过的文件(锁定)
49
+ * },
50
+ * msg: '批量更新成功'
51
+ * }
52
+ * ```
53
+ */
54
+ router.post('/cgi-bin/mockbubu/batch-update-mock', validate({
55
+ urls: validateArray,
56
+ mock: validateBoolean,
57
+ }), async (ctx) => {
58
+ try {
59
+ const { groupManager } = ctx
60
+ const { urls, mock } = ctx.request.body
61
+
62
+ const currentGroupId = await groupManager.getCurrentGroupId()
63
+
64
+ const success = []
65
+ const failed = []
66
+ const skipped = []
67
+
68
+ for (const url of urls) {
69
+ try {
70
+ // 检查文件是否存在且被锁定
71
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, url)
72
+
73
+ if (!fileConfig) {
74
+ // 文件不存在(可能是暂存区数据),前端应先保存
75
+ failed.push(url)
76
+ continue
77
+ }
78
+
79
+ if (fileConfig.locked) {
80
+ skipped.push(url)
81
+ continue
82
+ }
83
+
84
+ // 更新 Mock 状态
85
+ await groupManager.setGroupFileConfig(currentGroupId, url, {
86
+ mock,
87
+ mockTime: mock ? Date.now() : null,
88
+ })
89
+
90
+ success.push(url)
91
+ } catch (error) {
92
+ console.error('[batch-update-mock] 更新失败:', url, error)
93
+ failed.push(url)
94
+ }
95
+ }
96
+
97
+ const message = `成功: ${success.length}, 失败: ${failed.length}, 跳过: ${skipped.length}`
98
+ ctx.body = createSuccessResponse({ success, failed, skipped }, message)
99
+ } catch (error) {
100
+ console.error('[batch-update-mock] 批量更新异常:', error)
101
+ ctx.body = createErrorResponse(error.message)
102
+ }
103
+ })
104
+
105
+ /**
106
+ * 批量删除文件
107
+ *
108
+ * 智能删除:
109
+ * - 已保存文件:删除文件系统和配置
110
+ * - 暂存区数据:后端静默跳过,前端自行处理
111
+ *
112
+ * 请求体:
113
+ * ```javascript
114
+ * {
115
+ * urls: ['url1', 'url2', ...] // 文件 URL 数组
116
+ * }
117
+ * ```
118
+ *
119
+ * 响应:
120
+ * ```javascript
121
+ * {
122
+ * code: 0,
123
+ * data: {
124
+ * success: ['url1', 'url2'], // 成功删除的文件
125
+ * notFound: ['url3'], // 不存在的文件(暂存区数据)
126
+ * skipped: ['url4'] // 跳过的文件(锁定)
127
+ * },
128
+ * msg: '批量删除完成'
129
+ * }
130
+ * ```
131
+ */
132
+ router.post('/cgi-bin/mockbubu/batch-delete-by-names', validate({
133
+ urls: validateArray,
134
+ }), async (ctx) => {
135
+ try {
136
+ const { storageAdapter } = ctx
137
+ const { groupManager } = ctx
138
+ const { urls } = ctx.request.body
139
+
140
+ const currentGroupId = await groupManager.getCurrentGroupId()
141
+
142
+ console.log('[batch-delete] 开始批量删除', new Date().toLocaleString('zh-CN', { hour12: false }))
143
+ console.log('[batch-delete] 待删除文件数:', urls.length)
144
+ console.log('[batch-delete] 目标组ID:', currentGroupId)
145
+
146
+ const success = []
147
+ const notFound = []
148
+ const skipped = []
149
+
150
+ for (const url of urls) {
151
+ try {
152
+ // 检查文件是否存在
153
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, url)
154
+
155
+ if (!fileConfig) {
156
+ // 文件不存在(暂存区数据),前端自行处理
157
+ notFound.push(url)
158
+ continue
159
+ }
160
+
161
+ if (fileConfig.locked) {
162
+ skipped.push(url)
163
+ continue
164
+ }
165
+
166
+ // 删除物理文件
167
+ await storageAdapter.removeFile(`${currentGroupId}/${url}`)
168
+ // 删除配置
169
+ await groupManager.removeGroupFileConfig(currentGroupId, url)
170
+
171
+ success.push(url)
172
+ } catch (error) {
173
+ console.error('[batch-delete] 删除失败:', url, error)
174
+ // 删除失败也归类到 notFound(可能文件已被其他操作删除)
175
+ notFound.push(url)
176
+ }
177
+ }
178
+
179
+ const message = `成功: ${success.length}, 不存在: ${notFound.length}, 跳过: ${skipped.length}`
180
+ ctx.body = createSuccessResponse({ success, notFound, skipped }, message)
181
+ } catch (error) {
182
+ ctx.body = createErrorResponse(error.message)
183
+ }
184
+ })
185
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * 文件名: file-config-router.js
3
+ * 功能: 文件配置路由(mock 开关、锁定开关)
4
+ * 依赖: util.js, validator.js, router-helpers.js
5
+ *
6
+ * 职责:
7
+ * - 切换 mock 状态(启用/禁用 mock)
8
+ * - 切换锁定状态(防止误删)
9
+ *
10
+ * 路由:
11
+ * - POST /cgi-bin/mockbubu/update-api-mock - 更新 mock 开关
12
+ * - POST /cgi-bin/mockbubu/update-api-lock - 更新锁定开关
13
+ */
14
+
15
+ const { createSuccessResponse } = require('../../utils/util')
16
+ const {
17
+ validateFilename,
18
+ validateBoolean,
19
+ validate,
20
+ } = require('../../utils/validator')
21
+ const { wrapRouteHandler } = require('../../utils/router-helpers')
22
+
23
+ module.exports = (router) => {
24
+ /**
25
+ * 更新 mock 开关
26
+ *
27
+ * 功能:
28
+ * - 切换文件的 mock 状态
29
+ * - 更新 mockTime 时间戳
30
+ *
31
+ * 请求体:
32
+ * ```javascript
33
+ * {
34
+ * name: 'https://api.example.com/users', // 文件名(URL)
35
+ * mock: true // mock 状态(true/false)
36
+ * }
37
+ * ```
38
+ *
39
+ * 响应:
40
+ * ```javascript
41
+ * {
42
+ * code: 0,
43
+ * data: null,
44
+ * msg: '更新成功'
45
+ * }
46
+ * ```
47
+ *
48
+ * 工作原理:
49
+ * - mock = true: 启用 mock,请求返回缓存的响应
50
+ * - mock = false: 禁用 mock,请求代理到真实服务器
51
+ */
52
+ router.post('/cgi-bin/mockbubu/update-api-mock', validate({
53
+ name: validateFilename,
54
+ mock: (val) => validateBoolean(val, 'mock'),
55
+ }), wrapRouteHandler(async (ctx) => {
56
+ const { groupManager } = ctx
57
+ const { name, mock } = ctx.request.body
58
+
59
+ const currentGroupId = await groupManager.getCurrentGroupId()
60
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
61
+
62
+ fileConfig.mock = mock
63
+ fileConfig.mockTime = Date.now()
64
+
65
+ await groupManager.setGroupFileConfig(currentGroupId, name, fileConfig)
66
+
67
+ // 返回更新后的配置(包含 mockTime)
68
+ ctx.body = createSuccessResponse({
69
+ mock: fileConfig.mock,
70
+ mockTime: fileConfig.mockTime,
71
+ }, '更新成功')
72
+ }, '更新mock开关'))
73
+
74
+ /**
75
+ * 更新锁定开关
76
+ *
77
+ * 功能:
78
+ * - 切换文件的锁定状态
79
+ * - 锁定后无法删除(防止误删重要 mock)
80
+ *
81
+ * 请求体:
82
+ * ```javascript
83
+ * {
84
+ * name: 'https://api.example.com/users', // 文件名(URL)
85
+ * locked: true // 锁定状态(true/false)
86
+ * }
87
+ * ```
88
+ *
89
+ * 响应:
90
+ * ```javascript
91
+ * {
92
+ * code: 0,
93
+ * data: null,
94
+ * msg: '更新成功'
95
+ * }
96
+ * ```
97
+ *
98
+ * 工作原理:
99
+ * - locked = true: 文件被锁定,无法删除
100
+ * - locked = false: 文件未锁定,可以删除
101
+ */
102
+ router.post('/cgi-bin/mockbubu/update-api-lock', validate({
103
+ name: validateFilename,
104
+ locked: (val) => validateBoolean(val, 'locked'),
105
+ }), wrapRouteHandler(async (ctx) => {
106
+ const { groupManager } = ctx
107
+ const { name, locked } = ctx.request.body
108
+
109
+ const currentGroupId = await groupManager.getCurrentGroupId()
110
+ const fileConfig = await groupManager.getGroupFileConfig(currentGroupId, name)
111
+
112
+ fileConfig.locked = locked
113
+
114
+ await groupManager.setGroupFileConfig(currentGroupId, name, fileConfig)
115
+
116
+ ctx.body = createSuccessResponse(null, '更新成功')
117
+ }, '更新锁定开关'))
118
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * 文件名: file-crud-router.js
3
+ * 功能: 文件 CRUD 路由(读取、更新、删除)
4
+ * 依赖: utils.js, util.js, validator.js
5
+ *
6
+ * 职责:
7
+ * - 获取文件详情(包含 session 数据)
8
+ * - 更新文件内容
9
+ * - 删除文件(支持锁定保护)
10
+ *
11
+ * 路由:
12
+ * - POST /cgi-bin/mockbubu/get-api-data - 获取文件详情
13
+ * - POST /cgi-bin/mockbubu/update-api-data - 更新文件内容
14
+ * - POST /cgi-bin/mockbubu/delete-api - 删除文件
15
+ */
16
+
17
+ const {
18
+ updateFile,
19
+ readFile,
20
+ readSession,
21
+ } = require('../../../utils/utils')
22
+ const {
23
+ createErrorResponse,
24
+ createSuccessResponse,
25
+ } = require('../../utils/util')
26
+ const {
27
+ validateFilename,
28
+ validateMockData,
29
+ validate,
30
+ } = require('../../utils/validator')
31
+
32
+ module.exports = (router) => {
33
+ /**
34
+ * 获取文件详情
35
+ *
36
+ * 功能:
37
+ * - 读取文件内容
38
+ * - 读取完整 session 数据(req + res)
39
+ * - 读取文件配置(mock 状态、锁定状态等)
40
+ *
41
+ * 请求体:
42
+ * ```javascript
43
+ * {
44
+ * name: 'https://api.example.com/users' // 文件名(URL)
45
+ * }
46
+ * ```
47
+ *
48
+ * 响应:
49
+ * ```javascript
50
+ * {
51
+ * code: 0,
52
+ * data: {
53
+ * name: 'https://api.example.com/users',
54
+ * data: '{"data": [...]}', // 响应体内容
55
+ * session: { // 完整 session 数据
56
+ * req: {...}, // 请求信息
57
+ * res: {...} // 响应信息
58
+ * },
59
+ * mock: false, // mock 状态
60
+ * locked: false, // 锁定状态
61
+ * mockTime: 1762891525570
62
+ * },
63
+ * msg: 'success'
64
+ * }
65
+ * ```
66
+ */
67
+ router.post('/cgi-bin/mockbubu/get-api-data', validate({
68
+ name: validateFilename,
69
+ }), async (ctx) => {
70
+ try {
71
+ const { storageAdapter } = ctx
72
+ const { groupManager } = ctx
73
+ const { name } = ctx.request.body
74
+
75
+ const currentGroupId = await groupManager.getCurrentGroupId()
76
+
77
+ const file = await readFile(storageAdapter, name, currentGroupId)
78
+ const session = await readSession(storageAdapter, name, currentGroupId)
79
+ const config = await groupManager.getGroupFileConfig(currentGroupId, name)
80
+
81
+ ctx.body = createSuccessResponse({
82
+ ...config,
83
+ name,
84
+ data: file,
85
+ session, // 包含完整的 req 和 res 数据
86
+ })
87
+ } catch (error) {
88
+ ctx.body = createErrorResponse(error.message)
89
+ }
90
+ })
91
+
92
+ /**
93
+ * 更新文件内容
94
+ *
95
+ * 功能:
96
+ * - 更新 mock 文件的响应体内容
97
+ * - 仅更新 res.body 字段
98
+ * - 返回更新后的文件信息
99
+ *
100
+ * 请求体:
101
+ * ```javascript
102
+ * {
103
+ * name: 'https://api.example.com/users', // 文件名(URL)
104
+ * data: { data: [...] } // 新的响应数据(对象)
105
+ * }
106
+ * ```
107
+ *
108
+ * 响应:
109
+ * ```javascript
110
+ * {
111
+ * code: 0,
112
+ * data: {
113
+ * name: 'https://api.example.com/users',
114
+ * data: '{"data": [...]}', // 更新后的内容
115
+ * mock: false,
116
+ * locked: false,
117
+ * mockTime: 1762891525570
118
+ * },
119
+ * msg: 'success'
120
+ * }
121
+ * ```
122
+ */
123
+ router.post('/cgi-bin/mockbubu/update-api-data', validate({
124
+ name: validateFilename,
125
+ data: validateMockData,
126
+ }), async (ctx) => {
127
+ try {
128
+ const { storageAdapter } = ctx
129
+ const { groupManager } = ctx
130
+ const { name, data } = ctx.request.body
131
+
132
+ const currentGroupId = await groupManager.getCurrentGroupId()
133
+
134
+ await updateFile(storageAdapter, name, data, currentGroupId)
135
+ const file = await readFile(storageAdapter, name, currentGroupId)
136
+ const config = await groupManager.getGroupFileConfig(currentGroupId, name)
137
+
138
+ ctx.body = createSuccessResponse({
139
+ ...config,
140
+ name,
141
+ data: file,
142
+ })
143
+ } catch (error) {
144
+ ctx.body = createErrorResponse(error.message)
145
+ }
146
+ })
147
+
148
+ /**
149
+ * 删除文件(智能删除)
150
+ *
151
+ * 功能:
152
+ * - 删除物理文件
153
+ * - 删除文件配置
154
+ * - 支持锁定保护(锁定的文件无法删除)
155
+ *
156
+ * 请求体:
157
+ * ```javascript
158
+ * {
159
+ * name: 'https://api.example.com/users', // 文件名(URL)
160
+ * groupId: 'default' // 可选,目标组ID
161
+ * }
162
+ * ```
163
+ *
164
+ * 响应:
165
+ * ```javascript
166
+ * {
167
+ * code: 0,
168
+ * data: null,
169
+ * msg: '删除成功'
170
+ * }
171
+ * ```
172
+ *
173
+ * 错误处理:
174
+ * - 如果文件被锁定,返回错误信息
175
+ */
176
+ router.post('/cgi-bin/mockbubu/delete-api', validate({
177
+ name: validateFilename,
178
+ }), async (ctx) => {
179
+ try {
180
+ const { storageAdapter } = ctx
181
+ const { groupManager } = ctx
182
+ const { name, groupId } = ctx.request.body
183
+
184
+ // 优先使用传入的 groupId,否则使用当前组ID
185
+ const targetGroupId = groupId || await groupManager.getCurrentGroupId()
186
+
187
+ // [mockbubu] 单文件删除日志 (delete-api)
188
+ console.log('[mockbubu] delete-api 开始', new Date().toLocaleString('zh-CN', { hour12: false }))
189
+ console.log('[mockbubu] 待删除文件:', name)
190
+ console.log('[mockbubu] 目标组ID:', targetGroupId)
191
+
192
+ // ✅ 检查文件是否被锁定
193
+ const fileConfig = await groupManager.getGroupFileConfig(targetGroupId, name)
194
+ if (fileConfig && fileConfig.locked) {
195
+ console.log('[mockbubu] delete-api 失败: 文件已锁定', name)
196
+ ctx.body = createErrorResponse('文件已锁定,无法删除')
197
+ return
198
+ }
199
+
200
+ // 完全隔离架构:直接删除物理文件 + 组配置
201
+ // 注意:storageAdapter.removeFile 是 async 方法,必须 await
202
+ await storageAdapter.removeFile(`${targetGroupId}/${name}`)
203
+ await groupManager.removeGroupFileConfig(targetGroupId, name)
204
+
205
+ console.log('[mockbubu] delete-api 删除成功:', name)
206
+
207
+ ctx.body = createSuccessResponse(null, '删除成功')
208
+ } catch (error) {
209
+ ctx.body = createErrorResponse(error.message)
210
+ }
211
+ })
212
+ }