whistle.mockbubu 2.0.0 → 2.1.1

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 +3732 -1929
  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
package/README.md CHANGED
@@ -117,3 +117,41 @@ pattern mockbubu:// 或者 pattern mockbubu://pathname
117
117
  - mock 返回内容使用当前选中的版本
118
118
 
119
119
  [![step2.png](https://i.postimg.cc/K8wsg8xv/step2.png)](https://postimg.cc/Js3Qfms9)
120
+
121
+ ---
122
+
123
+ ## 开发者文档
124
+
125
+ 如果你需要参与 whistle.mockbubu 的开发、维护或迭代,请阅读以下文档:
126
+
127
+ 📖 **[开发者指南 (DEVELOPER-GUIDE.md)](./docs/DEVELOPER-GUIDE.md)**
128
+
129
+ 包含以下内容:
130
+ - 项目架构设计
131
+ - 完整业务流程
132
+ - 关键业务逻辑和判断
133
+ - 数据存储设计
134
+ - API 接口清单
135
+ - 重要注意事项
136
+ - 迭代开发指南(影响范围和可行性分析)
137
+ - 故障排查指南
138
+ - 编码规范
139
+
140
+ 📋 **[编码规范 (CODE-STANDARDS.md)](./docs/CODE-STANDARDS.md)**
141
+
142
+ 代码质量标准和最佳实践
143
+
144
+ ---
145
+
146
+ ## 贡献
147
+
148
+ 欢迎提交 Issue 和 Pull Request。在提交 PR 前,请确保:
149
+
150
+ 1. 阅读了开发者指南
151
+ 2. 遵循编码规范
152
+ 3. 测试了所有相关功能
153
+ 4. 更新了相关文档
154
+
155
+ ## License
156
+
157
+ MIT
package/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  exports.uiServer = require('./lib/uiServer')
2
- // exports.rulesServer = require('./lib/rulesServer')
2
+ exports.rulesServer = require('./lib/core/rulesServer')
3
3
  // exports.tunnelRulesServer = require('./lib/tunnelRulesServer');
4
- exports.resRulesServer = require('./lib/resRulesServer')
4
+ exports.resRulesServer = require('./lib/core/resRulesServer')
5
5
  // exports.statsServer = require('./lib/statsServer')
6
6
  // exports.resStatsServer = require('./lib/resStatsServer')
7
- exports.server = require('./lib/server')
7
+ exports.server = require('./lib/core/server-entry/server')
8
8
  // exports.reqRead = require('./lib/reqRead')
9
9
  // exports.reqWrite = require('./lib/reqWrite');
10
10
  // exports.resRead = require('./lib/resRead')
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 文件名: const.js
3
+ * 功能: 全局常量定义
4
+ * 更新: 2025-12-02
5
+ *
6
+ * 包含:
7
+ * - 规则类型映射
8
+ * - 过滤器配置
9
+ * - 业务规则常量
10
+ * - 系统常量
11
+ */
12
+
13
+ // ============================================
14
+ // Whistle 规则相关常量
15
+ // ============================================
16
+
17
+ /**
18
+ * 规则值类型映射
19
+ * 用于 Whistle 规则中的 mockbubu://[mode] 参数
20
+ */
21
+ exports.RuleValueMap = {
22
+ href: 'href', // 完整URL (包含query)
23
+ pathname: 'pathname', // origin + pathname (默认)
24
+ pattern: 'pattern', // 使用 Whistle pattern 作为文件名
25
+ }
26
+
27
+ /**
28
+ * 范围筛选类型映射
29
+ * 用于UI中的范围筛选下拉框
30
+ */
31
+ exports.RangeMap = {
32
+ MOCKING: 'mocking', // 正在mock的文件
33
+ NOT_MOCKING: 'not-mocking', // 未mock的文件
34
+ LOCKED: 'locked', // 已锁定的文件
35
+ }
36
+
37
+ /**
38
+ * 范围筛选配置映射
39
+ * 定义每种范围筛选的具体过滤条件
40
+ */
41
+ exports.RangeFilterMap = {
42
+ 'mocking': { key: 'mock', value: true, method: 'equal' },
43
+ 'not-mocking': { key: 'mock', value: false, method: 'equal' },
44
+ 'locked': { key: 'locked', value: true, method: 'equal' },
45
+ }
46
+
47
+ /**
48
+ * 规则类型筛选配置映射
49
+ * 用于按规则类型 (href/pathname/pattern) 过滤文件
50
+ */
51
+ exports.RuleFilterMap = {
52
+ 'href': { key: 'ruleValue', value: 'href', method: 'equal' },
53
+ 'pathname': { key: 'ruleValue', value: 'pathname', method: 'equal' },
54
+ 'pattern': { key: 'ruleValue', value: 'pattern', method: 'equal' },
55
+ }
56
+
57
+ /**
58
+ * 锁定状态筛选配置映射
59
+ * 用于按锁定状态过滤文件
60
+ */
61
+ exports.LockedFilterMap = {
62
+ 'locked': { key: 'locked', value: true, method: 'equal' },
63
+ 'unlocked': { key: 'locked', value: false, method: 'equal' },
64
+ }
65
+
66
+ // ============================================
67
+ // 业务规则常量
68
+ // ============================================
69
+
70
+ /**
71
+ * 业务规则常量
72
+ * 定义系统的各种限制和规则
73
+ */
74
+ exports.BusinessRules = {
75
+ // 版本管理
76
+ MAX_VERSIONS_PER_FILE: 10, // 每个文件最多10个版本
77
+ MAX_VERSION_NAME_LENGTH: 20, // 版本名最长20字符
78
+
79
+ // 组管理
80
+ MAX_GROUPS: 100, // 最多100个组
81
+
82
+ // 文件管理
83
+ MAX_FILENAME_LENGTH: 2048, // 文件名最长2048字符
84
+ MAX_MOCK_DATA_SIZE: 5 * 1024 * 1024, // Mock数据最大5MB
85
+
86
+ // 缓存
87
+ INDEX_CACHE_TTL: 60 * 1000, // 索引缓存1分钟过期 (60秒)
88
+ RULE_AUTO_EXPIRE_TIME: 10 * 1000, // 规则10秒无请求自动过期
89
+
90
+ // 临时文件清理
91
+ TEMP_DIR_MAX_AGE: 7 * 24 * 60 * 60 * 1000, // 临时目录保留7天
92
+ TEMP_CLEANUP_RETRIES: 3, // 临时目录清理重试3次
93
+ TEMP_CLEANUP_RETRY_DELAY: 100, // 重试间隔100ms
94
+ }
95
+
96
+ // ============================================
97
+ // 系统常量
98
+ // ============================================
99
+
100
+ /**
101
+ * 系统常量
102
+ * 定义系统级别的固定值
103
+ */
104
+ exports.SystemConstants = {
105
+ // 默认组
106
+ DEFAULT_GROUP_ID: 'default',
107
+ DEFAULT_GROUP_NAME: '默认组',
108
+
109
+ // 存储键名
110
+ GROUPS_META_KEY: '__groups__', // 组元数据的存储键
111
+ API_LIST_UPDATED_KEY: 'api-list-updated', // API列表更新标志的存储键
112
+ PLUGIN_ENABLED_KEY: 'mockbubu.enabled', // 插件启用状态的存储键(已废弃,保留用于迁移)
113
+
114
+ // 版本相关
115
+ DEFAULT_VERSION_NAME: 'source', // 默认版本名称 (原始响应)
116
+ }
117
+
118
+ // ============================================
119
+ // 插件模式相关常量(直接导出,便于引用)
120
+ // ============================================
121
+
122
+ /**
123
+ * 插件工作模式的存储键
124
+ */
125
+ exports.PLUGIN_MODE_KEY = 'mockbubu.mode'
126
+
127
+ /**
128
+ * 插件工作模式枚举
129
+ */
130
+ exports.PLUGIN_MODE = {
131
+ DEFAULT: 'default', // 默认模式:全功能(捕获新请求 + 返回Mock)
132
+ MOCK_ONLY: 'mock-only', // 仅Mock模式:不捕获,仅返回已开启Mock的数据
133
+ }
134
+
135
+ /**
136
+ * 默认插件模式
137
+ */
138
+ exports.DEFAULT_PLUGIN_MODE = 'default'
@@ -0,0 +1,81 @@
1
+ /**
2
+ * 规则收集器 - 收集和管理生效的 Whistle 规则
3
+ * 用于在 UI 中显示帮助提示
4
+ *
5
+ * 设计原则:
6
+ * - 规则列表在每次请求到来时动态更新
7
+ * - API 查询前先清空,确保返回的是当前实际生效的规则
8
+ */
9
+
10
+ class RuleCollector {
11
+ constructor() {
12
+ this.activeRules = new Set()
13
+ this.lastRequestTime = 0
14
+ }
15
+
16
+ /**
17
+ * 添加一条生效的规则
18
+ * @param {string} rule - 规则字符串,如 "*.shein.com mockbubu://pathname"
19
+ */
20
+ addRule(rule) {
21
+ this.activeRules.add(rule)
22
+ this.lastRequestTime = Date.now()
23
+ }
24
+
25
+ /**
26
+ * 开始新的收集周期(清空旧规则)
27
+ * 应该在 API 查询开始时调用
28
+ */
29
+ startNewCollection() {
30
+ this.activeRules.clear()
31
+ this.lastRequestTime = Date.now()
32
+ }
33
+
34
+ /**
35
+ * 获取所有生效的规则列表
36
+ * 如果超过10秒没有新请求,自动清空(说明规则可能已关闭)
37
+ * @returns {Array<string>} 规则数组
38
+ */
39
+ getRules() {
40
+ const now = Date.now()
41
+ const timeSinceLastRequest = now - this.lastRequestTime
42
+
43
+ // 如果超过10秒没有新请求,说明规则可能已关闭
44
+ if (this.lastRequestTime > 0 && timeSinceLastRequest > 10000) {
45
+ console.log('[mockbubu] 🧹 超过10秒无请求,清空规则列表')
46
+ this.activeRules.clear()
47
+ this.lastRequestTime = 0
48
+ }
49
+
50
+ return Array.from(this.activeRules).sort()
51
+ }
52
+
53
+ /**
54
+ * 清空所有规则
55
+ */
56
+ clear() {
57
+ this.activeRules.clear()
58
+ this.lastRequestTime = 0
59
+ }
60
+
61
+ /**
62
+ * 获取规则数量
63
+ * @returns {number}
64
+ */
65
+ getCount() {
66
+ // 获取数量时也要检查是否需要清空
67
+ this.getRules()
68
+ return this.activeRules.size
69
+ }
70
+
71
+ /**
72
+ * 获取最后一次收到请求的时间
73
+ * @returns {number} 时间戳
74
+ */
75
+ getLastRequestTime() {
76
+ return this.lastRequestTime
77
+ }
78
+ }
79
+
80
+ // 导出单例
81
+ module.exports = new RuleCollector()
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 全局常量定义
3
+ *
4
+ * 用途:
5
+ * - 避免魔法数字
6
+ * - 集中管理配置
7
+ * - 提高代码可维护性
8
+ */
9
+
10
+ /**
11
+ * 缓存容量限制
12
+ */
13
+ const CACHE_LIMITS = {
14
+ // 后端暂存区最大容量(超出触发滑动窗口清理)
15
+ BACKEND_STAGING_MAX_SIZE: 200,
16
+
17
+ // 前端缓存最大容量
18
+ FRONTEND_CACHE_MAX_SIZE: 2000,
19
+
20
+ // 滑动窗口清理批次大小(每次清理最旧的N条)
21
+ CLEANUP_BATCH_SIZE: 50,
22
+ }
23
+
24
+ /**
25
+ * 轮询相关配置
26
+ */
27
+ const POLLING_CONFIG = {
28
+ // 前端轮询间隔(毫秒)
29
+ INTERVAL: 2000,
30
+
31
+ // 首次轮询的 startTime 值
32
+ INITIAL_START_TIME: 0,
33
+ }
34
+
35
+ /**
36
+ * 文件操作相关配置
37
+ */
38
+ const FILE_CONFIG = {
39
+ // 版本数量上限
40
+ MAX_VERSIONS: 10,
41
+
42
+ // 默认版本名称
43
+ DEFAULT_VERSION_NAME: 'source',
44
+ }
45
+
46
+ /**
47
+ * 日志前缀
48
+ */
49
+ const LOG_PREFIX = {
50
+ MEMORY_BUFFER: '[MemoryBuffer]',
51
+ CAPTURE_HANDLER: '[CaptureHandler]',
52
+ API_LIST: '[ApiList]',
53
+ FILE_SAVE: '[FileSave]',
54
+ UI_SERVER: '[uiServer]',
55
+ }
56
+
57
+ module.exports = {
58
+ CACHE_LIMITS,
59
+ POLLING_CONFIG,
60
+ FILE_CONFIG,
61
+ LOG_PREFIX,
62
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * 内存缓冲区
3
+ *
4
+ * 职责:
5
+ * - 捕获请求后暂存,供前端拉取
6
+ * - URL 去重(已存在则跳过)
7
+ * - 容量管理(滑动窗口清理)
8
+ *
9
+ * 核心规则:
10
+ * - 取出即清空(无论第一次还是后续轮询)
11
+ *
12
+ * 生命周期:
13
+ * - 进程重启清空
14
+ */
15
+
16
+ const { CACHE_LIMITS, LOG_PREFIX } = require('../../constants')
17
+
18
+ class MemoryBuffer {
19
+ /**
20
+ * 构造函数
21
+ *
22
+ * @param {Object} options - 配置选项
23
+ * @param {number} options.maxSize - 最大容量
24
+ * @param {number} options.cleanupBatchSize - 滑动窗口清理批次大小
25
+ */
26
+ constructor(options = {}) {
27
+ // Key: `${groupId}/${url}`
28
+ // Value: { url, method, status, session, captureTime }
29
+ this.stagingRequests = new Map()
30
+
31
+ this.maxSize = options.maxSize || CACHE_LIMITS.BACKEND_STAGING_MAX_SIZE
32
+ this.cleanupBatchSize = options.cleanupBatchSize || CACHE_LIMITS.CLEANUP_BATCH_SIZE
33
+ }
34
+
35
+ /**
36
+ * 添加捕获的请求
37
+ *
38
+ * 去重规则:URL 已存在时跳过新数据(保留旧的)
39
+ *
40
+ * @param {string} groupId - 组ID
41
+ * @param {string} url - 请求URL
42
+ * @param {Object} data - 捕获数据
43
+ * @param {Object} data.session - 完整会话数据
44
+ * @param {string} data.method - HTTP方法
45
+ * @param {number} data.status - 状态码
46
+ */
47
+ add(groupId, url, data) {
48
+ // 参数校验
49
+ if (!groupId || !url || !data || !data.session) {
50
+ console.error(`${LOG_PREFIX.MEMORY_BUFFER} add 参数不完整`, {
51
+ hasGroupId: !!groupId,
52
+ hasUrl: !!url,
53
+ hasData: !!data,
54
+ hasSession: !!(data && data.session),
55
+ })
56
+ return
57
+ }
58
+
59
+ const key = `${groupId}/${url}`
60
+
61
+ // 去重:已存在则跳过(保留旧的)
62
+ if (this.stagingRequests.has(key)) {
63
+ console.log(`${LOG_PREFIX.MEMORY_BUFFER} 暂存区已存在,跳过: ${url}`)
64
+ return
65
+ }
66
+
67
+ // 新增到暂存区
68
+ this.stagingRequests.set(key, {
69
+ url,
70
+ method: data.method,
71
+ status: data.status,
72
+ session: data.session,
73
+ captureTime: Date.now(),
74
+ })
75
+
76
+ console.log(`${LOG_PREFIX.MEMORY_BUFFER} 暂存区新增: ${url} (当前容量: ${this.stagingRequests.size})`)
77
+
78
+ // 容量检查:超出上限触发滑动窗口清理
79
+ if (this.stagingRequests.size > this.maxSize) {
80
+ this.slidingWindowCleanup()
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 获取增量数据并清空
86
+ *
87
+ * 统一的轮询逻辑:
88
+ * - startTime=0: 第一次轮询,返回所有数据
89
+ * - startTime>0: 后续轮询,返回增量数据
90
+ *
91
+ * 无论哪种情况,取出即清空
92
+ *
93
+ * @param {string} groupId - 组ID
94
+ * @param {number} startTime - 起始时间戳
95
+ * @returns {Array<Object>} 请求数据列表
96
+ */
97
+ getAndClear(groupId, startTime) {
98
+ // 参数校验
99
+ if (!groupId || typeof startTime !== 'number') {
100
+ console.error(`${LOG_PREFIX.MEMORY_BUFFER} getAndClear 参数不完整`, {
101
+ hasGroupId: !!groupId,
102
+ startTimeType: typeof startTime,
103
+ })
104
+ return []
105
+ }
106
+
107
+ const result = []
108
+ const keysToDelete = []
109
+
110
+ // 遍历暂存区
111
+ for (const [key, value] of this.stagingRequests) {
112
+ if (key.startsWith(`${groupId}/`)) {
113
+ // startTime=0: 返回所有数据(第一次轮询)
114
+ // startTime>0: 返回增量数据
115
+ if (startTime === 0 || value.captureTime > startTime) {
116
+ result.push(value)
117
+ keysToDelete.push(key)
118
+ }
119
+ }
120
+ }
121
+
122
+ // 取出即清空
123
+ keysToDelete.forEach(key => {
124
+ this.stagingRequests.delete(key)
125
+ })
126
+
127
+ if (keysToDelete.length > 0) {
128
+ console.log(`${LOG_PREFIX.MEMORY_BUFFER} 返回 ${result.length} 条并清空 (剩余容量: ${this.stagingRequests.size})`)
129
+ }
130
+
131
+ return result
132
+ }
133
+
134
+ /**
135
+ * 滑动窗口清理
136
+ *
137
+ * 清理策略:
138
+ * - 按 captureTime 排序(最旧的在前)
139
+ * - 删除最旧的 cleanupBatchSize 条数据
140
+ */
141
+ slidingWindowCleanup() {
142
+ const entries = Array.from(this.stagingRequests.entries())
143
+
144
+ // 按时间排序(最旧的在前)
145
+ entries.sort((a, b) => a[1].captureTime - b[1].captureTime)
146
+
147
+ // 删除最旧的数据
148
+ const toRemove = entries.slice(0, this.cleanupBatchSize)
149
+ toRemove.forEach(([key]) => {
150
+ this.stagingRequests.delete(key)
151
+ })
152
+
153
+ console.log(`${LOG_PREFIX.MEMORY_BUFFER} 滑动窗口清理 ${toRemove.length} 条 (剩余容量: ${this.stagingRequests.size})`)
154
+ }
155
+
156
+ /**
157
+ * 清空指定组的所有数据
158
+ *
159
+ * 场景:切换组或删除组时清理对应的暂存数据
160
+ *
161
+ * @param {string} groupId - 组ID
162
+ * @returns {number} 清除的数量
163
+ */
164
+ clearGroup(groupId) {
165
+ // 参数校验
166
+ if (!groupId) {
167
+ console.error(`${LOG_PREFIX.MEMORY_BUFFER} clearGroup 参数不完整`)
168
+ return 0
169
+ }
170
+
171
+ let clearedCount = 0
172
+ const keysToDelete = []
173
+
174
+ // 遍历找到该组的所有数据
175
+ for (const [key] of this.stagingRequests) {
176
+ if (key.startsWith(`${groupId}/`)) {
177
+ keysToDelete.push(key)
178
+ }
179
+ }
180
+
181
+ // 删除数据
182
+ keysToDelete.forEach(key => {
183
+ this.stagingRequests.delete(key)
184
+ clearedCount++
185
+ })
186
+
187
+ if (clearedCount > 0) {
188
+ console.log(`${LOG_PREFIX.MEMORY_BUFFER} 清空组数据: ${groupId}, 已删除 ${clearedCount} 条 (剩余容量: ${this.stagingRequests.size})`)
189
+ }
190
+
191
+ return clearedCount
192
+ }
193
+
194
+ /**
195
+ * 获取统计信息
196
+ *
197
+ * @returns {Object} 统计信息
198
+ */
199
+ getStats() {
200
+ return {
201
+ size: this.stagingRequests.size,
202
+ maxSize: this.maxSize,
203
+ }
204
+ }
205
+ }
206
+
207
+ module.exports = { MemoryBuffer }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 共享的内存缓冲区实例
3
+ *
4
+ * 职责:
5
+ * - 提供单例的 MemoryBuffer 实例
6
+ * - 被 server.js(捕获端)和 uiServer(UI端)共享
7
+ * - 确保数据流转的唯一入口
8
+ */
9
+
10
+ const { MemoryBuffer } = require('./index')
11
+
12
+ // 创建单例实例
13
+ const sharedMemoryBuffer = new MemoryBuffer()
14
+
15
+ module.exports = sharedMemoryBuffer
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 文件名: plugin-mode-manager.js
3
+ * 功能: 插件模式管理器(单例)
4
+ * 职责:
5
+ * - 缓存当前插件模式
6
+ * - 提供同步读取模式的方法
7
+ * - 提供异步更新模式的方法
8
+ */
9
+
10
+ const { PLUGIN_MODE, DEFAULT_PLUGIN_MODE } = require('../config/const')
11
+
12
+ /**
13
+ * 插件模式管理器(单例模式)
14
+ */
15
+ class PluginModeManager {
16
+ constructor() {
17
+ // 当前模式缓存
18
+ this.currentMode = DEFAULT_PLUGIN_MODE
19
+ // Storage 引用(在 init 时设置)
20
+ this.storage = null
21
+ }
22
+
23
+ /**
24
+ * 初始化模式管理器
25
+ * @param {Object} storage - Whistle storage 对象
26
+ */
27
+ async init(storage) {
28
+ this.storage = storage
29
+ await this.refresh()
30
+ console.log(`[mockbubu] ✅ 插件模式管理器初始化完成: ${this.currentMode}`)
31
+ }
32
+
33
+ /**
34
+ * 从 storage 刷新模式缓存
35
+ */
36
+ async refresh() {
37
+ if (!this.storage) {
38
+ return
39
+ }
40
+
41
+ const { PLUGIN_MODE_KEY } = require('../config/const')
42
+ const mode = await this.storage.getProperty(PLUGIN_MODE_KEY)
43
+ this.currentMode = Object.values(PLUGIN_MODE).includes(mode) ? mode : DEFAULT_PLUGIN_MODE
44
+ }
45
+
46
+ /**
47
+ * 获取当前模式(同步方法)
48
+ * @returns {string} 'capture' | 'mock-only'
49
+ */
50
+ getMode() {
51
+ return this.currentMode
52
+ }
53
+
54
+ /**
55
+ * 设置模式(异步方法)
56
+ * @param {string} newMode - 新模式
57
+ */
58
+ async setMode(newMode) {
59
+ if (!Object.values(PLUGIN_MODE).includes(newMode)) {
60
+ throw new Error(`无效的模式: ${newMode}`)
61
+ }
62
+
63
+ if (!this.storage) {
64
+ throw new Error('Storage 未初始化')
65
+ }
66
+
67
+ const { PLUGIN_MODE_KEY } = require('../config/const')
68
+ await this.storage.setProperty(PLUGIN_MODE_KEY, newMode)
69
+ this.currentMode = newMode
70
+ }
71
+ }
72
+
73
+ // 导出单例实例
74
+ module.exports = new PluginModeManager()
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Whistle resRulesServer 钩子
3
+ * 为使用了 Mock 数据的请求添加样式标记(在 Network 面板中显示)
4
+ */
5
+ module.exports = (server, options) => {
6
+ server.on('request', (req, res) => {
7
+ // 检查响应头中是否有 x-mockbubu-mocked 标记(表示返回了 Mock 数据)
8
+ if (req?.headers?.['x-mockbubu-mocked'] === 'true') {
9
+ res.end('* style://color=@2c8bea&bgColor=@0dd142&fontStyle=bold')
10
+ return
11
+ }
12
+ res.end()
13
+ })
14
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * rulesServer 钩子
3
+ * 动态返回 mockbubu 规则,根据启用状态控制
4
+ */
5
+ module.exports = (server, options) => {
6
+ server.on('request', (req, res) => {
7
+ const storage = options.storage
8
+
9
+ // 从 properties 读取启用状态,默认为启用
10
+ const enabled = storage.getProperty('mockbubu.enabled') !== false
11
+
12
+ if (!enabled) {
13
+ // 禁用时返回空规则
14
+ console.log('[mockbubu] 🚫 插件已禁用,不返回规则')
15
+ res.end('')
16
+ return
17
+ }
18
+
19
+ // 启用时返回 mockbubu 规则
20
+ // 这里返回通配规则,让所有请求都经过 server 钩子判断
21
+ const rules = `
22
+ # mockbubu 插件规则
23
+ # 所有请求都会经过 mockbubu server 钩子处理
24
+ # 具体是否 mock 由 server 钩子中的 mock 开关决定
25
+ * mockbubu://
26
+ `.trim()
27
+
28
+ console.log('[mockbubu] ✅ 插件已启用,返回规则')
29
+ res.end(rules)
30
+ })
31
+ }