@xingyuchen/mysql-mcp-server 3.0.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.
@@ -0,0 +1,159 @@
1
+ import { DatabaseManager } from "./database.js";
2
+ import { logger } from "./logger.js";
3
+ export class ConnectionManager {
4
+ connections = new Map();
5
+ activeConnectionId = null;
6
+ /**
7
+ * 添加新的数据库连接
8
+ */
9
+ async addConnection(connectionId, config) {
10
+ try {
11
+ // 如果连接已存在,先断开
12
+ if (this.connections.has(connectionId)) {
13
+ await this.removeConnection(connectionId);
14
+ }
15
+ // 创建新的数据库管理器并连接
16
+ const manager = new DatabaseManager();
17
+ await manager.connect(config);
18
+ // 存储连接信息
19
+ const connectionInfo = {
20
+ id: connectionId,
21
+ config,
22
+ manager,
23
+ isActive: false,
24
+ connectedAt: new Date()
25
+ };
26
+ this.connections.set(connectionId, connectionInfo);
27
+ // 如果是第一个连接,自动设为活跃连接
28
+ if (this.activeConnectionId === null) {
29
+ await this.switchActiveConnection(connectionId);
30
+ }
31
+ logger.info(`数据库连接已添加`, {
32
+ connectionId,
33
+ host: config.host,
34
+ database: config.database,
35
+ totalConnections: this.connections.size
36
+ });
37
+ }
38
+ catch (error) {
39
+ const err = error instanceof Error ? error : new Error(String(error));
40
+ logger.error(`添加数据库连接失败`, { connectionId, error: err.message });
41
+ throw new Error(`添加数据库连接失败: ${err.message}`);
42
+ }
43
+ }
44
+ /**
45
+ * 移除数据库连接
46
+ */
47
+ async removeConnection(connectionId) {
48
+ const connection = this.connections.get(connectionId);
49
+ if (!connection) {
50
+ throw new Error(`连接 '${connectionId}' 不存在`);
51
+ }
52
+ try {
53
+ // 断开数据库连接
54
+ await connection.manager.disconnect();
55
+ // 从连接列表中移除
56
+ this.connections.delete(connectionId);
57
+ // 如果移除的是活跃连接,选择新的活跃连接
58
+ if (this.activeConnectionId === connectionId) {
59
+ this.activeConnectionId = null;
60
+ // 如果还有其他连接,选择第一个作为活跃连接
61
+ const remainingConnections = Array.from(this.connections.keys());
62
+ if (remainingConnections.length > 0) {
63
+ await this.switchActiveConnection(remainingConnections[0]);
64
+ }
65
+ }
66
+ logger.info(`数据库连接已移除`, {
67
+ connectionId,
68
+ remainingConnections: this.connections.size
69
+ });
70
+ }
71
+ catch (error) {
72
+ const err = error instanceof Error ? error : new Error(String(error));
73
+ logger.error(`移除数据库连接失败`, { connectionId, error: err.message });
74
+ throw new Error(`移除数据库连接失败: ${err.message}`);
75
+ }
76
+ }
77
+ /**
78
+ * 切换活跃连接
79
+ */
80
+ async switchActiveConnection(connectionId) {
81
+ if (!this.connections.has(connectionId)) {
82
+ throw new Error(`连接 '${connectionId}' 不存在`);
83
+ }
84
+ // 更新活跃状态
85
+ this.connections.forEach((conn, id) => {
86
+ conn.isActive = (id === connectionId);
87
+ });
88
+ this.activeConnectionId = connectionId;
89
+ logger.info(`已切换活跃连接`, {
90
+ connectionId,
91
+ database: this.connections.get(connectionId)?.config.database
92
+ });
93
+ }
94
+ /**
95
+ * 获取活跃连接的数据库管理器
96
+ */
97
+ getActiveConnection() {
98
+ if (!this.activeConnectionId) {
99
+ return null;
100
+ }
101
+ const connection = this.connections.get(this.activeConnectionId);
102
+ return connection ? connection.manager : null;
103
+ }
104
+ /**
105
+ * 获取指定连接的数据库管理器
106
+ */
107
+ getConnection(connectionId) {
108
+ const connection = this.connections.get(connectionId);
109
+ return connection ? connection.manager : null;
110
+ }
111
+ /**
112
+ * 获取活跃连接ID
113
+ */
114
+ getActiveConnectionId() {
115
+ return this.activeConnectionId;
116
+ }
117
+ /**
118
+ * 列出所有连接信息
119
+ */
120
+ listConnections() {
121
+ return Array.from(this.connections.values()).map(conn => ({
122
+ id: conn.id,
123
+ host: conn.config.host,
124
+ database: conn.config.database,
125
+ user: conn.config.user,
126
+ port: conn.config.port,
127
+ isActive: conn.isActive,
128
+ connectedAt: conn.connectedAt.toISOString()
129
+ }));
130
+ }
131
+ /**
132
+ * 检查是否有活跃连接
133
+ */
134
+ hasActiveConnection() {
135
+ return this.activeConnectionId !== null && this.connections.has(this.activeConnectionId);
136
+ }
137
+ /**
138
+ * 获取连接总数
139
+ */
140
+ getConnectionCount() {
141
+ return this.connections.size;
142
+ }
143
+ /**
144
+ * 断开所有连接
145
+ */
146
+ async disconnectAll() {
147
+ const connectionIds = Array.from(this.connections.keys());
148
+ for (const connectionId of connectionIds) {
149
+ try {
150
+ await this.removeConnection(connectionId);
151
+ }
152
+ catch (error) {
153
+ logger.error(`断开连接失败`, { connectionId, error: error instanceof Error ? error.message : String(error) });
154
+ }
155
+ }
156
+ this.activeConnectionId = null;
157
+ logger.info(`所有数据库连接已断开`);
158
+ }
159
+ }
@@ -0,0 +1,400 @@
1
+ import mysql from "mysql2/promise";
2
+ import { dbLogger, logSqlOperation, logConnection } from "./logger.js";
3
+ import { TransactionManager } from "./transaction-manager.js";
4
+ export class DatabaseManager {
5
+ connection = null;
6
+ config = null;
7
+ transactionManager = new TransactionManager();
8
+ /**
9
+ * 连接到MySQL数据库
10
+ */
11
+ async connect(config) {
12
+ const startTime = Date.now();
13
+ try {
14
+ this.config = config;
15
+ this.connection = await mysql.createConnection({
16
+ host: config.host,
17
+ port: config.port,
18
+ user: config.user,
19
+ password: config.password,
20
+ database: config.database,
21
+ charset: 'utf8mb4',
22
+ timezone: '+08:00',
23
+ multipleStatements: true, // 允许执行多条语句
24
+ });
25
+ // 测试连接
26
+ await this.connection.ping();
27
+ const duration = Date.now() - startTime;
28
+ logConnection('connect', config);
29
+ dbLogger.info(`数据库连接成功,耗时: ${duration}ms`, {
30
+ host: config.host,
31
+ port: config.port,
32
+ database: config.database,
33
+ user: config.user
34
+ });
35
+ }
36
+ catch (error) {
37
+ const duration = Date.now() - startTime;
38
+ const err = error instanceof Error ? error : new Error(String(error));
39
+ logConnection('connect', config, err);
40
+ dbLogger.error(`数据库连接失败,耗时: ${duration}ms`, {
41
+ host: config.host,
42
+ port: config.port,
43
+ database: config.database,
44
+ user: config.user,
45
+ error: err.message
46
+ });
47
+ throw new Error(`数据库连接失败: ${err.message}`);
48
+ }
49
+ }
50
+ /**
51
+ * 检查是否已连接
52
+ */
53
+ isConnected() {
54
+ return this.connection !== null;
55
+ }
56
+ /**
57
+ * 执行SQL查询(支持所有SQL操作,自动事务管理)
58
+ */
59
+ async executeQuery(query, params = []) {
60
+ if (!this.connection) {
61
+ throw new Error("数据库未连接");
62
+ }
63
+ const startTime = Date.now();
64
+ const cleanQuery = query.trim();
65
+ const queryType = this.getQueryType(cleanQuery);
66
+ try {
67
+ // 对于INSERT、UPDATE、DELETE操作,自动开启事务
68
+ if (['INSERT', 'UPDATE', 'DELETE'].includes(queryType)) {
69
+ await this.ensureTransaction();
70
+ }
71
+ // 对于UPDATE和DELETE操作,先查询原始数据用于生成回滚查询
72
+ let originalData = [];
73
+ let tableName = '';
74
+ let whereClause = '';
75
+ if (['UPDATE', 'DELETE'].includes(queryType)) {
76
+ try {
77
+ tableName = this.extractTableName(cleanQuery);
78
+ const whereMatch = cleanQuery.match(/WHERE\s+(.+?)(?:\s+ORDER|\s+LIMIT|$)/i);
79
+ if (whereMatch) {
80
+ whereClause = whereMatch[1];
81
+ const originalDataQuery = `SELECT * FROM \`${tableName}\` WHERE ${whereClause}`;
82
+ // 提取WHERE条件的参数
83
+ let whereParams = [];
84
+ if (queryType === 'UPDATE') {
85
+ // UPDATE的参数格式是:[set_values..., where_params...]
86
+ const setCount = (cleanQuery.match(/SET\s+[^=]+=/gi) || []).length;
87
+ whereParams = params.slice(setCount);
88
+ }
89
+ else {
90
+ // DELETE的参数就是WHERE条件的参数
91
+ whereParams = params;
92
+ }
93
+ const [originalResult] = await this.connection.execute(originalDataQuery, whereParams);
94
+ originalData = originalResult;
95
+ }
96
+ }
97
+ catch (error) {
98
+ dbLogger.warn(`无法查询${queryType}操作的原始数据`, {
99
+ error: error instanceof Error ? error.message : String(error)
100
+ });
101
+ }
102
+ }
103
+ // 记录开始执行
104
+ dbLogger.debug(`开始执行SQL`, { query: cleanQuery, params });
105
+ const [result] = params.length > 0
106
+ ? await this.connection.execute(cleanQuery, params)
107
+ : await this.connection.execute(cleanQuery);
108
+ const duration = Date.now() - startTime;
109
+ // 记录成功执行
110
+ logSqlOperation('EXECUTE', cleanQuery, params, duration);
111
+ // 根据操作类型返回不同格式的结果
112
+ if (this.isSelectQuery(cleanQuery)) {
113
+ return {
114
+ type: 'SELECT',
115
+ data: result,
116
+ rowCount: Array.isArray(result) ? result.length : 0,
117
+ duration
118
+ };
119
+ }
120
+ else {
121
+ const header = result;
122
+ // 对于有影响行数的操作,记录到事务历史
123
+ if (['INSERT', 'UPDATE', 'DELETE'].includes(queryType) && (header.affectedRows || 0) > 0) {
124
+ if (!tableName) {
125
+ tableName = this.extractTableName(cleanQuery);
126
+ }
127
+ let rollbackQuery;
128
+ let rollbackParams;
129
+ // 生成回滚查询
130
+ if (queryType === 'INSERT' && header.insertId) {
131
+ // INSERT的回滚是DELETE
132
+ rollbackQuery = `DELETE FROM \`${tableName}\` WHERE id = ?`;
133
+ rollbackParams = [header.insertId];
134
+ }
135
+ else if (queryType === 'UPDATE' && originalData.length > 0) {
136
+ // UPDATE的回滚:恢复原始数据
137
+ rollbackQuery = this.generateUpdateRollbackQuery(tableName, originalData[0], whereClause);
138
+ const setCount = (cleanQuery.match(/SET\s+[^=]+=/gi) || []).length;
139
+ const whereParams = params.slice(setCount);
140
+ rollbackParams = [...Object.values(originalData[0]), ...whereParams];
141
+ }
142
+ else if (queryType === 'DELETE' && originalData.length > 0) {
143
+ // DELETE的回滚:重新插入删除的数据
144
+ rollbackQuery = this.generateInsertRollbackQuery(tableName, originalData);
145
+ rollbackParams = originalData.flatMap((row) => Object.values(row));
146
+ }
147
+ this.transactionManager.recordOperation({
148
+ type: queryType,
149
+ tableName,
150
+ description: `执行 ${queryType} 操作,影响 ${header.affectedRows} 行`,
151
+ query: cleanQuery,
152
+ params,
153
+ affectedRows: header.affectedRows || 0,
154
+ rollbackQuery,
155
+ rollbackParams
156
+ });
157
+ }
158
+ return {
159
+ type: queryType,
160
+ affectedRows: header.affectedRows || 0,
161
+ insertId: header.insertId || null,
162
+ changedRows: header.changedRows || 0,
163
+ duration
164
+ };
165
+ }
166
+ }
167
+ catch (error) {
168
+ const duration = Date.now() - startTime;
169
+ const err = error instanceof Error ? error : new Error(String(error));
170
+ // 记录执行失败
171
+ logSqlOperation('EXECUTE', cleanQuery, params, duration, err);
172
+ throw new Error(`查询执行失败: ${err.message}`);
173
+ }
174
+ }
175
+ /**
176
+ * 显示所有表
177
+ */
178
+ async showTables() {
179
+ if (!this.connection) {
180
+ throw new Error("数据库未连接");
181
+ }
182
+ try {
183
+ const [rows] = await this.connection.execute("SHOW TABLES");
184
+ return rows;
185
+ }
186
+ catch (error) {
187
+ const errorMessage = error instanceof Error ? error.message : String(error);
188
+ throw new Error(`获取表列表失败: ${errorMessage}`);
189
+ }
190
+ }
191
+ /**
192
+ * 描述表结构
193
+ */
194
+ async describeTable(tableName) {
195
+ if (!this.connection) {
196
+ throw new Error("数据库未连接");
197
+ }
198
+ try {
199
+ // 验证表名(防止SQL注入)
200
+ if (!this.isValidTableName(tableName)) {
201
+ throw new Error("无效的表名");
202
+ }
203
+ const [rows] = await this.connection.execute(`DESCRIBE \`${tableName}\``);
204
+ return rows;
205
+ }
206
+ catch (error) {
207
+ const errorMessage = error instanceof Error ? error.message : String(error);
208
+ throw new Error(`获取表结构失败: ${errorMessage}`);
209
+ }
210
+ }
211
+ /**
212
+ * 安全执行查询(支持参数化查询)
213
+ */
214
+ async executeQuerySafe(query, params = []) {
215
+ if (!this.connection) {
216
+ throw new Error("数据库未连接");
217
+ }
218
+ try {
219
+ const [rows] = await this.connection.execute(query, params);
220
+ return rows;
221
+ }
222
+ catch (error) {
223
+ const errorMessage = error instanceof Error ? error.message : String(error);
224
+ throw new Error(`查询执行失败: ${errorMessage}`);
225
+ }
226
+ }
227
+ /**
228
+ * 获取数据库信息
229
+ */
230
+ async getDatabaseInfo() {
231
+ if (!this.connection || !this.config) {
232
+ throw new Error("数据库未连接");
233
+ }
234
+ try {
235
+ const [version] = await this.connection.execute("SELECT VERSION() as version");
236
+ const [charset] = await this.connection.execute("SELECT @@character_set_database as charset");
237
+ const [collation] = await this.connection.execute("SELECT @@collation_database as collation");
238
+ return {
239
+ host: this.config.host,
240
+ port: this.config.port,
241
+ database: this.config.database,
242
+ user: this.config.user,
243
+ version: version[0]?.version,
244
+ charset: charset[0]?.charset,
245
+ collation: collation[0]?.collation,
246
+ };
247
+ }
248
+ catch (error) {
249
+ const errorMessage = error instanceof Error ? error.message : String(error);
250
+ throw new Error(`获取数据库信息失败: ${errorMessage}`);
251
+ }
252
+ }
253
+ /**
254
+ * 断开数据库连接
255
+ */
256
+ async disconnect() {
257
+ if (this.connection) {
258
+ try {
259
+ await this.connection.end();
260
+ logConnection('disconnect', this.config);
261
+ dbLogger.info("数据库连接已断开");
262
+ }
263
+ catch (error) {
264
+ const err = error instanceof Error ? error : new Error(String(error));
265
+ logConnection('disconnect', this.config, err);
266
+ dbLogger.error("断开数据库连接时发生错误", { error: err.message });
267
+ }
268
+ finally {
269
+ this.connection = null;
270
+ this.config = null;
271
+ }
272
+ }
273
+ }
274
+ /**
275
+ * 判断是否为SELECT查询
276
+ */
277
+ isSelectQuery(query) {
278
+ const upperQuery = query.toUpperCase().trim();
279
+ return upperQuery.startsWith('SELECT') || upperQuery.startsWith('SHOW') || upperQuery.startsWith('DESCRIBE') || upperQuery.startsWith('DESC');
280
+ }
281
+ /**
282
+ * 获取查询类型
283
+ */
284
+ getQueryType(query) {
285
+ const upperQuery = query.toUpperCase().trim();
286
+ const firstWord = upperQuery.split(' ')[0];
287
+ return firstWord || 'UNKNOWN';
288
+ }
289
+ /**
290
+ * 判断是否为危险查询
291
+ */
292
+ isDangerousQuery(query) {
293
+ const dangerousKeywords = [
294
+ 'DROP', 'DELETE', 'UPDATE', 'INSERT', 'ALTER',
295
+ 'TRUNCATE', 'CREATE', 'REPLACE', 'LOAD', 'IMPORT'
296
+ ];
297
+ const upperQuery = query.toUpperCase().trim();
298
+ return dangerousKeywords.some(keyword => upperQuery.startsWith(keyword + ' ') ||
299
+ upperQuery.includes(' ' + keyword + ' '));
300
+ }
301
+ /**
302
+ * 验证表名是否合法
303
+ */
304
+ isValidTableName(tableName) {
305
+ // 只允许字母、数字、下划线,且不能以数字开头
306
+ const tableNameRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
307
+ return tableNameRegex.test(tableName) && tableName.length <= 64;
308
+ }
309
+ /**
310
+ * 从SQL查询中提取表名
311
+ */
312
+ extractTableName(query) {
313
+ const cleanQuery = query.trim().toUpperCase();
314
+ // INSERT INTO table_name
315
+ if (cleanQuery.startsWith('INSERT')) {
316
+ const match = cleanQuery.match(/INSERT\s+INTO\s+`?(\w+)`?/);
317
+ return match ? match[1] : 'unknown';
318
+ }
319
+ // UPDATE table_name
320
+ if (cleanQuery.startsWith('UPDATE')) {
321
+ const match = cleanQuery.match(/UPDATE\s+`?(\w+)`?/);
322
+ return match ? match[1] : 'unknown';
323
+ }
324
+ // DELETE FROM table_name
325
+ if (cleanQuery.startsWith('DELETE')) {
326
+ const match = cleanQuery.match(/DELETE\s+FROM\s+`?(\w+)`?/);
327
+ return match ? match[1] : 'unknown';
328
+ }
329
+ return 'unknown';
330
+ }
331
+ /**
332
+ * 开始事务
333
+ */
334
+ async beginTransaction() {
335
+ if (!this.connection) {
336
+ throw new Error("数据库未连接");
337
+ }
338
+ await this.connection.beginTransaction();
339
+ dbLogger.info("事务已开始");
340
+ }
341
+ /**
342
+ * 提交事务
343
+ */
344
+ async commitTransaction() {
345
+ if (!this.connection) {
346
+ throw new Error("数据库未连接");
347
+ }
348
+ await this.connection.commit();
349
+ dbLogger.info("事务已提交");
350
+ }
351
+ /**
352
+ * 回滚事务
353
+ */
354
+ async rollbackTransaction() {
355
+ if (!this.connection) {
356
+ throw new Error("数据库未连接");
357
+ }
358
+ await this.connection.rollback();
359
+ dbLogger.info("事务已回滚");
360
+ }
361
+ /**
362
+ * 确保事务已开始
363
+ */
364
+ async ensureTransaction() {
365
+ if (!this.transactionManager.isActive()) {
366
+ await this.beginTransaction();
367
+ await this.transactionManager.startTransaction();
368
+ }
369
+ }
370
+ /**
371
+ * 生成UPDATE操作的回滚查询
372
+ */
373
+ generateUpdateRollbackQuery(tableName, originalData, whereClause) {
374
+ const columns = Object.keys(originalData);
375
+ const setClause = columns.map(col => `\`${col}\` = ?`).join(', ');
376
+ return `UPDATE \`${tableName}\` SET ${setClause} WHERE ${whereClause}`;
377
+ }
378
+ /**
379
+ * 生成INSERT操作的回滚查询(用于DELETE的回滚)
380
+ */
381
+ generateInsertRollbackQuery(tableName, originalData) {
382
+ if (originalData.length === 0)
383
+ return '';
384
+ const columns = Object.keys(originalData[0]);
385
+ const placeholders = columns.map(() => '?').join(', ');
386
+ if (originalData.length === 1) {
387
+ return `INSERT INTO \`${tableName}\` (\`${columns.join('`, `')}\`) VALUES (${placeholders})`;
388
+ }
389
+ else {
390
+ const valuePlaceholders = originalData.map(() => `(${placeholders})`).join(', ');
391
+ return `INSERT INTO \`${tableName}\` (\`${columns.join('`, `')}\`) VALUES ${valuePlaceholders}`;
392
+ }
393
+ }
394
+ /**
395
+ * 获取事务管理器
396
+ */
397
+ getTransactionManager() {
398
+ return this.transactionManager;
399
+ }
400
+ }