fdb2 1.0.2 → 1.0.4
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/bin/fdb2.js +11 -3
- package/dist/public/.vite/manifest.json +82 -0
- package/dist/public/bootstrap-icons.woff +0 -0
- package/dist/public/bootstrap-icons.woff2 +0 -0
- package/dist/public/bootstrap.css +14152 -0
- package/dist/public/bootstrap.js +5038 -0
- package/dist/public/explorer.css +2137 -0
- package/dist/public/explorer.js +49846 -0
- package/dist/public/index.css +1071 -0
- package/dist/public/index.js +12811 -0
- package/dist/public/layout.css +318 -0
- package/dist/public/layout.js +25 -0
- package/dist/public/vue.css +1 -0
- package/dist/public/vue.js +9111 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +598 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.ts +677 -0
- package/dist/server/model/connection.entity.d.ts +55 -0
- package/dist/server/model/connection.entity.d.ts.map +1 -0
- package/dist/server/model/connection.entity.js +59 -0
- package/dist/server/model/connection.entity.js.map +1 -0
- package/dist/server/model/connection.entity.ts +66 -0
- package/dist/server/model/database.entity.d.ts +203 -0
- package/dist/server/model/database.entity.d.ts.map +1 -0
- package/dist/server/model/database.entity.js +211 -0
- package/dist/server/model/database.entity.js.map +1 -0
- package/dist/server/model/database.entity.ts +246 -0
- package/dist/server/service/connection.service.d.ts +79 -0
- package/dist/server/service/connection.service.d.ts.map +1 -0
- package/dist/server/service/connection.service.js +351 -0
- package/dist/server/service/connection.service.js.map +1 -0
- package/dist/server/service/connection.service.ts +341 -0
- package/dist/server/service/database/base.service.d.ts +152 -0
- package/dist/server/service/database/base.service.d.ts.map +1 -0
- package/dist/server/service/database/base.service.js +236 -0
- package/dist/server/service/database/base.service.js.map +1 -0
- package/dist/server/service/database/base.service.ts +363 -0
- package/dist/server/service/database/cockroachdb.service.d.ts +95 -0
- package/dist/server/service/database/cockroachdb.service.d.ts.map +1 -0
- package/dist/server/service/database/cockroachdb.service.js +634 -0
- package/dist/server/service/database/cockroachdb.service.js.map +1 -0
- package/dist/server/service/database/cockroachdb.service.ts +659 -0
- package/dist/server/service/database/database.service.d.ts +487 -0
- package/dist/server/service/database/database.service.d.ts.map +1 -0
- package/dist/server/service/database/database.service.js +580 -0
- package/dist/server/service/database/database.service.js.map +1 -0
- package/dist/server/service/database/database.service.ts +630 -0
- package/dist/server/service/database/index.d.ts +8 -0
- package/dist/server/service/database/index.d.ts.map +1 -0
- package/dist/server/service/database/index.js +18 -0
- package/dist/server/service/database/index.js.map +1 -0
- package/dist/server/service/database/index.ts +7 -0
- package/dist/server/service/database/mongodb.service.d.ts +99 -0
- package/dist/server/service/database/mongodb.service.d.ts.map +1 -0
- package/dist/server/service/database/mongodb.service.js +459 -0
- package/dist/server/service/database/mongodb.service.js.map +1 -0
- package/dist/server/service/database/mongodb.service.ts +454 -0
- package/dist/server/service/database/mssql.service.d.ts +98 -0
- package/dist/server/service/database/mssql.service.d.ts.map +1 -0
- package/dist/server/service/database/mssql.service.js +694 -0
- package/dist/server/service/database/mssql.service.js.map +1 -0
- package/dist/server/service/database/mssql.service.ts +723 -0
- package/dist/server/service/database/mysql.service.d.ts +94 -0
- package/dist/server/service/database/mysql.service.d.ts.map +1 -0
- package/dist/server/service/database/mysql.service.js +735 -0
- package/dist/server/service/database/mysql.service.js.map +1 -0
- package/dist/server/service/database/mysql.service.ts +761 -0
- package/dist/server/service/database/oracle.service.d.ts +106 -0
- package/dist/server/service/database/oracle.service.d.ts.map +1 -0
- package/dist/server/service/database/oracle.service.js +787 -0
- package/dist/server/service/database/oracle.service.js.map +1 -0
- package/dist/server/service/database/oracle.service.ts +832 -0
- package/dist/server/service/database/postgres.service.d.ts +102 -0
- package/dist/server/service/database/postgres.service.d.ts.map +1 -0
- package/dist/server/service/database/postgres.service.js +696 -0
- package/dist/server/service/database/postgres.service.js.map +1 -0
- package/dist/server/service/database/postgres.service.ts +741 -0
- package/dist/server/service/database/sap.service.d.ts +95 -0
- package/dist/server/service/database/sap.service.d.ts.map +1 -0
- package/dist/server/service/database/sap.service.js +695 -0
- package/dist/server/service/database/sap.service.js.map +1 -0
- package/dist/server/service/database/sap.service.ts +713 -0
- package/dist/server/service/database/sqlite.service.d.ts +92 -0
- package/dist/server/service/database/sqlite.service.d.ts.map +1 -0
- package/dist/server/service/database/sqlite.service.js +532 -0
- package/dist/server/service/database/sqlite.service.js.map +1 -0
- package/dist/server/service/database/sqlite.service.ts +559 -0
- package/dist/server/service/session.service.ts +158 -0
- package/dist/view/index.html +45 -0
- package/package.json +2 -1
- package/scripts/preinstall.js +38 -0
- package/server.pid +0 -1
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MySQLService = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const base_service_1 = require("./base.service");
|
|
41
|
+
/**
|
|
42
|
+
* MySQL数据库服务实现
|
|
43
|
+
*/
|
|
44
|
+
class MySQLService extends base_service_1.BaseDatabaseService {
|
|
45
|
+
getDatabaseType() {
|
|
46
|
+
return 'mysql';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 导出表数据到 CSV 文件
|
|
50
|
+
*/
|
|
51
|
+
async exportTableDataToCSV(dataSource, databaseName, tableName, options) {
|
|
52
|
+
try {
|
|
53
|
+
const exportPath = options?.path || path.join(__dirname, '..', '..', '..', 'data', 'exports');
|
|
54
|
+
if (!fs.existsSync(exportPath)) {
|
|
55
|
+
fs.mkdirSync(exportPath, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
58
|
+
const exportFile = path.join(exportPath, `${tableName}_data_${timestamp}.csv`);
|
|
59
|
+
const columns = await this.getColumns(dataSource, databaseName, tableName);
|
|
60
|
+
const columnNames = columns.map(column => column.name);
|
|
61
|
+
// 写入 CSV 头部
|
|
62
|
+
const header = columnNames.join(',') + '\n';
|
|
63
|
+
fs.writeFileSync(exportFile, '\ufeff' + header, 'utf8');
|
|
64
|
+
// 分批处理数据
|
|
65
|
+
const batchSize = options?.batchSize || 10000;
|
|
66
|
+
let offset = 0;
|
|
67
|
+
let hasMoreData = true;
|
|
68
|
+
while (hasMoreData) {
|
|
69
|
+
const query = `SELECT * FROM ${this.quoteIdentifier(tableName)} LIMIT ${batchSize} OFFSET ${offset}`;
|
|
70
|
+
const data = await dataSource.query(query);
|
|
71
|
+
if (data.length === 0) {
|
|
72
|
+
hasMoreData = false;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
// 生成当前批次的 CSV 行
|
|
76
|
+
let batchContent = '';
|
|
77
|
+
data.forEach((row) => {
|
|
78
|
+
const values = columnNames.map(column => {
|
|
79
|
+
const value = row[column];
|
|
80
|
+
if (value === null || value === undefined)
|
|
81
|
+
return '';
|
|
82
|
+
if (typeof value === 'string') {
|
|
83
|
+
const stringValue = String(value);
|
|
84
|
+
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
|
|
85
|
+
return `"${stringValue.replace(/"/g, '""')}"`;
|
|
86
|
+
}
|
|
87
|
+
return stringValue;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === 'object') {
|
|
90
|
+
try {
|
|
91
|
+
const stringValue = JSON.stringify(value);
|
|
92
|
+
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
|
|
93
|
+
return `"${stringValue.replace(/"/g, '""')}"`;
|
|
94
|
+
}
|
|
95
|
+
return stringValue;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return `"${String(value).replace(/"/g, '""')}"`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return String(value);
|
|
102
|
+
});
|
|
103
|
+
batchContent += values.join(',') + '\n';
|
|
104
|
+
});
|
|
105
|
+
fs.appendFileSync(exportFile, batchContent, 'utf8');
|
|
106
|
+
offset += batchSize;
|
|
107
|
+
console.log(`MySQL导出CSV进度: ${tableName} - 已处理 ${offset} 行`);
|
|
108
|
+
}
|
|
109
|
+
return exportFile;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('MySQL导出CSV失败:', error);
|
|
113
|
+
throw new Error(`导出CSV失败: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 导出表数据到 JSON 文件
|
|
118
|
+
*/
|
|
119
|
+
async exportTableDataToJSON(dataSource, databaseName, tableName, options) {
|
|
120
|
+
try {
|
|
121
|
+
const exportPath = options?.path || path.join(__dirname, '..', '..', '..', 'data', 'exports');
|
|
122
|
+
if (!fs.existsSync(exportPath)) {
|
|
123
|
+
fs.mkdirSync(exportPath, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
126
|
+
const exportFile = path.join(exportPath, `${tableName}_data_${timestamp}.json`);
|
|
127
|
+
const columns = await this.getColumns(dataSource, databaseName, tableName);
|
|
128
|
+
const columnNames = columns.map(column => column.name);
|
|
129
|
+
// 分批处理数据,生成 JSON 数组
|
|
130
|
+
const batchSize = options?.batchSize || 10000;
|
|
131
|
+
let offset = 0;
|
|
132
|
+
let hasMoreData = true;
|
|
133
|
+
let isFirstBatch = true;
|
|
134
|
+
// 写入 JSON 开始
|
|
135
|
+
fs.writeFileSync(exportFile, '[', 'utf8');
|
|
136
|
+
while (hasMoreData) {
|
|
137
|
+
const query = `SELECT * FROM ${this.quoteIdentifier(tableName)} LIMIT ${batchSize} OFFSET ${offset}`;
|
|
138
|
+
const data = await dataSource.query(query);
|
|
139
|
+
if (data.length === 0) {
|
|
140
|
+
hasMoreData = false;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
// 生成当前批次的 JSON 数据
|
|
144
|
+
let batchContent = '';
|
|
145
|
+
data.forEach((row, index) => {
|
|
146
|
+
if (!isFirstBatch || index > 0) {
|
|
147
|
+
batchContent += ',';
|
|
148
|
+
}
|
|
149
|
+
// 处理每个字段,避免对已经是JSON字符串的值进行双重序列化
|
|
150
|
+
const processedRow = {};
|
|
151
|
+
for (const key in row) {
|
|
152
|
+
const value = row[key];
|
|
153
|
+
if (typeof value === 'string') {
|
|
154
|
+
// 检查是否是JSON字符串
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(value);
|
|
157
|
+
// 如果解析成功且是对象或数组,说明是JSON字符串,直接使用
|
|
158
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
159
|
+
processedRow[key] = parsed;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
processedRow[key] = value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// 不是JSON字符串,直接使用
|
|
167
|
+
processedRow[key] = value;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
processedRow[key] = value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
batchContent += JSON.stringify(processedRow);
|
|
175
|
+
});
|
|
176
|
+
fs.appendFileSync(exportFile, batchContent, 'utf8');
|
|
177
|
+
isFirstBatch = false;
|
|
178
|
+
offset += batchSize;
|
|
179
|
+
console.log(`MySQL导出JSON进度: ${tableName} - 已处理 ${offset} 行`);
|
|
180
|
+
}
|
|
181
|
+
// 写入 JSON 结束
|
|
182
|
+
fs.appendFileSync(exportFile, ']', 'utf8');
|
|
183
|
+
return exportFile;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('MySQL导出JSON失败:', error);
|
|
187
|
+
throw new Error(`导出JSON失败: ${error.message}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 导出表数据到 Excel 文件
|
|
192
|
+
*/
|
|
193
|
+
async exportTableDataToExcel(dataSource, databaseName, tableName, options) {
|
|
194
|
+
try {
|
|
195
|
+
// 由于 Excel 文件需要特殊处理,这里先导出为 CSV,然后在前端转换为 Excel
|
|
196
|
+
// 或者使用专门的库生成 Excel 文件
|
|
197
|
+
// 这里暂时实现为导出为 CSV,后续可以根据需要优化
|
|
198
|
+
return this.exportTableDataToCSV(dataSource, databaseName, tableName, options);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
console.error('MySQL导出Excel失败:', error);
|
|
202
|
+
throw new Error(`导出Excel失败: ${error.message}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 获取MySQL数据库列表
|
|
207
|
+
*/
|
|
208
|
+
async getDatabases(dataSource) {
|
|
209
|
+
const result = await dataSource.query('SHOW DATABASES');
|
|
210
|
+
// 过滤掉系统数据库
|
|
211
|
+
//const systemDatabases = ['information_schema', 'performance_schema', 'mysql', 'sys'];
|
|
212
|
+
return result
|
|
213
|
+
.map((row) => row.Database);
|
|
214
|
+
//.filter((db: string) => !systemDatabases.includes(db));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 获取MySQL表列表
|
|
218
|
+
*/
|
|
219
|
+
async getTables(dataSource, database) {
|
|
220
|
+
const result = await dataSource.query(`
|
|
221
|
+
SELECT
|
|
222
|
+
TABLE_NAME as name,
|
|
223
|
+
TABLE_TYPE as type,
|
|
224
|
+
ENGINE as engine,
|
|
225
|
+
TABLE_ROWS as rowCount,
|
|
226
|
+
DATA_LENGTH as dataSize,
|
|
227
|
+
INDEX_LENGTH as indexSize,
|
|
228
|
+
TABLE_COLLATION as collation,
|
|
229
|
+
CREATE_TIME as createdAt,
|
|
230
|
+
UPDATE_TIME as updatedAt,
|
|
231
|
+
TABLE_COMMENT as comment
|
|
232
|
+
FROM information_schema.TABLES
|
|
233
|
+
WHERE TABLE_SCHEMA = ?
|
|
234
|
+
`, [database]);
|
|
235
|
+
return result.map((row) => ({
|
|
236
|
+
name: row.name,
|
|
237
|
+
type: row.type,
|
|
238
|
+
engine: row.engine,
|
|
239
|
+
rowCount: row.rowCount || 0,
|
|
240
|
+
dataSize: row.dataSize || 0,
|
|
241
|
+
indexSize: row.indexSize || 0,
|
|
242
|
+
collation: row.collation,
|
|
243
|
+
createdAt: row.createdAt,
|
|
244
|
+
updatedAt: row.updatedAt,
|
|
245
|
+
comment: row.comment
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* 获取MySQL列信息
|
|
250
|
+
*/
|
|
251
|
+
async getColumns(dataSource, database, table) {
|
|
252
|
+
// 使用兼容的SQL查询,避免使用某些MySQL版本不支持的NUMERIC_PRECISION和NUMERIC_SCALE
|
|
253
|
+
const result = await dataSource.query(`
|
|
254
|
+
SELECT
|
|
255
|
+
COLUMN_NAME as name,
|
|
256
|
+
COLUMN_TYPE as type,
|
|
257
|
+
IS_NULLABLE as nullable,
|
|
258
|
+
COLUMN_DEFAULT as defaultValue,
|
|
259
|
+
COLUMN_KEY as columnKey,
|
|
260
|
+
EXTRA as extra,
|
|
261
|
+
CHARACTER_MAXIMUM_LENGTH as length,
|
|
262
|
+
CHARACTER_SET_NAME as charset,
|
|
263
|
+
COLLATION_NAME as collation,
|
|
264
|
+
COLUMN_COMMENT as comment
|
|
265
|
+
FROM information_schema.COLUMNS
|
|
266
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
267
|
+
`, [database, table]);
|
|
268
|
+
return result.map((row) => {
|
|
269
|
+
// 从COLUMN_TYPE中解析精度信息
|
|
270
|
+
const columnType = row.type || '';
|
|
271
|
+
let precision = undefined;
|
|
272
|
+
let scale = undefined;
|
|
273
|
+
// 解析DECIMAL(M,D)或NUMERIC(M,D)类型的精度
|
|
274
|
+
const decimalMatch = columnType.match(/(DECIMAL|NUMERIC)\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)/i);
|
|
275
|
+
if (decimalMatch) {
|
|
276
|
+
precision = parseInt(decimalMatch[2]);
|
|
277
|
+
scale = parseInt(decimalMatch[3]);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
name: row.name,
|
|
281
|
+
type: row.type,
|
|
282
|
+
nullable: row.nullable === 'YES',
|
|
283
|
+
defaultValue: row.defaultValue,
|
|
284
|
+
isPrimary: row.columnKey === 'PRI',
|
|
285
|
+
isAutoIncrement: row.extra?.includes('auto_increment') || false,
|
|
286
|
+
length: row.length,
|
|
287
|
+
precision: precision,
|
|
288
|
+
scale: scale,
|
|
289
|
+
charset: row.charset,
|
|
290
|
+
collation: row.collation,
|
|
291
|
+
comment: row.comment
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* 获取MySQL索引信息
|
|
297
|
+
*/
|
|
298
|
+
async getIndexes(dataSource, database, table) {
|
|
299
|
+
// 使用更兼容的SQL查询,避免使用保留关键字作为别名
|
|
300
|
+
const result = await dataSource.query(`
|
|
301
|
+
SELECT
|
|
302
|
+
INDEX_NAME as name,
|
|
303
|
+
INDEX_TYPE as type,
|
|
304
|
+
COLUMN_NAME as columnName
|
|
305
|
+
FROM information_schema.STATISTICS
|
|
306
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
307
|
+
ORDER BY INDEX_NAME, SEQ_IN_INDEX
|
|
308
|
+
`, [database, table]);
|
|
309
|
+
// 按索引名分组并判断唯一性
|
|
310
|
+
const indexMap = new Map();
|
|
311
|
+
result.forEach((row) => {
|
|
312
|
+
if (!indexMap.has(row.name)) {
|
|
313
|
+
// 主键索引是唯一的,其他索引需要通过SHOW INDEX查询确定
|
|
314
|
+
let isUnique = false;
|
|
315
|
+
if (row.name === 'PRIMARY') {
|
|
316
|
+
isUnique = true;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// 对于非主键索引,使用更简单的方法:检查索引名称是否包含UNIQUE关键字
|
|
320
|
+
isUnique = row.name.toUpperCase().includes('UNIQUE') || row.type === 'UNIQUE';
|
|
321
|
+
}
|
|
322
|
+
indexMap.set(row.name, {
|
|
323
|
+
name: row.name,
|
|
324
|
+
type: row.type,
|
|
325
|
+
columns: [],
|
|
326
|
+
unique: isUnique
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
indexMap.get(row.name).columns.push(row.columnName);
|
|
330
|
+
});
|
|
331
|
+
return Array.from(indexMap.values());
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* 获取MySQL外键信息
|
|
335
|
+
*/
|
|
336
|
+
async getForeignKeys(dataSource, database, table) {
|
|
337
|
+
const result = await dataSource.query(`
|
|
338
|
+
SELECT
|
|
339
|
+
kcu.CONSTRAINT_NAME as name,
|
|
340
|
+
kcu.COLUMN_NAME as columnName,
|
|
341
|
+
kcu.REFERENCED_TABLE_NAME as referencedTable,
|
|
342
|
+
kcu.REFERENCED_COLUMN_NAME as referencedColumn,
|
|
343
|
+
rc.DELETE_RULE as onDelete,
|
|
344
|
+
rc.UPDATE_RULE as onUpdate
|
|
345
|
+
FROM information_schema.KEY_COLUMN_USAGE kcu
|
|
346
|
+
LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS rc
|
|
347
|
+
ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
|
348
|
+
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
|
349
|
+
WHERE kcu.TABLE_SCHEMA = ?
|
|
350
|
+
AND kcu.TABLE_NAME = ?
|
|
351
|
+
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
|
|
352
|
+
`, [database, table]);
|
|
353
|
+
return result.map((row) => ({
|
|
354
|
+
name: row.name,
|
|
355
|
+
column: row.columnName,
|
|
356
|
+
referencedTable: row.referencedTable,
|
|
357
|
+
referencedColumn: row.referencedColumn,
|
|
358
|
+
onDelete: row.onDelete || 'NO ACTION',
|
|
359
|
+
onUpdate: row.onUpdate || 'NO ACTION'
|
|
360
|
+
}));
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 获取MySQL数据库大小
|
|
364
|
+
*/
|
|
365
|
+
async getDatabaseSize(dataSource, database) {
|
|
366
|
+
const result = await dataSource.query(`
|
|
367
|
+
SELECT SUM(data_length + index_length) as size
|
|
368
|
+
FROM information_schema.tables
|
|
369
|
+
WHERE table_schema = ?
|
|
370
|
+
`, [database]);
|
|
371
|
+
return result[0]?.size || 0;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* MySQL使用反引号标识符
|
|
375
|
+
*/
|
|
376
|
+
quoteIdentifier(identifier) {
|
|
377
|
+
return `\`${identifier}\``;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 获取MySQL视图列表
|
|
381
|
+
*/
|
|
382
|
+
async getViews(dataSource, database) {
|
|
383
|
+
// 完全移除TABLE_COMMENT字段,因为某些MySQL版本中不存在该字段
|
|
384
|
+
const result = await dataSource.query(`
|
|
385
|
+
SELECT
|
|
386
|
+
TABLE_NAME as name,
|
|
387
|
+
TABLE_SCHEMA as schemaName
|
|
388
|
+
FROM information_schema.VIEWS
|
|
389
|
+
WHERE TABLE_SCHEMA = ?
|
|
390
|
+
ORDER BY TABLE_NAME
|
|
391
|
+
`, [database]);
|
|
392
|
+
return result.map((row) => ({
|
|
393
|
+
name: row.name,
|
|
394
|
+
comment: '',
|
|
395
|
+
schemaName: row.schemaName
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 获取MySQL视图定义
|
|
400
|
+
*/
|
|
401
|
+
async getViewDefinition(dataSource, database, viewName) {
|
|
402
|
+
const result = await dataSource.query(`
|
|
403
|
+
SELECT VIEW_DEFINITION as definition
|
|
404
|
+
FROM information_schema.VIEWS
|
|
405
|
+
WHERE TABLE_SCHEMA = ?
|
|
406
|
+
AND TABLE_NAME = ?
|
|
407
|
+
`, [database, viewName]);
|
|
408
|
+
return result[0]?.definition || '';
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 获取MySQL存储过程列表
|
|
412
|
+
*/
|
|
413
|
+
async getProcedures(dataSource, database) {
|
|
414
|
+
const result = await dataSource.query(`
|
|
415
|
+
SELECT
|
|
416
|
+
ROUTINE_NAME as name,
|
|
417
|
+
ROUTINE_TYPE as type,
|
|
418
|
+
ROUTINE_COMMENT as comment
|
|
419
|
+
FROM information_schema.ROUTINES
|
|
420
|
+
WHERE ROUTINE_SCHEMA = ?
|
|
421
|
+
ORDER BY ROUTINE_NAME
|
|
422
|
+
`, [database]);
|
|
423
|
+
return result.map((row) => ({
|
|
424
|
+
name: row.name,
|
|
425
|
+
comment: row.comment || '',
|
|
426
|
+
type: row.type,
|
|
427
|
+
returnType: '',
|
|
428
|
+
language: 'SQL'
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 获取MySQL存储过程定义
|
|
433
|
+
*/
|
|
434
|
+
async getProcedureDefinition(dataSource, database, procedureName) {
|
|
435
|
+
const result = await dataSource.query(`
|
|
436
|
+
SELECT ROUTINE_DEFINITION as definition
|
|
437
|
+
FROM information_schema.ROUTINES
|
|
438
|
+
WHERE ROUTINE_SCHEMA = ?
|
|
439
|
+
AND ROUTINE_NAME = ?
|
|
440
|
+
`, [database, procedureName]);
|
|
441
|
+
return result[0]?.definition || '';
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* 创建MySQL数据库
|
|
445
|
+
*/
|
|
446
|
+
async createDatabase(dataSource, databaseName, options) {
|
|
447
|
+
let sql = `CREATE DATABASE ${this.quoteIdentifier(databaseName)}`;
|
|
448
|
+
if (options) {
|
|
449
|
+
const clauses = [];
|
|
450
|
+
if (options.charset) {
|
|
451
|
+
clauses.push(`CHARACTER SET ${options.charset}`);
|
|
452
|
+
}
|
|
453
|
+
if (options.collation) {
|
|
454
|
+
clauses.push(`COLLATE ${options.collation}`);
|
|
455
|
+
}
|
|
456
|
+
if (clauses.length > 0) {
|
|
457
|
+
sql += ' ' + clauses.join(' ');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
await dataSource.query(sql);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* 删除MySQL数据库
|
|
464
|
+
*/
|
|
465
|
+
async dropDatabase(dataSource, databaseName) {
|
|
466
|
+
const sql = `DROP DATABASE ${this.quoteIdentifier(databaseName)}`;
|
|
467
|
+
await dataSource.query(sql);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* 导出数据库架构
|
|
471
|
+
*/
|
|
472
|
+
async exportSchema(dataSource, databaseName) {
|
|
473
|
+
// 获取所有表
|
|
474
|
+
const tables = await this.getTables(dataSource, databaseName);
|
|
475
|
+
let schemaSql = `-- 数据库架构导出 - ${databaseName}\n`;
|
|
476
|
+
schemaSql += `-- 导出时间: ${new Date().toISOString()}\n\n`;
|
|
477
|
+
// 为每个表生成CREATE TABLE语句
|
|
478
|
+
for (const table of tables) {
|
|
479
|
+
// 获取表结构
|
|
480
|
+
const columns = await this.getColumns(dataSource, databaseName, table.name);
|
|
481
|
+
const indexes = await this.getIndexes(dataSource, databaseName, table.name);
|
|
482
|
+
const foreignKeys = await this.getForeignKeys(dataSource, databaseName, table.name);
|
|
483
|
+
// 生成CREATE TABLE语句
|
|
484
|
+
schemaSql += `-- 表结构: ${table.name}\n`;
|
|
485
|
+
schemaSql += `CREATE TABLE IF NOT EXISTS \`${table.name}\` (\n`;
|
|
486
|
+
// 添加列定义
|
|
487
|
+
const columnDefinitions = columns.map(column => {
|
|
488
|
+
let definition = ` \`${column.name}\` ${column.type}`;
|
|
489
|
+
if (!column.nullable)
|
|
490
|
+
definition += ' NOT NULL';
|
|
491
|
+
if (column.defaultValue !== undefined) {
|
|
492
|
+
// 特殊关键字处理
|
|
493
|
+
const upperDefault = column.defaultValue.toString().toUpperCase();
|
|
494
|
+
if (upperDefault === 'CURRENT_TIMESTAMP' || upperDefault === 'NOW()' || upperDefault === 'CURRENT_DATE') {
|
|
495
|
+
definition += ` DEFAULT ${upperDefault}`;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
definition += ` DEFAULT ${column.defaultValue === null ? 'NULL' : `'${column.defaultValue}'`}`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (column.isAutoIncrement)
|
|
502
|
+
definition += ' AUTO_INCREMENT';
|
|
503
|
+
return definition;
|
|
504
|
+
});
|
|
505
|
+
// 添加主键
|
|
506
|
+
const primaryKeyColumns = columns.filter(column => column.isPrimary);
|
|
507
|
+
if (primaryKeyColumns.length > 0) {
|
|
508
|
+
const primaryKeyNames = primaryKeyColumns.map(column => `\`${column.name}\``).join(', ');
|
|
509
|
+
columnDefinitions.push(` PRIMARY KEY (${primaryKeyNames})`);
|
|
510
|
+
}
|
|
511
|
+
schemaSql += columnDefinitions.join(',\n');
|
|
512
|
+
schemaSql += '\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n\n';
|
|
513
|
+
// 添加索引
|
|
514
|
+
for (const index of indexes) {
|
|
515
|
+
if (index.type === 'PRIMARY' || index.name.toUpperCase() === 'PRIMARY')
|
|
516
|
+
continue; // 跳过主键索引
|
|
517
|
+
schemaSql += `-- 索引: ${index.name} on ${table.name}\n`;
|
|
518
|
+
schemaSql += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX \`${index.name}\` ON \`${table.name}\` (${index.columns.map(col => `\`${col}\``).join(', ')})\n`;
|
|
519
|
+
}
|
|
520
|
+
if (indexes.length > 0)
|
|
521
|
+
schemaSql += '\n';
|
|
522
|
+
// 添加外键
|
|
523
|
+
for (const foreignKey of foreignKeys) {
|
|
524
|
+
schemaSql += `-- 外键: ${foreignKey.name} on ${table.name}\n`;
|
|
525
|
+
schemaSql += `ALTER TABLE \`${table.name}\` ADD CONSTRAINT \`${foreignKey.name}\` FOREIGN KEY (\`${foreignKey.column}\`) REFERENCES \`${foreignKey.referencedTable}\` (\`${foreignKey.referencedColumn}\`)${foreignKey.onDelete ? ` ON DELETE ${foreignKey.onDelete}` : ''}${foreignKey.onUpdate ? ` ON UPDATE ${foreignKey.onUpdate}` : ''};\n`;
|
|
526
|
+
}
|
|
527
|
+
if (foreignKeys.length > 0)
|
|
528
|
+
schemaSql += '\n';
|
|
529
|
+
}
|
|
530
|
+
return schemaSql;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* 查看数据库日志
|
|
534
|
+
*/
|
|
535
|
+
async viewLogs(dataSource, database, limit = 100) {
|
|
536
|
+
// MySQL查看错误日志
|
|
537
|
+
try {
|
|
538
|
+
const logs = await dataSource.query(`SHOW ERROR LOGS LIMIT ${limit}`);
|
|
539
|
+
return logs;
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
// 如果SHOW ERROR LOGS失败,尝试其他方式
|
|
543
|
+
try {
|
|
544
|
+
// 尝试查看通用日志
|
|
545
|
+
const logs = await dataSource.query(`SHOW GLOBAL VARIABLES LIKE '%log%'`);
|
|
546
|
+
return logs;
|
|
547
|
+
}
|
|
548
|
+
catch (e) {
|
|
549
|
+
return [{ message: '无法获取MySQL日志,请确保具有适当的权限' }];
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 备份数据库
|
|
555
|
+
*/
|
|
556
|
+
async backupDatabase(dataSource, databaseName, options) {
|
|
557
|
+
// MySQL备份数据库
|
|
558
|
+
try {
|
|
559
|
+
// 使用mysqldump命令备份
|
|
560
|
+
const backupPath = options?.path || path.join(__dirname, '..', '..', 'backups');
|
|
561
|
+
// 确保备份目录存在
|
|
562
|
+
if (!fs.existsSync(backupPath)) {
|
|
563
|
+
fs.mkdirSync(backupPath, { recursive: true });
|
|
564
|
+
}
|
|
565
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
566
|
+
const backupFile = path.join(backupPath, `${databaseName}_${timestamp}.sql`);
|
|
567
|
+
// 执行备份命令
|
|
568
|
+
const connectionOptions = dataSource.options;
|
|
569
|
+
const host = connectionOptions.host || 'localhost';
|
|
570
|
+
const port = connectionOptions.port || 3306;
|
|
571
|
+
const user = connectionOptions.username;
|
|
572
|
+
const password = connectionOptions.password;
|
|
573
|
+
// 构建mysqldump命令
|
|
574
|
+
let command = `mysqldump -h ${host} -P ${port} -u ${user}`;
|
|
575
|
+
if (password) {
|
|
576
|
+
command += ` -p${password}`;
|
|
577
|
+
}
|
|
578
|
+
command += ` ${databaseName} > ${backupFile}`;
|
|
579
|
+
// 执行命令
|
|
580
|
+
(0, child_process_1.execSync)(command);
|
|
581
|
+
return `备份成功:${backupFile}`;
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
console.error('MySQL备份失败:', error);
|
|
585
|
+
throw new Error(`备份失败: ${error.message}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* 恢复数据库
|
|
590
|
+
*/
|
|
591
|
+
async restoreDatabase(dataSource, databaseName, filePath, options) {
|
|
592
|
+
// MySQL恢复数据库
|
|
593
|
+
try {
|
|
594
|
+
// 执行恢复命令
|
|
595
|
+
const connectionOptions = dataSource.options;
|
|
596
|
+
const host = connectionOptions.host || 'localhost';
|
|
597
|
+
const port = connectionOptions.port || 3306;
|
|
598
|
+
const user = connectionOptions.username;
|
|
599
|
+
const password = connectionOptions.password;
|
|
600
|
+
// 构建mysql命令
|
|
601
|
+
let command = `mysql -h ${host} -P ${port} -u ${user}`;
|
|
602
|
+
if (password) {
|
|
603
|
+
command += ` -p${password}`;
|
|
604
|
+
}
|
|
605
|
+
command += ` ${databaseName} < ${filePath}`;
|
|
606
|
+
// 执行命令
|
|
607
|
+
(0, child_process_1.execSync)(command);
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
console.error('MySQL恢复失败:', error);
|
|
611
|
+
throw new Error(`恢复失败: ${error.message}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 导出表数据到 SQL 文件
|
|
616
|
+
*/
|
|
617
|
+
async exportTableDataToSQL(dataSource, databaseName, tableName, options) {
|
|
618
|
+
try {
|
|
619
|
+
// 创建导出目录
|
|
620
|
+
const exportPath = options?.path || path.join(__dirname, '..', '..', '..', 'data', 'exports');
|
|
621
|
+
if (!fs.existsSync(exportPath)) {
|
|
622
|
+
fs.mkdirSync(exportPath, { recursive: true });
|
|
623
|
+
}
|
|
624
|
+
// 生成文件名
|
|
625
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
626
|
+
const exportFile = path.join(exportPath, `${tableName}_data_${timestamp}.sql`);
|
|
627
|
+
// 获取表结构
|
|
628
|
+
const columns = await this.getColumns(dataSource, databaseName, tableName);
|
|
629
|
+
const columnNames = columns.map(column => column.name);
|
|
630
|
+
// 生成文件头部
|
|
631
|
+
const header = `-- 表数据导出 - ${tableName}\n` +
|
|
632
|
+
`-- 导出时间: ${new Date().toISOString()}\n\n`;
|
|
633
|
+
fs.writeFileSync(exportFile, header, 'utf8');
|
|
634
|
+
// 分批处理数据,避免一次性加载大量数据到内存
|
|
635
|
+
const batchSize = options?.batchSize || 10000; // 每批处理10000行
|
|
636
|
+
let offset = 0;
|
|
637
|
+
let hasMoreData = true;
|
|
638
|
+
while (hasMoreData) {
|
|
639
|
+
// 分批查询数据
|
|
640
|
+
const query = `SELECT * FROM ${this.quoteIdentifier(tableName)} LIMIT ${batchSize} OFFSET ${offset}`;
|
|
641
|
+
const data = await dataSource.query(query);
|
|
642
|
+
if (data.length === 0) {
|
|
643
|
+
hasMoreData = false;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
// 生成当前批次的 INSERT 语句
|
|
647
|
+
let batchSql = '';
|
|
648
|
+
data.forEach((row) => {
|
|
649
|
+
const values = columnNames.map(column => {
|
|
650
|
+
const value = row[column];
|
|
651
|
+
if (value === null || value === undefined) {
|
|
652
|
+
return 'NULL';
|
|
653
|
+
}
|
|
654
|
+
else if (typeof value === 'string') {
|
|
655
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
656
|
+
}
|
|
657
|
+
else if (typeof value === 'boolean') {
|
|
658
|
+
return value ? '1' : '0';
|
|
659
|
+
}
|
|
660
|
+
else if (value instanceof Date) {
|
|
661
|
+
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
|
|
662
|
+
}
|
|
663
|
+
else if (typeof value === 'object') {
|
|
664
|
+
try {
|
|
665
|
+
// 递归处理对象,确保所有嵌套的JSON字符串都被正确转义
|
|
666
|
+
const processValue = (val) => {
|
|
667
|
+
if (val === null || val === undefined) {
|
|
668
|
+
return val;
|
|
669
|
+
}
|
|
670
|
+
else if (typeof val === 'string') {
|
|
671
|
+
// 检查是否是JSON字符串
|
|
672
|
+
try {
|
|
673
|
+
const parsed = JSON.parse(val);
|
|
674
|
+
// 如果是JSON字符串,递归处理
|
|
675
|
+
if (typeof parsed === 'object') {
|
|
676
|
+
return processValue(parsed);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
// 不是JSON字符串,直接返回
|
|
681
|
+
}
|
|
682
|
+
return val;
|
|
683
|
+
}
|
|
684
|
+
else if (typeof val === 'object') {
|
|
685
|
+
if (Array.isArray(val)) {
|
|
686
|
+
return val.map(processValue);
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
const processedObj = {};
|
|
690
|
+
for (const key in val) {
|
|
691
|
+
processedObj[key] = processValue(val[key]);
|
|
692
|
+
}
|
|
693
|
+
return processedObj;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
return val;
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
// 处理对象
|
|
701
|
+
const processedValue = processValue(value);
|
|
702
|
+
// 序列化处理后的对象
|
|
703
|
+
let stringValue = JSON.stringify(processedValue);
|
|
704
|
+
// 对JSON字符串中的单引号进行转义,确保在SQL中正确处理
|
|
705
|
+
stringValue = stringValue.replace(/'/g, "''");
|
|
706
|
+
// 返回用单引号包裹的字符串
|
|
707
|
+
return `'${stringValue}'`;
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
return String(value);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
batchSql += `INSERT INTO ${this.quoteIdentifier(tableName)} (${columnNames.map(col => this.quoteIdentifier(col)).join(', ')}) VALUES (${values.join(', ')});\n`;
|
|
718
|
+
});
|
|
719
|
+
// 追加写入文件
|
|
720
|
+
fs.appendFileSync(exportFile, batchSql, 'utf8');
|
|
721
|
+
// 增加偏移量
|
|
722
|
+
offset += batchSize;
|
|
723
|
+
// 打印进度信息
|
|
724
|
+
console.log(`MySQL导出表数据进度: ${tableName} - 已处理 ${offset} 行`);
|
|
725
|
+
}
|
|
726
|
+
return exportFile;
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
console.error('MySQL导出表数据失败:', error);
|
|
730
|
+
throw new Error(`导出表数据失败: ${error.message}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
exports.MySQLService = MySQLService;
|
|
735
|
+
//# sourceMappingURL=mysql.service.js.map
|