whistle.mockbubu 1.0.0-dev.4 → 2.0.0-beta.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.
@@ -6,27 +6,98 @@ const path = require('path')
6
6
  const router = require('koa-router')()
7
7
  const setupRouter = require('./router')
8
8
  const cors = require('koa2-cors')
9
+ const GroupManager = require('../group-manager')
10
+ const StorageAdapter = require('../storage-adapter')
9
11
  const MAX_AGE = 1000 * 60 * 5
10
12
 
11
13
  module.exports = (server, options) => {
12
14
  const app = new Koa()
15
+ const { storage } = options
16
+
17
+ console.log('[mockbubu] 插件开始初始化...')
18
+
19
+ // 使用 StorageAdapter 包装 Whistle storage
20
+ // Whistle storage 对象直接有 baseDir 属性
21
+ const actualBaseDir = storage.baseDir || storage._options?.baseDir
22
+ console.log('[mockbubu] 🔍 UI Server storage.baseDir:', storage.baseDir)
23
+ console.log('[mockbubu] 🔍 UI Server 使用 baseDir:', actualBaseDir)
24
+ const storageAdapter = new StorageAdapter({ baseDir: actualBaseDir })
25
+
26
+ // 同步初始化:确保初始化完成后再处理请求
27
+ let isInitialized = false
28
+ let groupManager
29
+
30
+ ;(async () => {
31
+ try {
32
+ // 等待 storage 初始化完成
33
+ await storageAdapter.init()
34
+ console.log('[mockbubu] ✅ V3 Storage (UI Server) 已初始化')
35
+
36
+ // 创建组管理器
37
+ console.log('[mockbubu] 创建 GroupManager...')
38
+ groupManager = new GroupManager(storageAdapter)
39
+ console.log('[mockbubu] GroupManager 创建成功')
40
+
41
+ // 确保默认组存在
42
+ console.log('[mockbubu] 确保默认组存在...')
43
+ await groupManager.ensureDefaultGroup()
44
+ console.log('[mockbubu] 默认组已准备就绪')
45
+
46
+
47
+ isInitialized = true
48
+ console.log('[mockbubu] 插件初始化完成')
49
+ } catch (error) {
50
+ console.error('[mockbubu] 初始化失败:', error)
51
+ console.error('[mockbubu] 错误堆栈:', error.stack)
52
+ // 如果初始化失败,创建一个简单的 mock 对象
53
+ groupManager = {
54
+ getCurrentGroupId: async () => 'default',
55
+ getCurrentGroup: async () => ({ id: 'default', name: '默认组' }),
56
+ getGroups: async () => [{ id: 'default', name: '默认组' }],
57
+ }
58
+ isInitialized = true
59
+ }
60
+ })()
61
+
62
+ // 中间件:等待初始化完成
63
+ app.use(async (ctx, next) => {
64
+ // eslint-disable-next-line no-unmodified-loop-condition
65
+ while (!isInitialized) {
66
+ await new Promise(resolve => setTimeout(resolve, 100))
67
+ }
68
+ // 将 groupManager 和 storageAdapter 挂载到 ctx,让所有路由都能访问
69
+ ctx.groupManager = groupManager
70
+ ctx.storageAdapter = storageAdapter
71
+ await next()
72
+ })
13
73
 
14
74
  app.proxy = true
15
75
  app.silent = true
16
76
  onerror(app)
17
- setupRouter(router, options)
77
+ setupRouter(router, { ...options, storage: storageAdapter })
18
78
  app.use(
19
79
  cors({
20
- origin: '*',
80
+ origin: (ctx) => {
81
+ const origin = ctx.get('Origin')
82
+ // 只允许 localhost 和 127.0.0.1 的请求
83
+ if (!origin || /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) {
84
+ return origin || '*'
85
+ }
86
+ // Whistle UI 的域名
87
+ if (/^https?:\/\/local\.whistlejs\.com(:\d+)?$/i.test(origin)) {
88
+ return origin
89
+ }
90
+ return false
91
+ },
21
92
  maxAge: 10,
22
93
  credentials: true,
23
94
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
24
95
  }),
25
96
  )
26
97
  app.use(bodyParser({
27
- jsonLimit: '10mb',
28
- formLimit: '10mb',
29
- textLimit: '10mb',
98
+ jsonLimit: '100mb',
99
+ formLimit: '100mb',
100
+ textLimit: '100mb',
30
101
  }))
31
102
  app.use(router.routes())
32
103
  app.use(router.allowedMethods())
@@ -0,0 +1,218 @@
1
+ const {
2
+ createErrorResponse,
3
+ createSuccessResponse,
4
+ } = require('../util')
5
+ const {
6
+ validate,
7
+ } = require('../validator')
8
+
9
+ /**
10
+ * 组管理路由
11
+ */
12
+ module.exports = (router) => {
13
+ // 获取组列表
14
+ router.post('/cgi-bin/mockbubu/groups/list', async (ctx) => {
15
+ try {
16
+ const { groupManager } = ctx
17
+ const groups = await groupManager.getGroups()
18
+ const currentGroupId = await groupManager.getCurrentGroupId()
19
+
20
+ ctx.body = createSuccessResponse({
21
+ groups,
22
+ currentGroupId,
23
+ })
24
+ } catch (error) {
25
+ ctx.body = createErrorResponse(error.message)
26
+ }
27
+ })
28
+
29
+ // 获取当前组
30
+ router.post('/cgi-bin/mockbubu/groups/current', async (ctx) => {
31
+ try {
32
+ const { groupManager } = ctx
33
+ const currentGroup = await groupManager.getCurrentGroup()
34
+
35
+ ctx.body = createSuccessResponse({
36
+ currentGroup,
37
+ currentGroupId: await groupManager.getCurrentGroupId(),
38
+ })
39
+ } catch (error) {
40
+ ctx.body = createErrorResponse(error.message)
41
+ }
42
+ })
43
+
44
+ // 创建组
45
+ router.post('/cgi-bin/mockbubu/groups/create', validate({
46
+ name: (val) => {
47
+ if (!val || !val.trim()) {
48
+ throw new Error('组名不能为空')
49
+ }
50
+ if (val.trim().length > 50) {
51
+ throw new Error('组名长度不能超过50个字符')
52
+ }
53
+ return null
54
+ },
55
+ description: (val) => {
56
+ if (val && val.length > 200) {
57
+ throw new Error('描述长度不能超过200个字符')
58
+ }
59
+ return null
60
+ },
61
+ copyFromGroupId: (val) => {
62
+ // 可选参数,如果提供则必须是字符串
63
+ if (val !== undefined && val !== null && typeof val !== 'string') {
64
+ throw new Error('copyFromGroupId 必须是字符串')
65
+ }
66
+ return null
67
+ },
68
+ }), async (ctx) => {
69
+ try {
70
+ const { groupManager } = ctx
71
+ const { name, description, copyFromGroupId } = ctx.request.body
72
+
73
+ const newGroup = await groupManager.createGroup({
74
+ name: name.trim(),
75
+ description: description ? description.trim() : '',
76
+ copyFromGroupId: copyFromGroupId || null,
77
+ })
78
+
79
+ ctx.body = createSuccessResponse(newGroup, '创建成功')
80
+ } catch (error) {
81
+ ctx.body = createErrorResponse(error.message)
82
+ }
83
+ })
84
+
85
+ // 更新组
86
+ router.post('/cgi-bin/mockbubu/groups/update', validate({
87
+ groupId: (val) => {
88
+ if (!val || typeof val !== 'string') {
89
+ throw new Error('groupId 必须是字符串')
90
+ }
91
+ return null
92
+ },
93
+ name: (val) => {
94
+ if (val !== undefined && val !== null) {
95
+ if (!val.trim()) {
96
+ throw new Error('组名不能为空')
97
+ }
98
+ if (val.trim().length > 50) {
99
+ throw new Error('组名长度不能超过50个字符')
100
+ }
101
+ }
102
+ return null
103
+ },
104
+ description: (val) => {
105
+ if (val !== undefined && val !== null && val.length > 200) {
106
+ throw new Error('描述长度不能超过200个字符')
107
+ }
108
+ return null
109
+ },
110
+ }), async (ctx) => {
111
+ try {
112
+ const { groupManager } = ctx
113
+ const { groupId, name, description } = ctx.request.body
114
+
115
+ const updatedGroup = await groupManager.updateGroup(groupId, {
116
+ name: name ? name.trim() : undefined,
117
+ description: description !== undefined ? description.trim() : undefined,
118
+ })
119
+
120
+ ctx.body = createSuccessResponse(updatedGroup, '更新成功')
121
+ } catch (error) {
122
+ ctx.body = createErrorResponse(error.message)
123
+ }
124
+ })
125
+
126
+ // 删除组
127
+ router.post('/cgi-bin/mockbubu/groups/delete', validate({
128
+ groupId: (val) => {
129
+ if (!val || typeof val !== 'string') {
130
+ throw new Error('groupId 必须是字符串')
131
+ }
132
+ return null
133
+ },
134
+ }), async (ctx) => {
135
+ try {
136
+ const { groupManager } = ctx
137
+ const { groupId } = ctx.request.body
138
+
139
+ await groupManager.deleteGroup(groupId)
140
+
141
+ ctx.body = createSuccessResponse(null, '删除成功')
142
+ } catch (error) {
143
+ ctx.body = createErrorResponse(error.message)
144
+ }
145
+ })
146
+
147
+ // 切换当前组
148
+ router.post('/cgi-bin/mockbubu/groups/switch', validate({
149
+ groupId: (val) => {
150
+ if (!val || typeof val !== 'string') {
151
+ throw new Error('groupId 必须是字符串')
152
+ }
153
+ return null
154
+ },
155
+ }), async (ctx) => {
156
+ try {
157
+ const { groupManager } = ctx
158
+ const { groupId } = ctx.request.body
159
+
160
+ const currentGroup = await groupManager.setCurrentGroup(groupId)
161
+
162
+ ctx.body = createSuccessResponse({
163
+ currentGroup,
164
+ currentGroupId: groupId,
165
+ }, '切换成功')
166
+ } catch (error) {
167
+ ctx.body = createErrorResponse(error.message)
168
+ }
169
+ })
170
+
171
+ // 复制组
172
+ router.post('/cgi-bin/mockbubu/groups/copy', validate({
173
+ sourceGroupId: (val) => {
174
+ if (!val || typeof val !== 'string') {
175
+ throw new Error('sourceGroupId 必须是字符串')
176
+ }
177
+ return null
178
+ },
179
+ name: (val) => {
180
+ if (!val || !val.trim()) {
181
+ throw new Error('组名不能为空')
182
+ }
183
+ if (val.trim().length > 50) {
184
+ throw new Error('组名长度不能超过50个字符')
185
+ }
186
+ return null
187
+ },
188
+ description: (val) => {
189
+ if (val && val.length > 200) {
190
+ throw new Error('描述长度不能超过200个字符')
191
+ }
192
+ return null
193
+ },
194
+ }), async (ctx) => {
195
+ try {
196
+ const { groupManager } = ctx
197
+ const { sourceGroupId, name, description } = ctx.request.body
198
+
199
+ // 检查源组是否存在
200
+ const groups = await groupManager.getGroups()
201
+ const sourceGroup = groups.find(g => g.id === sourceGroupId)
202
+ if (!sourceGroup) {
203
+ throw new Error(`源组不存在: ${sourceGroupId}`)
204
+ }
205
+
206
+ // 创建新组并复制配置
207
+ const newGroup = await groupManager.createGroup({
208
+ name: name.trim(),
209
+ description: description ? description.trim() : '',
210
+ copyFromGroupId: sourceGroupId,
211
+ })
212
+
213
+ ctx.body = createSuccessResponse(newGroup, '复制成功')
214
+ } catch (error) {
215
+ ctx.body = createErrorResponse(error.message)
216
+ }
217
+ })
218
+ }