fdb2 1.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.
Files changed (125) hide show
  1. package/.dockerignore +21 -0
  2. package/.editorconfig +11 -0
  3. package/.eslintrc.cjs +14 -0
  4. package/.eslintrc.json +7 -0
  5. package/.prettierrc.js +3 -0
  6. package/.tpl.env +22 -0
  7. package/README.md +260 -0
  8. package/bin/build.sh +28 -0
  9. package/bin/deploy.sh +8 -0
  10. package/bin/dev.sh +10 -0
  11. package/bin/docker/.env +4 -0
  12. package/bin/docker/dev-docker-compose.yml +43 -0
  13. package/bin/docker/dev.Dockerfile +24 -0
  14. package/bin/docker/prod-docker-compose.yml +17 -0
  15. package/bin/docker/prod.Dockerfile +29 -0
  16. package/bin/fdb2.js +142 -0
  17. package/data/connections.demo.json +32 -0
  18. package/env.d.ts +1 -0
  19. package/nw-build.js +120 -0
  20. package/nw-dev.js +65 -0
  21. package/package.json +114 -0
  22. package/public/favicon.ico +0 -0
  23. package/public/index.html +9 -0
  24. package/public/modules/header.tpl +14 -0
  25. package/public/modules/initial_state.tpl +55 -0
  26. package/server/index.ts +677 -0
  27. package/server/model/connection.entity.ts +66 -0
  28. package/server/model/database.entity.ts +246 -0
  29. package/server/service/connection.service.ts +334 -0
  30. package/server/service/database/base.service.ts +363 -0
  31. package/server/service/database/database.service.ts +510 -0
  32. package/server/service/database/index.ts +7 -0
  33. package/server/service/database/mssql.service.ts +723 -0
  34. package/server/service/database/mysql.service.ts +761 -0
  35. package/server/service/database/oracle.service.ts +839 -0
  36. package/server/service/database/postgres.service.ts +744 -0
  37. package/server/service/database/sqlite.service.ts +559 -0
  38. package/server/service/session.service.ts +158 -0
  39. package/server.js +128 -0
  40. package/src/adapter/ajax.ts +135 -0
  41. package/src/assets/base.css +1 -0
  42. package/src/assets/database.css +950 -0
  43. package/src/assets/images/collapse.png +0 -0
  44. package/src/assets/images/no-login.png +0 -0
  45. package/src/assets/images/svg/illustrations/illustration-1.svg +1 -0
  46. package/src/assets/images/svg/illustrations/illustration-2.svg +2 -0
  47. package/src/assets/images/svg/illustrations/illustration-3.svg +50 -0
  48. package/src/assets/images/svg/illustrations/illustration-4.svg +1 -0
  49. package/src/assets/images/svg/illustrations/illustration-5.svg +73 -0
  50. package/src/assets/images/svg/illustrations/illustration-6.svg +89 -0
  51. package/src/assets/images/svg/illustrations/illustration-7.svg +39 -0
  52. package/src/assets/images/svg/illustrations/illustration-8.svg +1 -0
  53. package/src/assets/images/svg/separators/curve-2.svg +3 -0
  54. package/src/assets/images/svg/separators/curve.svg +3 -0
  55. package/src/assets/images/svg/separators/line.svg +3 -0
  56. package/src/assets/images/theme/light/screen-1-1000x800.jpg +0 -0
  57. package/src/assets/images/theme/light/screen-2-1000x800.jpg +0 -0
  58. package/src/assets/login/bg.jpg +0 -0
  59. package/src/assets/login/bg.png +0 -0
  60. package/src/assets/login/left.jpg +0 -0
  61. package/src/assets/logo.svg +73 -0
  62. package/src/assets/logo.webp +0 -0
  63. package/src/assets/main.css +1 -0
  64. package/src/base/config.ts +20 -0
  65. package/src/base/detect.ts +134 -0
  66. package/src/base/entity.ts +92 -0
  67. package/src/base/eventBus.ts +37 -0
  68. package/src/base//345/237/272/347/241/200/345/261/202.md +7 -0
  69. package/src/components/connection-editor/index.vue +590 -0
  70. package/src/components/dataGrid/index.vue +105 -0
  71. package/src/components/dataGrid/pagination.vue +106 -0
  72. package/src/components/loading/index.vue +43 -0
  73. package/src/components/modal/index.ts +181 -0
  74. package/src/components/modal/index.vue +560 -0
  75. package/src/components/toast/index.ts +44 -0
  76. package/src/components/toast/toast.vue +58 -0
  77. package/src/components/user/name.vue +104 -0
  78. package/src/components/user/selector.vue +416 -0
  79. package/src/domain/SysConfig.ts +74 -0
  80. package/src/platform/App.vue +8 -0
  81. package/src/platform/database/components/connection-detail.vue +1154 -0
  82. package/src/platform/database/components/data-editor.vue +478 -0
  83. package/src/platform/database/components/data-import-export.vue +1602 -0
  84. package/src/platform/database/components/database-detail.vue +1173 -0
  85. package/src/platform/database/components/database-monitor.vue +1086 -0
  86. package/src/platform/database/components/db-tools.vue +577 -0
  87. package/src/platform/database/components/query-history.vue +1349 -0
  88. package/src/platform/database/components/sql-executor.vue +738 -0
  89. package/src/platform/database/components/sql-query-editor.vue +1046 -0
  90. package/src/platform/database/components/table-detail.vue +1376 -0
  91. package/src/platform/database/components/table-editor.vue +690 -0
  92. package/src/platform/database/explorer.vue +1840 -0
  93. package/src/platform/database/index.vue +1193 -0
  94. package/src/platform/database/layout.vue +367 -0
  95. package/src/platform/database/router.ts +37 -0
  96. package/src/platform/database/styles/common.scss +602 -0
  97. package/src/platform/database/types/common.ts +445 -0
  98. package/src/platform/database/utils/export.ts +232 -0
  99. package/src/platform/database/utils/helpers.ts +437 -0
  100. package/src/platform/index.ts +33 -0
  101. package/src/platform/router.ts +41 -0
  102. package/src/service/base.ts +128 -0
  103. package/src/service/database.ts +500 -0
  104. package/src/service/login.ts +121 -0
  105. package/src/shims-vue.d.ts +7 -0
  106. package/src/stores/connection.ts +266 -0
  107. package/src/stores/session.ts +87 -0
  108. package/src/typings/database-types.ts +413 -0
  109. package/src/typings/database.ts +364 -0
  110. package/src/typings/global.d.ts +58 -0
  111. package/src/typings/pinia.d.ts +8 -0
  112. package/src/utils/clipboard.ts +30 -0
  113. package/src/utils/database-types.ts +243 -0
  114. package/src/utils/modal.ts +124 -0
  115. package/src/utils/request.ts +55 -0
  116. package/src/utils/sleep.ts +4 -0
  117. package/src/utils/toast.ts +73 -0
  118. package/src/utils/util.ts +171 -0
  119. package/src/utils/xlsx.ts +228 -0
  120. package/tsconfig.json +33 -0
  121. package/tsconfig.server.json +19 -0
  122. package/view/index.html +9 -0
  123. package/view/modules/header.tpl +14 -0
  124. package/view/modules/initial_state.tpl +20 -0
  125. package/vite.config.ts +384 -0
@@ -0,0 +1,66 @@
1
+
2
+ /**
3
+ * 数据库连接配置实体
4
+ * 注意:此实体仅用于代码结构,实际数据保存在本地JSON文件中
5
+ */
6
+ export class ConnectionEntity {
7
+ /**
8
+ * 连接ID
9
+ */
10
+ id: string;
11
+
12
+ /**
13
+ * 连接名称
14
+ */
15
+ name: string;
16
+
17
+ /**
18
+ * 数据库类型 (mysql, postgresql, sqlite, etc.)
19
+ */
20
+ type: string;
21
+
22
+ /**
23
+ * 主机地址
24
+ */
25
+ host: string;
26
+
27
+ /**
28
+ * 端口号
29
+ */
30
+ port: number;
31
+
32
+ /**
33
+ * 数据库名称
34
+ */
35
+ database: string;
36
+
37
+ /**
38
+ * 用户名
39
+ */
40
+ username: string;
41
+
42
+ /**
43
+ * 密码 (加密存储)
44
+ */
45
+ password: string;
46
+
47
+ /**
48
+ * 连接参数 (SSL等)
49
+ */
50
+ options: Record<string, any>;
51
+
52
+ /**
53
+ * 是否启用
54
+ */
55
+ enabled: boolean;
56
+
57
+ /**
58
+ * 创建时间
59
+ */
60
+ createdAt: Date;
61
+
62
+ /**
63
+ * 更新时间
64
+ */
65
+ updatedAt: Date;
66
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * 数据库信息实体
3
+ * 用于存储数据库的基本信息和结构
4
+ */
5
+
6
+ export class DatabaseEntity {
7
+ /**
8
+ * 数据库名称
9
+ */
10
+ name: string;
11
+
12
+ /**
13
+ * 数据库字符集
14
+ */
15
+ charset?: string;
16
+
17
+ /**
18
+ * 数据库排序规则
19
+ */
20
+ collation?: string;
21
+
22
+ /**
23
+ * 表数量
24
+ */
25
+ tableCount: number;
26
+
27
+ /**
28
+ * 数据库大小 (字节)
29
+ */
30
+ size: number;
31
+
32
+ /**
33
+ * 创建时间
34
+ */
35
+ createdAt?: Date;
36
+
37
+ /**
38
+ * 表信息列表
39
+ */
40
+ tables?: TableEntity[];
41
+ }
42
+
43
+ /**
44
+ * 数据库表实体
45
+ */
46
+ export class TableEntity {
47
+ /**
48
+ * 表名
49
+ */
50
+ name: string;
51
+
52
+ /**
53
+ * 表类型
54
+ */
55
+ type?: string;
56
+
57
+ /**
58
+ * 引擎 (MySQL)
59
+ */
60
+ engine?: string;
61
+
62
+ /**
63
+ * 行数
64
+ */
65
+ rowCount: number;
66
+
67
+ /**
68
+ * 数据大小 (字节)
69
+ */
70
+ dataSize: number;
71
+
72
+ /**
73
+ * 索引大小 (字节)
74
+ */
75
+ indexSize: number;
76
+
77
+ /**
78
+ * 字符集
79
+ */
80
+ charset?: string;
81
+
82
+ /**
83
+ * 排序规则
84
+ */
85
+ collation?: string;
86
+
87
+ /**
88
+ * 创建时间
89
+ */
90
+ createdAt?: Date;
91
+
92
+ /**
93
+ * 更新时间
94
+ */
95
+ updatedAt?: Date;
96
+
97
+ /**
98
+ * 注释
99
+ */
100
+ comment?: string;
101
+
102
+ /**
103
+ * 列信息
104
+ */
105
+ columns?: ColumnEntity[];
106
+
107
+ /**
108
+ * 索引信息
109
+ */
110
+ indexes?: IndexEntity[];
111
+
112
+ /**
113
+ * 外键信息
114
+ */
115
+ foreignKeys?: ForeignKeyEntity[];
116
+ }
117
+
118
+ /**
119
+ * 数据库列实体
120
+ */
121
+ export class ColumnEntity {
122
+ /**
123
+ * 列名
124
+ */
125
+ name: string;
126
+
127
+ /**
128
+ * 数据类型
129
+ */
130
+ type: string;
131
+
132
+ /**
133
+ * 是否允许NULL
134
+ */
135
+ nullable: boolean;
136
+
137
+ /**
138
+ * 默认值
139
+ */
140
+ defaultValue?: any;
141
+
142
+ /**
143
+ * 是否主键
144
+ */
145
+ isPrimary: boolean;
146
+
147
+ /**
148
+ * 是否自增
149
+ */
150
+ isAutoIncrement: boolean;
151
+
152
+ /**
153
+ * 字符长度
154
+ */
155
+ length?: number;
156
+
157
+ /**
158
+ * 小数位数
159
+ */
160
+ precision?: number;
161
+
162
+ /**
163
+ * 小数点后位数
164
+ */
165
+ scale?: number;
166
+
167
+ /**
168
+ * 字符集
169
+ */
170
+ charset?: string;
171
+
172
+ /**
173
+ * 排序规则
174
+ */
175
+ collation?: string;
176
+
177
+ /**
178
+ * 注释
179
+ */
180
+ comment?: string;
181
+ }
182
+
183
+ /**
184
+ * 数据库索引实体
185
+ */
186
+ export class IndexEntity {
187
+ /**
188
+ * 索引名
189
+ */
190
+ name: string;
191
+
192
+ /**
193
+ * 索引类型 (PRIMARY, UNIQUE, INDEX, FULLTEXT)
194
+ */
195
+ type: string;
196
+
197
+ /**
198
+ * 索引列
199
+ */
200
+ columns: string[];
201
+
202
+ /**
203
+ * 是否唯一
204
+ */
205
+ unique: boolean;
206
+
207
+ /**
208
+ * 注释
209
+ */
210
+ comment?: string;
211
+ }
212
+
213
+ /**
214
+ * 外键实体
215
+ */
216
+ export class ForeignKeyEntity {
217
+ /**
218
+ * 约束名
219
+ */
220
+ name: string;
221
+
222
+ /**
223
+ * 本地表列
224
+ */
225
+ column: string;
226
+
227
+ /**
228
+ * 目标表
229
+ */
230
+ referencedTable: string;
231
+
232
+ /**
233
+ * 目标表列
234
+ */
235
+ referencedColumn: string;
236
+
237
+ /**
238
+ * 删除规则 (CASCADE, SET NULL, RESTRICT, NO ACTION)
239
+ */
240
+ onDelete: string;
241
+
242
+ /**
243
+ * 更新规则 (CASCADE, SET NULL, RESTRICT, NO ACTION)
244
+ */
245
+ onUpdate: string;
246
+ }
@@ -0,0 +1,334 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { ConnectionEntity } from '../model/connection.entity';
4
+ import { DataSource, type DataSourceOptions } from 'typeorm';
5
+
6
+ /**
7
+ * 数据库连接管理服务
8
+ * 负责管理数据库连接配置和连接实例
9
+ */
10
+ export class ConnectionService {
11
+
12
+ /**
13
+ * 连接配置文件路径
14
+ */
15
+ private readonly configPath = path.join(process.cwd(), 'data', 'connections.json');
16
+
17
+ /**
18
+ * 活跃的数据库连接实例
19
+ */
20
+ private activeConnections: Map<string, DataSource> = new Map();
21
+
22
+ /**
23
+ * 初始化服务,创建配置目录
24
+ */
25
+ async init() {
26
+ const configDir = path.dirname(this.configPath);
27
+ if (!fs.existsSync(configDir)) {
28
+ fs.mkdirSync(configDir, { recursive: true });
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 获取所有数据库连接配置
34
+ */
35
+ async getAllConnections(): Promise<ConnectionEntity[]> {
36
+ try {
37
+ if (!fs.existsSync(this.configPath)) {
38
+ return [];
39
+ }
40
+ const data = fs.readFileSync(this.configPath, 'utf8');
41
+ return JSON.parse(data) as ConnectionEntity[];
42
+ } catch (error) {
43
+ console.error('读取连接配置失败:', error);
44
+ return [];
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 根据ID获取数据库连接配置
50
+ */
51
+ async getConnectionById(id: string): Promise<ConnectionEntity | null> {
52
+ const connections = await this.getAllConnections();
53
+ return connections.find(conn => conn.id === id) || null;
54
+ }
55
+
56
+ /**
57
+ * 添加数据库连接配置
58
+ */
59
+ async addConnection(connection: ConnectionEntity): Promise<ConnectionEntity> {
60
+ const connections = await this.getAllConnections();
61
+
62
+ // 检查名称是否重复
63
+ if (connections.find(conn => conn.name === connection.name)) {
64
+ throw new Error('连接名称已存在');
65
+ }
66
+
67
+ // 生成ID并设置时间戳
68
+ connection.id = this.generateId();
69
+ connection.createdAt = new Date();
70
+ connection.updatedAt = new Date();
71
+ connection.enabled = connection.enabled !== undefined ? connection.enabled : true;
72
+
73
+ connections.push(connection);
74
+ await this.saveConnections(connections);
75
+
76
+ return connection;
77
+ }
78
+
79
+ /**
80
+ * 更新数据库连接配置
81
+ */
82
+ async updateConnection(id: string, updates: Partial<ConnectionEntity>): Promise<ConnectionEntity> {
83
+ const connections = await this.getAllConnections();
84
+ const index = connections.findIndex(conn => conn.id === id);
85
+
86
+ if (index === -1) {
87
+ throw new Error('连接配置不存在');
88
+ }
89
+
90
+ // 检查名称重复
91
+ if (updates.name && connections.find((conn, idx) => conn.name === updates.name && idx !== index)) {
92
+ throw new Error('连接名称已存在');
93
+ }
94
+ // @ts-ignore
95
+ connections[index] = { ...connections[index], ...updates, updatedAt: new Date() };
96
+ await this.saveConnections(connections);
97
+ // @ts-ignore
98
+ return connections[index];
99
+ }
100
+
101
+ /**
102
+ * 删除数据库连接配置
103
+ */
104
+ async deleteConnection(id: string): Promise<void> {
105
+ const connections = await this.getAllConnections();
106
+ const filteredConnections = connections.filter(conn => conn.id !== id);
107
+
108
+ if (filteredConnections.length === connections.length) {
109
+ throw new Error('连接配置不存在');
110
+ }
111
+
112
+ // 关闭活跃连接
113
+ if (this.activeConnections.has(id)) {
114
+ await this.activeConnections.get(id)?.destroy();
115
+ this.activeConnections.delete(id);
116
+ }
117
+
118
+ await this.saveConnections(filteredConnections);
119
+ }
120
+
121
+ /**
122
+ * 测试数据库连接
123
+ */
124
+ async testConnection(connection: ConnectionEntity): Promise<boolean> {
125
+ try {
126
+ console.log('test', connection);
127
+ const tempDataSource = await this.createTypeORMDataSource(connection);
128
+ await tempDataSource.query('SELECT 1');
129
+ await tempDataSource.destroy();
130
+ return true;
131
+ } catch (error) {
132
+ console.error(error);
133
+ return false;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * 获取活跃的数据库连接
139
+ */
140
+ async getActiveConnection(id: string, database?: string): Promise<DataSource> {
141
+ const key = database ? `${id}_${database}` : id;
142
+ if (this.activeConnections.has(key)) {
143
+ const db = this.activeConnections.get(key);
144
+ // 检查连接是否仍然有效
145
+ if (db?.isInitialized) {
146
+ return db;
147
+ } else {
148
+ // 连接已关闭,从缓存中移除
149
+ this.activeConnections.delete(key);
150
+ }
151
+ }
152
+
153
+ // 连接池大小限制,防止连接数无限增长
154
+ if (this.activeConnections.size >= 10) {
155
+ // 关闭最旧的连接
156
+ const oldestKey = this.activeConnections.keys().next().value || '';
157
+ const oldestConnection = this.activeConnections.get(oldestKey);
158
+ try {
159
+ await oldestConnection?.destroy();
160
+ } catch (error) {
161
+ console.error(`关闭旧连接 ${oldestKey} 失败:`, error);
162
+ }
163
+ this.activeConnections.delete(oldestKey);
164
+ }
165
+
166
+ const connectionConfig = await this.getConnectionById(id);
167
+ if (!connectionConfig) {
168
+ throw new Error('连接配置不存在');
169
+ }
170
+
171
+ // 创建一个新的连接配置,使用指定的数据库
172
+ const updatedConnectionConfig: ConnectionEntity = {
173
+ ...connectionConfig,
174
+ database: database || connectionConfig.database
175
+ };
176
+
177
+ const dataSource = await this.createTypeORMDataSource(updatedConnectionConfig);
178
+ this.activeConnections.set(key, dataSource);
179
+
180
+ return dataSource;
181
+ }
182
+
183
+ /**
184
+ * 关闭数据库连接
185
+ */
186
+ async closeConnection(id: string, database?: string): Promise<void> {
187
+ const key = database ? `${id}_${database}` : id;
188
+ if (this.activeConnections.has(key)) {
189
+ await this.activeConnections.get(key)?.destroy();
190
+ this.activeConnections.delete(key);
191
+ }
192
+ // 也关闭默认连接(如果存在)
193
+ if (database && this.activeConnections.has(id)) {
194
+ await this.activeConnections.get(id)?.destroy();
195
+ this.activeConnections.delete(id);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 关闭特定连接的所有数据库连接
201
+ */
202
+ async closeAllConnectionsForId(connectionId: string): Promise<void> {
203
+ const keysToDelete: string[] = [];
204
+ for (const [key, dataSource] of this.activeConnections) {
205
+ if (key.startsWith(connectionId + '_') || key === connectionId) {
206
+ try {
207
+ await dataSource.destroy();
208
+ keysToDelete.push(key);
209
+ } catch (error) {
210
+ console.error(`关闭连接 ${key} 失败:`, error);
211
+ }
212
+ }
213
+ }
214
+ keysToDelete.forEach(key => this.activeConnections.delete(key));
215
+ }
216
+
217
+ /**
218
+ * 关闭所有数据库连接
219
+ */
220
+ async closeAllConnections(): Promise<void> {
221
+ for (const [id, dataSource] of this.activeConnections) {
222
+ try {
223
+ await dataSource.destroy();
224
+ } catch (error) {
225
+ console.error(`关闭连接 ${id} 失败:`, error);
226
+ }
227
+ }
228
+ this.activeConnections.clear();
229
+ }
230
+
231
+ /**
232
+ * 创建TypeORM数据源
233
+ */
234
+ private async createTypeORMDataSource(connectionConfig: ConnectionEntity): Promise<DataSource> {
235
+ const connectionOptions = this.getTypeORMOptions(connectionConfig);
236
+ return new DataSource(connectionOptions).initialize();
237
+ }
238
+
239
+ /**
240
+ * 获取TypeORM连接配置
241
+ */
242
+ private getTypeORMOptions(connectionConfig: ConnectionEntity): DataSourceOptions {
243
+ const baseOptions = {
244
+ type: connectionConfig.type as any,
245
+ host: connectionConfig.host,
246
+ port: connectionConfig.port,
247
+ username: connectionConfig.username,
248
+ password: connectionConfig.password,
249
+ database: connectionConfig.database,
250
+ synchronize: false,
251
+ logging: false,
252
+ // 关键配置:开启多语句执行
253
+ extra: {
254
+ multipleStatements: true
255
+ },
256
+ ...connectionConfig.options
257
+ };
258
+
259
+ // 根据数据库类型调整配置
260
+ switch (connectionConfig.type.toLowerCase()) {
261
+ case 'sqlite':
262
+ return {
263
+ ...baseOptions,
264
+ type: 'sqlite' as any,
265
+ database: connectionConfig.database,
266
+ host: undefined,
267
+ port: undefined,
268
+ username: undefined,
269
+ password: undefined
270
+ };
271
+ case 'postgres':
272
+ case 'postgresql':
273
+ return {
274
+ ...baseOptions,
275
+ type: 'postgres' as any,
276
+ ssl: connectionConfig.options?.ssl || false
277
+ };
278
+ case 'oracle':
279
+ return {
280
+ ...baseOptions,
281
+ type: 'oracle' as any,
282
+ connectString: `${connectionConfig.host}:${connectionConfig.port}/${connectionConfig.database}`,
283
+ host: undefined,
284
+ port: undefined,
285
+ database: undefined,
286
+ extra: {
287
+ connectionTimeout: 60000,
288
+ poolMax: 10,
289
+ poolMin: 1,
290
+ poolIncrement: 1
291
+ }
292
+ };
293
+ case 'mssql':
294
+ case 'sqlserver':
295
+ return {
296
+ ...baseOptions,
297
+ type: 'mssql' as any,
298
+ options: {
299
+ encrypt: connectionConfig.options?.encrypt || false,
300
+ trustServerCertificate: true
301
+ },
302
+ extra: {
303
+ connectionTimeout: 60000,
304
+ requestTimeout: 15000
305
+ }
306
+ };
307
+ default:
308
+ return baseOptions;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * 保存连接配置到文件
314
+ */
315
+ private async saveConnections(connections: ConnectionEntity[]): Promise<void> {
316
+ try {
317
+ const dir = path.dirname(this.configPath);
318
+ if (!fs.existsSync(dir)) {
319
+ fs.mkdirSync(dir, { recursive: true });
320
+ }
321
+ fs.writeFileSync(this.configPath, JSON.stringify(connections, null, 2), 'utf8');
322
+ } catch (error) {
323
+ console.error('保存连接配置失败:', error);
324
+ throw error;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * 生成唯一ID
330
+ */
331
+ private generateId(): string {
332
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
333
+ }
334
+ }