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.
- package/README.md +38 -0
- package/index.js +3 -3
- package/lib/config/const.js +138 -0
- package/lib/config/rule-collector.js +81 -0
- package/lib/constants.js +62 -0
- package/lib/core/memory-buffer/index.js +207 -0
- package/lib/core/memory-buffer/shared-instance.js +15 -0
- package/lib/core/plugin-mode-manager.js +74 -0
- package/lib/core/resRulesServer.js +14 -0
- package/lib/core/rulesServer.js +31 -0
- package/lib/core/server-entry/capture-handler.js +191 -0
- package/lib/core/server-entry/request-interceptor.js +82 -0
- package/lib/core/server-entry/response-handler.js +147 -0
- package/lib/core/server-entry/server.js +230 -0
- package/lib/storage/group-manager.js +627 -0
- package/lib/storage/storage-adapter.js +712 -0
- package/lib/storage/storage-v3.js +1418 -0
- package/lib/uiServer/index.js +61 -24
- package/lib/uiServer/router/export/import-export-router.js +459 -0
- package/lib/uiServer/router/files/api-list-router.js +150 -0
- package/lib/uiServer/router/files/batch-operations-router.js +185 -0
- package/lib/uiServer/router/files/file-config-router.js +118 -0
- package/lib/uiServer/router/files/file-crud-router.js +212 -0
- package/lib/uiServer/router/files/file-save-router.js +146 -0
- package/lib/uiServer/router/files/version-router.js +260 -0
- package/lib/uiServer/router/global/plugin-control.js +135 -0
- package/lib/uiServer/router/global/system-info-router.js +386 -0
- package/lib/uiServer/router/{group-router.js → groups/group-router.js} +21 -20
- package/lib/uiServer/router/index.js +38 -1521
- package/lib/uiServer/utils/router-helpers.js +100 -0
- package/lib/uiServer/utils/util.js +172 -0
- package/lib/uiServer/{validator.js → utils/validator.js} +11 -6
- package/lib/utils/archive-utils.js +788 -0
- package/lib/utils/error-handler.js +173 -0
- package/lib/utils/logger.js +79 -0
- package/lib/utils/path-utils.js +147 -0
- package/lib/utils/performance.js +265 -0
- package/lib/utils/utils.js +541 -0
- package/package.json +2 -2
- package/public/js/app.js +3707 -1922
- package/public/js/app.js.map +1 -1
- package/public/js/chunk-vendors.js +5098 -3965
- package/public/js/chunk-vendors.js.map +1 -1
- package/rules.txt +1 -1
- package/CHANGELOG_GROUP_FEATURE.md +0 -468
- package/CHANGELOG_P0_FIXES.md +0 -412
- package/CHANGELOG_P1_OPTIMIZATIONS.md +0 -292
- package/CLAUDE.md +0 -436
- package/GROUP_FEATURE_DESIGN.md +0 -520
- package/lib/const.js +0 -47
- package/lib/group-manager.js +0 -491
- package/lib/resRulesServer.js +0 -9
- package/lib/server.js +0 -249
- package/lib/uiServer/router/version-router.js +0 -205
- package/lib/uiServer/util.js +0 -153
- 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
|
+
}
|