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
@@ -0,0 +1,173 @@
1
+ /**
2
+ * 文件名: error-handler.js
3
+ * 功能: 统一的错误处理工具
4
+ * 依赖: logger.js
5
+ * 更新: 2025-12-02
6
+ *
7
+ * 使用示例:
8
+ * ```javascript
9
+ * const { withErrorHandling, createErrorResponse } = require('./error-handler')
10
+ *
11
+ * // 包装异步函数
12
+ * const safeReadFile = withErrorHandling(
13
+ * readFile,
14
+ * { operationName: '读取文件', rethrow: false }
15
+ * )
16
+ *
17
+ * // 创建错误响应
18
+ * ctx.body = createErrorResponse('文件不存在', 404)
19
+ * ```
20
+ *
21
+ * 注意事项:
22
+ * - 错误会自动记录到日志
23
+ * - 可以选择是否重新抛出异常
24
+ * - 支持自定义错误处理回调
25
+ */
26
+
27
+ const { createLogger } = require('./logger')
28
+
29
+ /**
30
+ * 包装异步函数,添加统一错误处理
31
+ *
32
+ * 自动记录错误日志,可选择是否重新抛出异常
33
+ *
34
+ * @param {Function} fn - 要包装的异步函数
35
+ * @param {Object} options - 配置选项
36
+ * @param {string} options.operationName - 操作名称 (用于日志)
37
+ * @param {boolean} options.rethrow - 是否重新抛出异常 (默认: true)
38
+ * @param {Function} options.onError - 自定义错误处理函数
39
+ * @param {*} options.defaultReturn - 捕获异常后的默认返回值 (默认: null)
40
+ * @returns {Function} 包装后的函数
41
+ *
42
+ * @example
43
+ * // 包装函数,捕获异常但不抛出
44
+ * const safeReadFile = withErrorHandling(
45
+ * readFile,
46
+ * { operationName: '读取文件', rethrow: false }
47
+ * )
48
+ *
49
+ * const content = await safeReadFile('test.json')
50
+ * // 如果读取失败,返回 null 而不是抛出异常
51
+ *
52
+ * @example
53
+ * // 包装函数,自定义错误处理
54
+ * const safeWriteFile = withErrorHandling(
55
+ * writeFile,
56
+ * {
57
+ * operationName: '写入文件',
58
+ * onError: (err, args) => {
59
+ * console.log('写入失败的文件:', args[0])
60
+ * }
61
+ * }
62
+ * )
63
+ */
64
+ function withErrorHandling(fn, options = {}) {
65
+ const {
66
+ operationName = '操作',
67
+ rethrow = true,
68
+ onError = null,
69
+ defaultReturn = null,
70
+ } = options
71
+
72
+ return async function (...args) {
73
+ const logger = createLogger()
74
+ try {
75
+ return await fn.apply(this, args)
76
+ } catch (err) {
77
+ // 记录错误日志
78
+ logger.error(`${operationName}失败:`, err.message)
79
+
80
+ // 仅在开发环境输出完整堆栈
81
+ if (process.env.NODE_ENV === 'development') {
82
+ logger.error('错误堆栈:', err.stack)
83
+ }
84
+
85
+ // 调用自定义错误处理函数
86
+ if (onError) {
87
+ try {
88
+ onError(err, args)
89
+ } catch (callbackErr) {
90
+ logger.error('错误处理回调执行失败:', callbackErr.message)
91
+ }
92
+ }
93
+
94
+ // 是否重新抛出异常
95
+ if (rethrow) {
96
+ throw err
97
+ }
98
+
99
+ return defaultReturn
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * 创建错误响应对象 (用于HTTP响应)
106
+ *
107
+ * @param {string} message - 错误消息
108
+ * @param {number} code - 错误代码 (默认: 500)
109
+ * @param {Object} details - 详细信息 (可选)
110
+ * @returns {Object} 错误响应对象
111
+ *
112
+ * @example
113
+ * createErrorResponse('文件不存在', 404)
114
+ * // => { code: 404, msg: '文件不存在' }
115
+ *
116
+ * createErrorResponse('参数验证失败', 400, { errors: [...] })
117
+ * // => { code: 400, msg: '参数验证失败', data: { errors: [...] } }
118
+ */
119
+ function createErrorResponse(message, code = 500, details = null) {
120
+ return {
121
+ code,
122
+ msg: message || '操作失败',
123
+ ...(details && { data: details }),
124
+ }
125
+ }
126
+
127
+ /**
128
+ * 创建成功响应对象 (用于HTTP响应)
129
+ *
130
+ * @param {*} data - 响应数据
131
+ * @param {string} message - 成功消息 (默认: '操作成功')
132
+ * @returns {Object} 成功响应对象
133
+ *
134
+ * @example
135
+ * createSuccessResponse({ id: 123 })
136
+ * // => { code: 200, msg: '操作成功', data: { id: 123 } }
137
+ *
138
+ * createSuccessResponse(null, '删除成功')
139
+ * // => { code: 200, msg: '删除成功' }
140
+ */
141
+ function createSuccessResponse(data = null, message = '操作成功') {
142
+ return {
143
+ code: 200,
144
+ msg: message,
145
+ ...(data !== null && data !== undefined && { data }),
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 安全执行异步函数 (简化版的 withErrorHandling)
151
+ *
152
+ * @param {Function} fn - 要执行的异步函数
153
+ * @param {string} operationName - 操作名称
154
+ * @returns {Promise<*>} 执行结果,失败时返回 null
155
+ *
156
+ * @example
157
+ * const data = await safeExecute(
158
+ * () => storage.readFile('test.json'),
159
+ * '读取文件'
160
+ * )
161
+ * // 失败时返回 null,不抛出异常
162
+ */
163
+ async function safeExecute(fn, operationName = '操作') {
164
+ const wrapped = withErrorHandling(fn, { operationName, rethrow: false })
165
+ return await wrapped()
166
+ }
167
+
168
+ module.exports = {
169
+ withErrorHandling,
170
+ createErrorResponse,
171
+ createSuccessResponse,
172
+ safeExecute,
173
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 文件名: logger.js
3
+ * 功能: 统一的日志工具,提供带时间戳的日志输出
4
+ * 依赖: 无
5
+ * 更新: 2025-12-02
6
+ *
7
+ * 使用示例:
8
+ * ```javascript
9
+ * const { createLogger } = require('./logger')
10
+ * const logger = createLogger('myModule')
11
+ * logger.log('操作成功')
12
+ * logger.error('操作失败', error)
13
+ * ```
14
+ *
15
+ * 注意事项:
16
+ * - 时间格式统一为: YYYY/MM/DD HH:mm:ss
17
+ * - 日志前缀格式: [moduleName 时间戳]
18
+ */
19
+
20
+ /**
21
+ * 格式化时间戳为中文格式
22
+ *
23
+ * @returns {string} 格式: "2025/12/02 15:30:45"
24
+ *
25
+ * @example
26
+ * formatTimestamp() // "2025/12/02 15:30:45"
27
+ */
28
+ function formatTimestamp() {
29
+ const now = new Date()
30
+ const year = now.getFullYear()
31
+ const month = String(now.getMonth() + 1).padStart(2, '0')
32
+ const day = String(now.getDate()).padStart(2, '0')
33
+ const hours = String(now.getHours()).padStart(2, '0')
34
+ const minutes = String(now.getMinutes()).padStart(2, '0')
35
+ const seconds = String(now.getSeconds()).padStart(2, '0')
36
+ return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
37
+ }
38
+
39
+ /**
40
+ * 创建带时间戳的日志工具
41
+ *
42
+ * 提供统一的日志输出格式: [prefix 时间戳] message
43
+ *
44
+ * @param {string} prefix - 日志前缀 (默认: "mockbubu")
45
+ * @returns {Object} 日志工具对象,包含 log, warn, error, info 方法
46
+ *
47
+ * @example
48
+ * const logger = createLogger('storage')
49
+ * logger.log('文件创建成功') // 输出: [storage 2025/12/02 15:30:45] 文件创建成功
50
+ * logger.error('文件创建失败:', error) // 输出: [storage 2025/12/02 15:30:45] 文件创建失败: Error: ...
51
+ */
52
+ function createLogger(prefix = 'mockbubu') {
53
+ return {
54
+ /**
55
+ * 普通日志
56
+ */
57
+ log: (...args) => console.log(`[${prefix} ${formatTimestamp()}]`, ...args),
58
+
59
+ /**
60
+ * 警告日志
61
+ */
62
+ warn: (...args) => console.warn(`[${prefix} ${formatTimestamp()}]`, ...args),
63
+
64
+ /**
65
+ * 错误日志
66
+ */
67
+ error: (...args) => console.error(`[${prefix} ${formatTimestamp()}]`, ...args),
68
+
69
+ /**
70
+ * 信息日志
71
+ */
72
+ info: (...args) => console.info(`[${prefix} ${formatTimestamp()}]`, ...args),
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ formatTimestamp,
78
+ createLogger,
79
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * 文件名: path-utils.js
3
+ * 功能: 路径解析工具,处理 groupId/filename 格式的路径字符串
4
+ * 依赖: 无
5
+ * 更新: 2025-12-02
6
+ *
7
+ * 使用示例:
8
+ * ```javascript
9
+ * const { parsePath, parseConfigKey } = require('./path-utils')
10
+ *
11
+ * const { groupId, filename } = parsePath('default/https://api.test.com/users')
12
+ * console.log(groupId) // 'default'
13
+ * console.log(filename) // 'https://api.test.com/users'
14
+ * ```
15
+ *
16
+ * 注意事项:
17
+ * - 支持文件名中包含斜杠 (URL路径)
18
+ * - 配置键格式必须严格为 "group.{groupId}.file.{filename}"
19
+ */
20
+
21
+ /**
22
+ * 解析完整路径为组ID和文件名
23
+ *
24
+ * 路径格式: "groupId/filename" 或 "filename"
25
+ * 当路径不包含斜杠时,使用默认组ID
26
+ *
27
+ * @param {string} pathStr - 完整路径字符串
28
+ * @param {string} defaultGroupId - 默认组ID (当路径不包含组时使用)
29
+ * @returns {{groupId: string, filename: string}} 解析结果
30
+ * @throws {Error} 路径为空时抛出异常
31
+ *
32
+ * @example
33
+ * parsePath('default/https://api.test.com/users')
34
+ * // => { groupId: 'default', filename: 'https://api.test.com/users' }
35
+ *
36
+ * parsePath('https://api.test.com/users', 'default')
37
+ * // => { groupId: 'default', filename: 'https://api.test.com/users' }
38
+ *
39
+ * parsePath('group_123/https://api.test.com/path/to/resource')
40
+ * // => { groupId: 'group_123', filename: 'https://api.test.com/path/to/resource' }
41
+ */
42
+ function parsePath(pathStr, defaultGroupId = 'default') {
43
+ if (!pathStr) {
44
+ throw new Error('路径不能为空')
45
+ }
46
+
47
+ // 查找第一个斜杠的位置
48
+ const firstSlashIndex = pathStr.indexOf('/')
49
+
50
+ // 如果没有斜杠,整个字符串就是文件名
51
+ if (firstSlashIndex === -1) {
52
+ return {
53
+ groupId: defaultGroupId,
54
+ filename: pathStr,
55
+ }
56
+ }
57
+
58
+ // 提取组ID (第一个斜杠之前的部分)
59
+ const groupId = pathStr.substring(0, firstSlashIndex)
60
+
61
+ // 提取文件名 (第一个斜杠之后的所有内容,支持文件名中包含斜杠)
62
+ const filename = pathStr.substring(firstSlashIndex + 1)
63
+
64
+ return { groupId, filename }
65
+ }
66
+
67
+ /**
68
+ * 解析配置键为组ID和文件名
69
+ *
70
+ * 配置键格式: "group.{groupId}.file.{filename}"
71
+ * 这是旧架构在 properties 文件中存储文件配置的键格式
72
+ *
73
+ * @param {string} key - 配置键字符串
74
+ * @returns {{groupId: string, filename: string}} 解析结果
75
+ * @throws {Error} 配置键格式不正确时抛出异常
76
+ *
77
+ * @example
78
+ * parseConfigKey('group.default.file.https://api.test.com/users')
79
+ * // => { groupId: 'default', filename: 'https://api.test.com/users' }
80
+ *
81
+ * parseConfigKey('group.group_123.file.https://api.test.com/users')
82
+ * // => { groupId: 'group_123', filename: 'https://api.test.com/users' }
83
+ *
84
+ * parseConfigKey('invalid-key')
85
+ * // => 抛出 Error: 配置键格式不正确
86
+ */
87
+ function parseConfigKey(key) {
88
+ if (!key || typeof key !== 'string') {
89
+ throw new Error('配置键不能为空')
90
+ }
91
+
92
+ // 按 ".file." 分割,应该得到2部分
93
+ const parts = key.split('.file.')
94
+ if (parts.length !== 2) {
95
+ throw new Error(`配置键格式不正确: ${key}, 应为 "group.{groupId}.file.{filename}"`)
96
+ }
97
+
98
+ // 从第一部分提取组ID (去除 "group." 前缀)
99
+ const groupId = parts[0].replace('group.', '')
100
+
101
+ // 第二部分就是文件名
102
+ const filename = parts[1]
103
+
104
+ // 验证解析结果
105
+ if (!groupId || !filename) {
106
+ throw new Error(`配置键解析失败: groupId="${groupId}", filename="${filename}"`)
107
+ }
108
+
109
+ return { groupId, filename }
110
+ }
111
+
112
+ /**
113
+ * 构建完整路径
114
+ *
115
+ * @param {string} groupId - 组ID
116
+ * @param {string} filename - 文件名
117
+ * @returns {string} 格式: "groupId/filename"
118
+ *
119
+ * @example
120
+ * buildPath('default', 'https://api.test.com/users')
121
+ * // => 'default/https://api.test.com/users'
122
+ */
123
+ function buildPath(groupId, filename) {
124
+ return `${groupId}/${filename}`
125
+ }
126
+
127
+ /**
128
+ * 构建配置键
129
+ *
130
+ * @param {string} groupId - 组ID
131
+ * @param {string} filename - 文件名
132
+ * @returns {string} 格式: "group.{groupId}.file.{filename}"
133
+ *
134
+ * @example
135
+ * buildConfigKey('default', 'https://api.test.com/users')
136
+ * // => 'group.default.file.https://api.test.com/users'
137
+ */
138
+ function buildConfigKey(groupId, filename) {
139
+ return `group.${groupId}.file.${filename}`
140
+ }
141
+
142
+ module.exports = {
143
+ parsePath,
144
+ parseConfigKey,
145
+ buildPath,
146
+ buildConfigKey,
147
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * 文件名: performance.js
3
+ * 功能: 性能监控工具,用于测量函数执行时间
4
+ * 依赖: logger.js
5
+ * 更新: 2025-12-02
6
+ *
7
+ * 使用示例:
8
+ * ```javascript
9
+ * const { measureTime, createTimer } = require('./performance')
10
+ *
11
+ * // 方式1: 包装函数
12
+ * const measuredFn = measureTime(myFunc, 'MyOperation')
13
+ * await measuredFn(arg1, arg2)
14
+ *
15
+ * // 方式2: 手动计时
16
+ * const timer = createTimer('ExportOperation')
17
+ * // ... 执行操作
18
+ * timer.end() // 输出耗时
19
+ * ```
20
+ *
21
+ * 注意事项:
22
+ * - 耗时超过1秒时会输出警告日志
23
+ * - 开发环境下会输出所有计时日志
24
+ */
25
+
26
+ const { createLogger } = require('./logger')
27
+
28
+ /**
29
+ * 包装函数,自动记录执行时间
30
+ *
31
+ * 适用于需要监控性能的函数
32
+ * - 开发环境: 所有调用都输出日志
33
+ * - 生产环境: 仅耗时超过1秒时输出
34
+ *
35
+ * @param {Function} fn - 要监控的函数
36
+ * @param {string} operationName - 操作名称
37
+ * @param {Object} options - 配置选项
38
+ * @param {number} options.threshold - 耗时阈值(ms),超过此值才输出日志 (默认: 1000)
39
+ * @param {boolean} options.alwaysLog - 是否总是输出日志 (默认: false)
40
+ * @returns {Function} 包装后的函数
41
+ *
42
+ * @example
43
+ * const measuredExport = measureTime(exportGroup, '导出组')
44
+ * await measuredExport(groupId)
45
+ * // 如果耗时超过1秒,输出: [mockbubu 2025/12/02 15:30:45] 导出组 耗时: 1234ms
46
+ *
47
+ * @example
48
+ * // 总是输出日志
49
+ * const measuredQuery = measureTime(
50
+ * queryFiles,
51
+ * '查询文件',
52
+ * { alwaysLog: true }
53
+ * )
54
+ */
55
+ function measureTime(fn, operationName, options = {}) {
56
+ const {
57
+ threshold = 1000,
58
+ alwaysLog = false,
59
+ } = options
60
+
61
+ return async function (...args) {
62
+ const logger = createLogger()
63
+ const startTime = Date.now()
64
+
65
+ try {
66
+ const result = await fn.apply(this, args)
67
+ const duration = Date.now() - startTime
68
+
69
+ // 判断是否需要输出日志
70
+ const shouldLog = (
71
+ alwaysLog ||
72
+ process.env.NODE_ENV === 'development' ||
73
+ duration > threshold
74
+ )
75
+
76
+ if (shouldLog) {
77
+ if (duration > threshold) {
78
+ logger.warn(`${operationName} 耗时: ${duration}ms (超过阈值 ${threshold}ms)`)
79
+ } else {
80
+ logger.info(`${operationName} 耗时: ${duration}ms`)
81
+ }
82
+ }
83
+
84
+ return result
85
+ } catch (err) {
86
+ const duration = Date.now() - startTime
87
+ logger.error(`${operationName} 失败 (耗时: ${duration}ms):`, err.message)
88
+ throw err
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * 创建性能计时器
95
+ *
96
+ * 适用于需要手动控制计时开始和结束的场景
97
+ *
98
+ * @param {string} label - 计时器标签
99
+ * @param {Object} options - 配置选项
100
+ * @param {number} options.threshold - 耗时阈值(ms),超过此值输出警告 (默认: 1000)
101
+ * @returns {Object} 计时器对象
102
+ * @property {Function} end - 结束计时并输出日志
103
+ * @property {Function} getDuration - 获取当前耗时(不结束计时)
104
+ * @property {Function} checkpoint - 记录检查点
105
+ *
106
+ * @example
107
+ * const timer = createTimer('导出操作')
108
+ * // ... 步骤1
109
+ * timer.checkpoint('文件复制')
110
+ * // ... 步骤2
111
+ * timer.checkpoint('压缩打包')
112
+ * // ... 步骤3
113
+ * timer.end() // 输出总耗时和各检查点耗时
114
+ *
115
+ * @example
116
+ * const timer = createTimer('复杂操作', { threshold: 2000 })
117
+ * // ... 执行操作
118
+ * const duration = timer.getDuration() // 获取当前耗时(不结束)
119
+ * // ... 继续操作
120
+ * timer.end() // 结束并输出日志
121
+ */
122
+ function createTimer(label, options = {}) {
123
+ const {
124
+ threshold = 1000,
125
+ } = options
126
+
127
+ const logger = createLogger()
128
+ const startTime = Date.now()
129
+ const checkpoints = []
130
+
131
+ return {
132
+ /**
133
+ * 记录检查点
134
+ * @param {string} checkpointLabel - 检查点标签
135
+ */
136
+ checkpoint(checkpointLabel) {
137
+ const duration = Date.now() - startTime
138
+ checkpoints.push({
139
+ label: checkpointLabel,
140
+ time: duration,
141
+ })
142
+ logger.info(` ✓ ${checkpointLabel} (${duration}ms)`)
143
+ },
144
+
145
+ /**
146
+ * 结束计时并输出日志
147
+ * @returns {number} 总耗时(ms)
148
+ */
149
+ end() {
150
+ const duration = Date.now() - startTime
151
+
152
+ // 输出总耗时
153
+ if (duration > threshold) {
154
+ logger.warn(`${label} 总耗时: ${duration}ms (超过阈值 ${threshold}ms)`)
155
+ } else {
156
+ logger.info(`${label} 总耗时: ${duration}ms`)
157
+ }
158
+
159
+ // 如果有检查点,输出详细信息
160
+ if (checkpoints.length > 0) {
161
+ logger.info(' 检查点明细:')
162
+ checkpoints.forEach((cp, index) => {
163
+ const prevTime = index > 0 ? checkpoints[index - 1].time : 0
164
+ const stepDuration = cp.time - prevTime
165
+ logger.info(` ${index + 1}. ${cp.label}: ${stepDuration}ms`)
166
+ })
167
+ }
168
+
169
+ return duration
170
+ },
171
+
172
+ /**
173
+ * 获取当前耗时(不结束计时)
174
+ * @returns {number} 当前耗时(ms)
175
+ */
176
+ getDuration() {
177
+ return Date.now() - startTime
178
+ },
179
+ }
180
+ }
181
+
182
+ /**
183
+ * 创建性能分析器 (用于分析代码瓶颈)
184
+ *
185
+ * @param {string} label - 分析器标签
186
+ * @returns {Object} 分析器对象
187
+ *
188
+ * @example
189
+ * const profiler = createProfiler('API查询')
190
+ *
191
+ * profiler.start('数据库查询')
192
+ * // ... 数据库操作
193
+ * profiler.end()
194
+ *
195
+ * profiler.start('数据处理')
196
+ * // ... 数据处理
197
+ * profiler.end()
198
+ *
199
+ * profiler.report() // 输出性能报告
200
+ */
201
+ function createProfiler(label) {
202
+ const logger = createLogger()
203
+ const records = []
204
+ let currentRecord = null
205
+
206
+ return {
207
+ /**
208
+ * 开始记录
209
+ */
210
+ start(operationName) {
211
+ if (currentRecord) {
212
+ logger.warn(`前一个操作 "${currentRecord.name}" 未结束`)
213
+ }
214
+ currentRecord = {
215
+ name: operationName,
216
+ startTime: Date.now(),
217
+ }
218
+ },
219
+
220
+ /**
221
+ * 结束记录
222
+ */
223
+ end() {
224
+ if (!currentRecord) {
225
+ logger.warn('没有正在进行的操作')
226
+ return
227
+ }
228
+ currentRecord.duration = Date.now() - currentRecord.startTime
229
+ records.push(currentRecord)
230
+ currentRecord = null
231
+ },
232
+
233
+ /**
234
+ * 输出性能报告
235
+ */
236
+ report() {
237
+ if (records.length === 0) {
238
+ logger.info(`${label}: 无性能数据`)
239
+ return
240
+ }
241
+
242
+ const totalDuration = records.reduce((sum, r) => sum + r.duration, 0)
243
+ logger.info(`${label} 性能报告 (总耗时: ${totalDuration}ms):`)
244
+
245
+ records.forEach((record, index) => {
246
+ const percentage = ((record.duration / totalDuration) * 100).toFixed(1)
247
+ logger.info(` ${index + 1}. ${record.name}: ${record.duration}ms (${percentage}%)`)
248
+ })
249
+ },
250
+
251
+ /**
252
+ * 重置分析器
253
+ */
254
+ reset() {
255
+ records.length = 0
256
+ currentRecord = null
257
+ },
258
+ }
259
+ }
260
+
261
+ module.exports = {
262
+ measureTime,
263
+ createTimer,
264
+ createProfiler,
265
+ }