xinyu-pro 0.21.4 → 0.22.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.
package/.env.example CHANGED
@@ -13,9 +13,5 @@ GITHUB_TOKEN=ghp_your-token-here
13
13
  # GitHub 用户名
14
14
  GITHUB_USERNAME=your-username
15
15
 
16
- # 数据库配置
17
- DB_HOST=localhost
18
- DB_PORT=3306
19
- DB_USER=root
20
- DB_PASSWORD=你的密码
21
- DB_NAME=xinyu
16
+ # 数据库文件路径(可选,默认 data/xinyu.db)
17
+ DB_PATH=data/xinyu.db
@@ -1,173 +1,169 @@
1
- // app/api/plugins/bindings/route.ts - 插件绑定 API
2
-
3
- import { NextRequest, NextResponse } from 'next/server';
4
- import { getDbPool } from '@/lib/db';
5
- import { ensureDb } from '@/lib/db-init';
6
-
7
- /** 解析 JSON 字段 */
8
- function parseJsonField<T>(value: unknown): T {
9
- if (typeof value === 'string') {
10
- try { return JSON.parse(value) as T; } catch { return value as unknown as T; }
11
- }
12
- return value as T;
13
- }
14
-
15
- /** 行数据转换为绑定对象 */
16
- function rowToBinding(row: Record<string, unknown>) {
17
- return {
18
- id: row.id,
19
- extensionId: row.extension_id,
20
- scope: row.scope,
21
- worldId: row.world_id,
22
- enabled: Boolean(row.enabled),
23
- config: parseJsonField<Record<string, unknown>>(row.config),
24
- sortOrder: row.sort_order,
25
- createdAt: row.created_at,
26
- updatedAt: row.updated_at,
27
- };
28
- }
29
-
30
- // GET: 获取绑定列表
31
- export async function GET(request: NextRequest) {
32
- try {
33
- await ensureDb();
34
- const db = getDbPool();
35
- const { searchParams } = new URL(request.url);
36
- const id = searchParams.get('id');
37
- const scope = searchParams.get('scope');
38
- const worldId = searchParams.get('worldId');
39
-
40
- if (id) {
41
- const [rows] = await db.execute(
42
- 'SELECT * FROM extension_bindings WHERE id = ?',
43
- [id]
44
- ) as [Record<string, unknown>[], unknown];
45
- if (rows.length === 0) {
46
- return NextResponse.json({ error: '绑定不存在' }, { status: 404 });
47
- }
48
- return NextResponse.json(rowToBinding(rows[0]));
49
- }
50
-
51
- let query = 'SELECT * FROM extension_bindings WHERE 1=1';
52
- const params: (string | null)[] = [];
53
-
54
- if (scope) {
55
- query += ' AND scope = ?';
56
- params.push(scope);
57
- }
58
- if (worldId) {
59
- query += ' AND world_id = ?';
60
- params.push(worldId);
61
- }
62
-
63
- query += ' ORDER BY sort_order ASC, id ASC';
64
- const [rows] = await db.execute(query, params) as [Record<string, unknown>[], unknown];
65
- return NextResponse.json(rows.map(rowToBinding));
66
- } catch (e) {
67
- console.error('获取插件绑定失败:', e);
68
- return NextResponse.json({ error: '获取插件绑定失败' }, { status: 500 });
69
- }
70
- }
71
-
72
- // POST: 创建或更新绑定(upsert)
73
- export async function POST(request: NextRequest) {
74
- try {
75
- await ensureDb();
76
- const db = getDbPool();
77
- const body = await request.json();
78
- const { extensionId, scope, worldId, enabled, config, sortOrder } = body;
79
-
80
- if (!extensionId) {
81
- return NextResponse.json({ error: '缺少插件 ID' }, { status: 400 });
82
- }
83
-
84
- const bindingScope = scope ?? 'global';
85
- const bindingWorldId = worldId ?? '';
86
- const bindingEnabled = enabled !== undefined ? enabled : true;
87
- const bindingConfig = config ?? {};
88
- const bindingSortOrder = sortOrder ?? 0;
89
-
90
- await db.execute(
91
- `INSERT INTO extension_bindings (extension_id, scope, world_id, enabled, config, sort_order)
92
- VALUES (?, ?, ?, ?, ?, ?)
93
- ON DUPLICATE KEY UPDATE
94
- enabled = VALUES(enabled), config = VALUES(config), sort_order = VALUES(sort_order)`,
95
- [extensionId, bindingScope, bindingWorldId, bindingEnabled, JSON.stringify(bindingConfig), bindingSortOrder]
96
- );
97
-
98
- return NextResponse.json({ success: true, extensionId });
99
- } catch (e) {
100
- console.error('创建插件绑定失败:', e);
101
- return NextResponse.json({ error: '创建插件绑定失败' }, { status: 500 });
102
- }
103
- }
104
-
105
- // PUT: 更新绑定
106
- export async function PUT(request: NextRequest) {
107
- try {
108
- await ensureDb();
109
- const db = getDbPool();
110
- const body = await request.json();
111
- const { id, ...fields } = body;
112
-
113
- if (!id) {
114
- return NextResponse.json({ error: '缺少绑定 ID' }, { status: 400 });
115
- }
116
-
117
- const updates: string[] = [];
118
- const params: (string | number | boolean | null)[] = [];
119
-
120
- const allowedFields = ['enabled', 'config', 'sortOrder', 'scope', 'worldId'];
121
- const columnMap: Record<string, string> = {
122
- sortOrder: 'sort_order',
123
- worldId: 'world_id',
124
- };
125
-
126
- for (const field of allowedFields) {
127
- if (fields[field] !== undefined) {
128
- const col = columnMap[field] || field;
129
- updates.push(`${col} = ?`);
130
- if (field === 'config') {
131
- params.push(JSON.stringify(fields[field]));
132
- } else {
133
- params.push(fields[field]);
134
- }
135
- }
136
- }
137
-
138
- if (updates.length === 0) {
139
- return NextResponse.json({ error: '没有需要更新的字段' }, { status: 400 });
140
- }
141
-
142
- params.push(id);
143
- await db.execute(
144
- `UPDATE extension_bindings SET ${updates.join(', ')} WHERE id = ?`,
145
- params
146
- );
147
-
148
- return NextResponse.json({ success: true, id });
149
- } catch (e) {
150
- console.error('更新插件绑定失败:', e);
151
- return NextResponse.json({ error: '更新插件绑定失败' }, { status: 500 });
152
- }
153
- }
154
-
155
- // DELETE: 删除绑定
156
- export async function DELETE(request: NextRequest) {
157
- try {
158
- await ensureDb();
159
- const db = getDbPool();
160
- const { searchParams } = new URL(request.url);
161
- const id = searchParams.get('id');
162
-
163
- if (!id) {
164
- return NextResponse.json({ error: '缺少绑定 ID' }, { status: 400 });
165
- }
166
-
167
- await db.execute('DELETE FROM extension_bindings WHERE id = ?', [id]);
168
- return NextResponse.json({ success: true, id });
169
- } catch (e) {
170
- console.error('删除插件绑定失败:', e);
171
- return NextResponse.json({ error: '删除插件绑定失败' }, { status: 500 });
172
- }
173
- }
1
+ // app/api/plugins/bindings/route.ts - 插件绑定 API
2
+
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+ import { execute } from '@/lib/db';
5
+ import { ensureDb } from '@/lib/db-init';
6
+
7
+ /** 解析 JSON 字段 */
8
+ function parseJsonField<T>(value: unknown): T {
9
+ if (typeof value === 'string') {
10
+ try { return JSON.parse(value) as T; } catch { return value as unknown as T; }
11
+ }
12
+ return value as T;
13
+ }
14
+
15
+ /** 行数据转换为绑定对象 */
16
+ function rowToBinding(row: Record<string, unknown>) {
17
+ return {
18
+ id: row.id,
19
+ extensionId: row.extension_id,
20
+ scope: row.scope,
21
+ worldId: row.world_id,
22
+ enabled: Boolean(row.enabled),
23
+ config: parseJsonField<Record<string, unknown>>(row.config),
24
+ sortOrder: row.sort_order,
25
+ createdAt: row.created_at,
26
+ updatedAt: row.updated_at,
27
+ };
28
+ }
29
+
30
+ // GET: 获取绑定列表
31
+ export async function GET(request: NextRequest) {
32
+ try {
33
+ await ensureDb();
34
+ const { searchParams } = new URL(request.url);
35
+ const id = searchParams.get('id');
36
+ const scope = searchParams.get('scope');
37
+ const worldId = searchParams.get('worldId');
38
+
39
+ if (id) {
40
+ const [rows] = await execute(
41
+ 'SELECT * FROM extension_bindings WHERE id = ?',
42
+ [id]
43
+ );
44
+ if ((rows as Record<string, unknown>[]).length === 0) {
45
+ return NextResponse.json({ error: '绑定不存在' }, { status: 404 });
46
+ }
47
+ return NextResponse.json(rowToBinding((rows as Record<string, unknown>[])[0]));
48
+ }
49
+
50
+ let query = 'SELECT * FROM extension_bindings WHERE 1=1';
51
+ const params: (string | null)[] = [];
52
+
53
+ if (scope) {
54
+ query += ' AND scope = ?';
55
+ params.push(scope);
56
+ }
57
+ if (worldId) {
58
+ query += ' AND world_id = ?';
59
+ params.push(worldId);
60
+ }
61
+
62
+ query += ' ORDER BY sort_order ASC, id ASC';
63
+ const [rows] = await execute(query, params);
64
+ return NextResponse.json((rows as Record<string, unknown>[]).map(rowToBinding));
65
+ } catch (e) {
66
+ console.error('获取插件绑定失败:', e);
67
+ return NextResponse.json({ error: '获取插件绑定失败' }, { status: 500 });
68
+ }
69
+ }
70
+
71
+ // POST: 创建或更新绑定(upsert)
72
+ export async function POST(request: NextRequest) {
73
+ try {
74
+ await ensureDb();
75
+ const body = await request.json();
76
+ const { extensionId, scope, worldId, enabled, config, sortOrder } = body;
77
+
78
+ if (!extensionId) {
79
+ return NextResponse.json({ error: '缺少插件 ID' }, { status: 400 });
80
+ }
81
+
82
+ const bindingScope = scope ?? 'global';
83
+ const bindingWorldId = worldId ?? '';
84
+ const bindingEnabled = enabled !== undefined ? enabled : true;
85
+ const bindingConfig = config ?? {};
86
+ const bindingSortOrder = sortOrder ?? 0;
87
+
88
+ await execute(
89
+ `INSERT INTO extension_bindings (extension_id, scope, world_id, enabled, config, sort_order)
90
+ VALUES (?, ?, ?, ?, ?, ?)
91
+ ON CONFLICT(extension_id, scope, world_id) DO UPDATE SET
92
+ enabled = excluded.enabled, config = excluded.config, sort_order = excluded.sort_order`,
93
+ [extensionId, bindingScope, bindingWorldId, bindingEnabled, JSON.stringify(bindingConfig), bindingSortOrder]
94
+ );
95
+
96
+ return NextResponse.json({ success: true, extensionId });
97
+ } catch (e) {
98
+ console.error('创建插件绑定失败:', e);
99
+ return NextResponse.json({ error: '创建插件绑定失败' }, { status: 500 });
100
+ }
101
+ }
102
+
103
+ // PUT: 更新绑定
104
+ export async function PUT(request: NextRequest) {
105
+ try {
106
+ await ensureDb();
107
+ const body = await request.json();
108
+ const { id, ...fields } = body;
109
+
110
+ if (!id) {
111
+ return NextResponse.json({ error: '缺少绑定 ID' }, { status: 400 });
112
+ }
113
+
114
+ const updates: string[] = [];
115
+ const params: (string | number | boolean | null)[] = [];
116
+
117
+ const allowedFields = ['enabled', 'config', 'sortOrder', 'scope', 'worldId'];
118
+ const columnMap: Record<string, string> = {
119
+ sortOrder: 'sort_order',
120
+ worldId: 'world_id',
121
+ };
122
+
123
+ for (const field of allowedFields) {
124
+ if (fields[field] !== undefined) {
125
+ const col = columnMap[field] || field;
126
+ updates.push(`${col} = ?`);
127
+ if (field === 'config') {
128
+ params.push(JSON.stringify(fields[field]));
129
+ } else {
130
+ params.push(fields[field]);
131
+ }
132
+ }
133
+ }
134
+
135
+ if (updates.length === 0) {
136
+ return NextResponse.json({ error: '没有需要更新的字段' }, { status: 400 });
137
+ }
138
+
139
+ params.push(id);
140
+ await execute(
141
+ `UPDATE extension_bindings SET ${updates.join(', ')} WHERE id = ?`,
142
+ params
143
+ );
144
+
145
+ return NextResponse.json({ success: true, id });
146
+ } catch (e) {
147
+ console.error('更新插件绑定失败:', e);
148
+ return NextResponse.json({ error: '更新插件绑定失败' }, { status: 500 });
149
+ }
150
+ }
151
+
152
+ // DELETE: 删除绑定
153
+ export async function DELETE(request: NextRequest) {
154
+ try {
155
+ await ensureDb();
156
+ const { searchParams } = new URL(request.url);
157
+ const id = searchParams.get('id');
158
+
159
+ if (!id) {
160
+ return NextResponse.json({ error: '缺少绑定 ID' }, { status: 400 });
161
+ }
162
+
163
+ await execute('DELETE FROM extension_bindings WHERE id = ?', [id]);
164
+ return NextResponse.json({ success: true, id });
165
+ } catch (e) {
166
+ console.error('删除插件绑定失败:', e);
167
+ return NextResponse.json({ error: '删除插件绑定失败' }, { status: 500 });
168
+ }
169
+ }
@@ -1,7 +1,7 @@
1
1
  // app/api/plugins/export/route.ts - 插件导出 API
2
2
 
3
3
  import { NextRequest, NextResponse } from 'next/server';
4
- import { getDbPool } from '@/lib/db';
4
+ import { execute } from '@/lib/db';
5
5
  import { ensureDb } from '@/lib/db-init';
6
6
  import { readPluginCode } from '@/lib/plugin-files';
7
7
 
@@ -70,17 +70,16 @@ async function fillPluginCode(plugin: ReturnType<typeof rowToPlugin>): Promise<t
70
70
  export async function GET(request: NextRequest) {
71
71
  try {
72
72
  await ensureDb();
73
- const db = getDbPool();
74
73
 
75
74
  const { searchParams } = new URL(request.url);
76
75
  const pluginId = searchParams.get('id');
77
76
 
78
77
  if (pluginId) {
79
78
  // 导出单个插件(直接返回插件对象,不带 format 包裹)
80
- const [rows] = await db.execute(
79
+ const [rows] = await execute(
81
80
  'SELECT * FROM extensions WHERE id = ?',
82
81
  [pluginId]
83
- ) as [Record<string, unknown>[], unknown];
82
+ );
84
83
 
85
84
  if (rows.length === 0) {
86
85
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
@@ -97,9 +96,9 @@ export async function GET(request: NextRequest) {
97
96
  }
98
97
 
99
98
  // 导出全部插件(多插件包格式)
100
- const [rows] = await db.execute(
99
+ const [rows] = await execute(
101
100
  'SELECT * FROM extensions ORDER BY updated_at DESC'
102
- ) as [Record<string, unknown>[], unknown];
101
+ );
103
102
 
104
103
  const plugins = await Promise.all(rows.map(r => fillPluginCode(rowToPlugin(r))));
105
104
 
@@ -1,7 +1,7 @@
1
1
  // app/api/plugins/export-xye/route.ts - 导出插件为 .xye 包(ZIP 格式)
2
2
 
3
3
  import { NextRequest, NextResponse } from 'next/server';
4
- import { getDbPool } from '@/lib/db';
4
+ import { execute } from '@/lib/db';
5
5
  import { ensureDb } from '@/lib/db-init';
6
6
  import { getPluginDir, readPluginCode } from '@/lib/plugin-files';
7
7
  import { listPluginFiles } from '@/lib/plugin-files';
@@ -41,7 +41,6 @@ function parsePermissions(value: unknown): {
41
41
  export async function GET(request: NextRequest) {
42
42
  try {
43
43
  await ensureDb();
44
- const db = getDbPool();
45
44
 
46
45
  const { searchParams } = new URL(request.url);
47
46
  const pluginId = searchParams.get('id');
@@ -50,10 +49,10 @@ export async function GET(request: NextRequest) {
50
49
  return NextResponse.json({ error: '缺少 id 参数' }, { status: 400 });
51
50
  }
52
51
 
53
- const [rows] = await db.execute(
52
+ const [rows] = await execute(
54
53
  'SELECT * FROM extensions WHERE id = ?',
55
54
  [pluginId]
56
- ) as [Record<string, unknown>[], unknown];
55
+ );
57
56
 
58
57
  if (rows.length === 0) {
59
58
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { NextRequest, NextResponse } from 'next/server';
4
4
  import { ensureDb } from '@/lib/db-init';
5
- import { getDbPool } from '@/lib/db';
5
+ import { execute } from '@/lib/db';
6
6
  import { listPluginFiles, readPluginResourceText } from '@/lib/plugin-files';
7
7
  import path from 'path';
8
8
 
@@ -56,11 +56,10 @@ export async function GET(request: NextRequest) {
56
56
 
57
57
  // 验证插件存在
58
58
  await ensureDb();
59
- const db = getDbPool();
60
- const [rows] = await db.execute(
59
+ const [rows] = await execute(
61
60
  'SELECT id FROM extensions WHERE id = ?',
62
61
  [pluginId]
63
- ) as [Array<{ id: string }>, unknown];
62
+ );
64
63
 
65
64
  if (rows.length === 0) {
66
65
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { NextRequest, NextResponse } from 'next/server';
4
4
  import { ensureDb } from '@/lib/db-init';
5
- import { getDbPool } from '@/lib/db';
5
+ import { execute } from '@/lib/db';
6
6
  import { getPluginDir } from '@/lib/plugin-files';
7
7
  import path from 'path';
8
8
  import { promises as fs } from 'fs';
@@ -19,15 +19,11 @@ function isSafePath(filePath: string): boolean {
19
19
  /** 验证插件存在 */
20
20
  async function verifyPlugin(pluginId: string) {
21
21
  await ensureDb();
22
- const db = getDbPool();
23
- const [rows] = await db.execute(
22
+ const [rows] = await execute(
24
23
  'SELECT id FROM extensions WHERE id = ?',
25
24
  [pluginId]
26
- ) as [Array<{ id: string }>, unknown];
27
- if (rows.length === 0) {
28
- return null;
29
- }
30
- return db;
25
+ );
26
+ return (rows as Record<string, unknown>[]).length > 0;
31
27
  }
32
28
 
33
29
  // ==================== POST: 上传资源文件 ====================
@@ -48,8 +44,8 @@ export async function POST(request: NextRequest) {
48
44
  return NextResponse.json({ error: '非法目标目录' }, { status: 400 });
49
45
  }
50
46
 
51
- const db = await verifyPlugin(pluginId);
52
- if (!db) {
47
+ const exists = await verifyPlugin(pluginId);
48
+ if (!exists) {
53
49
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
54
50
  }
55
51
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { NextRequest, NextResponse } from 'next/server';
4
4
  import { ensureDb } from '@/lib/db-init';
5
- import { getDbPool } from '@/lib/db';
5
+ import { execute } from '@/lib/db';
6
6
  import { writePluginResource, getPluginDir } from '@/lib/plugin-files';
7
7
  import path from 'path';
8
8
  import { promises as fs } from 'fs';
@@ -19,19 +19,15 @@ function isSafePath(filePath: string): boolean {
19
19
  /** 验证插件存在 */
20
20
  async function verifyPlugin(pluginId: string) {
21
21
  await ensureDb();
22
- const db = getDbPool();
23
- const [rows] = await db.execute(
22
+ const [rows] = await execute(
24
23
  'SELECT id FROM extensions WHERE id = ?',
25
24
  [pluginId]
26
- ) as [Array<{ id: string }>, unknown];
27
- if (rows.length === 0) {
28
- return null;
29
- }
30
- return db;
25
+ );
26
+ return (rows as Record<string, unknown>[]).length > 0;
31
27
  }
32
28
 
33
29
  /** 同步 manifest.json 到数据库 */
34
- async function syncManifestToDb(db: ReturnType<typeof getDbPool>, pluginId: string, content: string) {
30
+ async function syncManifestToDb(pluginId: string, content: string) {
35
31
  try {
36
32
  const manifest = JSON.parse(content);
37
33
  const updates: string[] = [];
@@ -58,7 +54,7 @@ async function syncManifestToDb(db: ReturnType<typeof getDbPool>, pluginId: stri
58
54
 
59
55
  if (updates.length > 0) {
60
56
  params.push(pluginId);
61
- await db.execute(`UPDATE extensions SET ${updates.join(', ')} WHERE id = ?`, params);
57
+ await execute(`UPDATE extensions SET ${updates.join(', ')} WHERE id = ?`, params);
62
58
  }
63
59
  } catch (e) {
64
60
  console.error('同步 manifest 到数据库失败:', e);
@@ -83,8 +79,8 @@ export async function PUT(request: NextRequest) {
83
79
  return NextResponse.json({ error: '非法文件路径' }, { status: 400 });
84
80
  }
85
81
 
86
- const db = await verifyPlugin(pluginId);
87
- if (!db) {
82
+ const exists = await verifyPlugin(pluginId);
83
+ if (!exists) {
88
84
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
89
85
  }
90
86
 
@@ -98,7 +94,7 @@ export async function PUT(request: NextRequest) {
98
94
  await writePluginResource(pluginId, filePath, content);
99
95
 
100
96
  if (filePath === 'manifest.json') {
101
- await syncManifestToDb(db, pluginId, content);
97
+ await syncManifestToDb(pluginId, content);
102
98
  }
103
99
 
104
100
  return NextResponse.json({ success: true, path: filePath });
@@ -122,8 +118,8 @@ export async function POST(request: NextRequest) {
122
118
  return NextResponse.json({ error: '非法插件 ID' }, { status: 400 });
123
119
  }
124
120
 
125
- const db = await verifyPlugin(pluginId);
126
- if (!db) {
121
+ const exists = await verifyPlugin(pluginId);
122
+ if (!exists) {
127
123
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
128
124
  }
129
125
 
@@ -180,18 +176,19 @@ export async function DELETE(request: NextRequest) {
180
176
  return NextResponse.json({ error: '不能删除 resources 目录(资源文件目录,仅支持删除其中的文件)' }, { status: 400 });
181
177
  }
182
178
 
183
- const db = await verifyPlugin(pluginId);
184
- if (!db) {
179
+ const exists = await verifyPlugin(pluginId);
180
+ if (!exists) {
185
181
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
186
182
  }
187
183
 
188
184
  // 从数据库读取 code_path,提取入口文件名,禁止删除入口文件
189
- const [rows] = await db.execute(
185
+ const [codeRows] = await execute(
190
186
  'SELECT code_path FROM extensions WHERE id = ?',
191
187
  [pluginId]
192
- ) as [Array<{ code_path: string }>, unknown];
193
- if (rows.length > 0 && rows[0].code_path) {
194
- const entryFile = rows[0].code_path.split('/').pop();
188
+ );
189
+ const codeRowList = codeRows as Record<string, unknown>[];
190
+ if (codeRowList.length > 0 && codeRowList[0].code_path) {
191
+ const entryFile = (codeRowList[0].code_path as string).split('/').pop();
195
192
  if (entryFile && filePath === entryFile) {
196
193
  return NextResponse.json({ error: `不能删除入口文件 ${entryFile}` }, { status: 400 });
197
194
  }
@@ -236,8 +233,8 @@ export async function PATCH(request: NextRequest) {
236
233
  return NextResponse.json({ error: '非法文件路径' }, { status: 400 });
237
234
  }
238
235
 
239
- const db = await verifyPlugin(pluginId);
240
- if (!db) {
236
+ const exists = await verifyPlugin(pluginId);
237
+ if (!exists) {
241
238
  return NextResponse.json({ error: '插件不存在' }, { status: 404 });
242
239
  }
243
240