/** * 数据库服务 - 支持MySQL远程同步和SQLite本地存储 * 使用 sql.js (SQLite WASM版本) 无需原生编译 */ const initSqlJs = require('sql.js'); const mysql = require('mysql2/promise'); const path = require('path'); const fs = require('fs'); const { app } = require('electron'); class DatabaseService { constructor() { this.mysqlConnection = null; this.sqliteDb = null; this.SQL = null; this.isRemoteConnected = false; this.dbPath = null; } /** * 获取SQLite数据库路径 */ getSqlitePath() { const userDataPath = app?.getPath('userData') || process.cwd(); return path.join(userDataPath, 'easyshell.db'); } /** * 保存数据库到文件 */ saveDatabase() { if (this.sqliteDb && this.dbPath) { const data = this.sqliteDb.export(); const buffer = Buffer.from(data); fs.writeFileSync(this.dbPath, buffer); } } /** * 初始化本地SQLite数据库 */ async initLocalDatabase() { try { // 初始化 sql.js this.SQL = await initSqlJs(); this.dbPath = this.getSqlitePath(); // 尝试加载现有数据库 if (fs.existsSync(this.dbPath)) { const fileBuffer = fs.readFileSync(this.dbPath); this.sqliteDb = new this.SQL.Database(fileBuffer); } else { this.sqliteDb = new this.SQL.Database(); } this.createLocalTables(); this.saveDatabase(); console.log('✅ 本地数据库初始化成功'); return { success: true }; } catch (error) { console.error('❌ 本地数据库初始化失败:', error); return { success: false, error: error.message }; } } /** * 创建本地表结构 */ createLocalTables() { // 主机信息表 this.sqliteDb.run(` CREATE TABLE IF NOT EXISTS hosts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, host TEXT NOT NULL, port INTEGER DEFAULT 22, username TEXT NOT NULL, password TEXT, private_key TEXT, group_name TEXT DEFAULT '默认分组', color TEXT DEFAULT '#58a6ff', description TEXT, last_connected_at TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, is_synced INTEGER DEFAULT 0 ) `); // 命令提示/历史表 this.sqliteDb.run(` CREATE TABLE IF NOT EXISTS commands ( id INTEGER PRIMARY KEY AUTOINCREMENT, command TEXT NOT NULL UNIQUE, description TEXT, category TEXT DEFAULT '通用', usage_count INTEGER DEFAULT 0, host_id INTEGER, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (host_id) REFERENCES hosts(id) ) `); // 命令片段/快捷命令表 this.sqliteDb.run(` CREATE TABLE IF NOT EXISTS snippets ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, command TEXT NOT NULL, description TEXT, category TEXT DEFAULT '通用', hotkey TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ) `); // 同步记录表 this.sqliteDb.run(` CREATE TABLE IF NOT EXISTS sync_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, sync_type TEXT NOT NULL, sync_time TEXT DEFAULT CURRENT_TIMESTAMP, status TEXT, details TEXT ) `); // 插入默认命令提示 const defaultCommands = [ { command: 'ls -la', description: '列出所有文件详细信息', category: '文件操作' }, { command: 'cd', description: '切换目录', category: '文件操作' }, { command: 'pwd', description: '显示当前目录', category: '文件操作' }, { command: 'mkdir', description: '创建目录', category: '文件操作' }, { command: 'rm -rf', description: '强制删除文件/目录', category: '文件操作' }, { command: 'cp -r', description: '递归复制文件/目录', category: '文件操作' }, { command: 'mv', description: '移动/重命名文件', category: '文件操作' }, { command: 'cat', description: '查看文件内容', category: '文件操作' }, { command: 'tail -f', description: '实时查看日志', category: '日志查看' }, { command: 'grep', description: '文本搜索', category: '文本处理' }, { command: 'ps aux', description: '查看所有进程', category: '系统管理' }, { command: 'top', description: '实时系统监控', category: '系统管理' }, { command: 'htop', description: '增强版系统监控', category: '系统管理' }, { command: 'df -h', description: '查看磁盘使用情况', category: '系统管理' }, { command: 'free -m', description: '查看内存使用情况', category: '系统管理' }, { command: 'netstat -tunlp', description: '查看网络连接', category: '网络' }, { command: 'systemctl status', description: '查看服务状态', category: '服务管理' }, { command: 'systemctl restart', description: '重启服务', category: '服务管理' }, { command: 'docker ps', description: '查看运行中的容器', category: 'Docker' }, { command: 'docker logs -f', description: '实时查看容器日志', category: 'Docker' }, ]; for (const cmd of defaultCommands) { try { this.sqliteDb.run( `INSERT OR IGNORE INTO commands (command, description, category) VALUES (?, ?, ?)`, [cmd.command, cmd.description, cmd.category] ); } catch (e) { // 忽略重复插入错误 } } } /** * 连接MySQL远程数据库 */ async connectMySQL(config) { try { // 如果是 localhost,转换为 127.0.0.1 强制使用 IPv4 let host = config.host; if (host === 'localhost') { host = '127.0.0.1'; } // 先不指定数据库连接,这样可以创建数据库 this.mysqlConnection = await mysql.createConnection({ host: host, port: config.port || 3306, user: config.user, password: config.password, connectTimeout: 10000, }); // 自动建库建表 await this.initRemoteDatabase(config.database || 'easyshell'); this.isRemoteConnected = true; console.log('✅ MySQL远程数据库连接成功'); return { success: true }; } catch (error) { console.error('❌ MySQL连接失败:', error); this.isRemoteConnected = false; return { success: false, error: error.message }; } } /** * 初始化远程MySQL数据库(自动建库建表) */ async initRemoteDatabase(dbName) { try { // 创建数据库 - 使用 query 而不是 execute await this.mysqlConnection.query( `CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci` ); // 使用数据库 - 使用 query 而不是 execute await this.mysqlConnection.query(`USE \`${dbName}\``); // 创建主机表 await this.mysqlConnection.query(` CREATE TABLE IF NOT EXISTS hosts ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, host VARCHAR(255) NOT NULL, port INT DEFAULT 22, username VARCHAR(255) NOT NULL, password TEXT, private_key TEXT, group_name VARCHAR(100) DEFAULT '默认分组', color VARCHAR(20) DEFAULT '#58a6ff', description TEXT, last_connected_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); // 创建命令表 await this.mysqlConnection.query(` CREATE TABLE IF NOT EXISTS commands ( id INT PRIMARY KEY AUTO_INCREMENT, command TEXT NOT NULL, description TEXT, category VARCHAR(100) DEFAULT '通用', usage_count INT DEFAULT 0, host_id INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); // 创建命令片段表 await this.mysqlConnection.query(` CREATE TABLE IF NOT EXISTS snippets ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, command TEXT NOT NULL, description TEXT, category VARCHAR(100) DEFAULT '通用', hotkey VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); console.log('✅ 远程数据库表结构初始化完成'); } catch (error) { console.error('❌ 远程数据库初始化失败:', error); throw error; } } /** * 断开MySQL连接 */ async disconnectMySQL() { if (this.mysqlConnection) { await this.mysqlConnection.end(); this.mysqlConnection = null; this.isRemoteConnected = false; } } /** * 同步数据到远程 */ async syncToRemote() { if (!this.isRemoteConnected) { return { success: false, error: '未连接到远程数据库' }; } try { // 获取本地未同步的主机 const localHosts = this.runQuery('SELECT * FROM hosts WHERE is_synced = 0'); for (const host of localHosts) { await this.mysqlConnection.execute(` INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name), port = VALUES(port), username = VALUES(username), password = VALUES(password), private_key = VALUES(private_key), group_name = VALUES(group_name), color = VALUES(color), description = VALUES(description) `, [host.name, host.host, host.port, host.username, host.password, host.private_key, host.group_name, host.color, host.description]); // 标记为已同步 this.sqliteDb.run('UPDATE hosts SET is_synced = 1 WHERE id = ?', [host.id]); } this.saveDatabase(); // 记录同步日志 this.sqliteDb.run( `INSERT INTO sync_log (sync_type, status, details) VALUES ('upload', 'success', ?)`, [JSON.stringify({ synced_hosts: localHosts.length })] ); this.saveDatabase(); return { success: true, synced: localHosts.length }; } catch (error) { console.error('❌ 同步到远程失败:', error); return { success: false, error: error.message }; } } /** * 从远程同步数据 */ async syncFromRemote() { if (!this.isRemoteConnected) { return { success: false, error: '未连接到远程数据库' }; } try { // 获取远程主机 const [remoteHosts] = await this.mysqlConnection.execute('SELECT * FROM hosts'); for (const host of remoteHosts) { this.sqliteDb.run(` INSERT OR REPLACE INTO hosts (id, name, host, port, username, password, private_key, group_name, color, description, is_synced) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1) `, [host.id, host.name, host.host, host.port, host.username, host.password, host.private_key, host.group_name, host.color, host.description]); } // 同步命令 const [remoteCommands] = await this.mysqlConnection.execute('SELECT * FROM commands'); for (const cmd of remoteCommands) { this.sqliteDb.run(` INSERT OR REPLACE INTO commands (id, command, description, category, usage_count) VALUES (?, ?, ?, ?, ?) `, [cmd.id, cmd.command, cmd.description, cmd.category, cmd.usage_count]); } this.saveDatabase(); // 记录同步日志 this.sqliteDb.run( `INSERT INTO sync_log (sync_type, status, details) VALUES ('download', 'success', ?)`, [JSON.stringify({ hosts: remoteHosts.length, commands: remoteCommands.length })] ); this.saveDatabase(); return { success: true, hosts: remoteHosts.length, commands: remoteCommands.length }; } catch (error) { console.error('❌ 从远程同步失败:', error); return { success: false, error: error.message }; } } /** * 执行查询并返回结果数组 */ runQuery(sql, params = []) { const stmt = this.sqliteDb.prepare(sql); stmt.bind(params); const results = []; while (stmt.step()) { results.push(stmt.getAsObject()); } stmt.free(); return results; } /** * 执行查询并返回单个结果 */ runQuerySingle(sql, params = []) { const results = this.runQuery(sql, params); return results.length > 0 ? results[0] : null; } // ========== 主机管理方法 ========== getAllHosts() { return this.runQuery('SELECT * FROM hosts ORDER BY group_name, name'); } getHostById(id) { return this.runQuerySingle('SELECT * FROM hosts WHERE id = ?', [id]); } addHost(host) { this.sqliteDb.run(` INSERT INTO hosts (name, host, port, username, password, private_key, group_name, color, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ host.name, host.host, host.port || 22, host.username, host.password, host.privateKey, host.groupName || '默认分组', host.color || '#58a6ff', host.description ]); this.saveDatabase(); // 获取最后插入的ID const result = this.runQuerySingle('SELECT last_insert_rowid() as id'); return { id: result.id }; } updateHost(id, host) { this.sqliteDb.run(` UPDATE hosts SET name = ?, host = ?, port = ?, username = ?, password = ?, private_key = ?, group_name = ?, color = ?, description = ?, updated_at = CURRENT_TIMESTAMP, is_synced = 0 WHERE id = ? `, [ host.name, host.host, host.port, host.username, host.password, host.privateKey, host.groupName, host.color, host.description, id ]); this.saveDatabase(); return { success: true }; } deleteHost(id) { this.sqliteDb.run('DELETE FROM hosts WHERE id = ?', [id]); this.saveDatabase(); return { success: true }; } updateLastConnected(id) { this.sqliteDb.run(`UPDATE hosts SET last_connected_at = CURRENT_TIMESTAMP WHERE id = ?`, [id]); this.saveDatabase(); } // ========== 命令提示方法 ========== searchCommands(keyword) { return this.runQuery(` SELECT * FROM commands WHERE command LIKE ? OR description LIKE ? ORDER BY usage_count DESC, command LIMIT 20 `, [`%${keyword}%`, `%${keyword}%`]); } getAllCommands() { return this.runQuery('SELECT * FROM commands ORDER BY category, command'); } incrementCommandUsage(id) { this.sqliteDb.run('UPDATE commands SET usage_count = usage_count + 1 WHERE id = ?', [id]); this.saveDatabase(); } addCommand(command) { this.sqliteDb.run(` INSERT INTO commands (command, description, category) VALUES (?, ?, ?) `, [command.command, command.description, command.category || '通用']); this.saveDatabase(); const result = this.runQuerySingle('SELECT last_insert_rowid() as id'); return { id: result.id }; } // ========== 命令片段方法 ========== getAllSnippets() { return this.runQuery('SELECT * FROM snippets ORDER BY category, name'); } addSnippet(snippet) { this.sqliteDb.run(` INSERT INTO snippets (name, command, description, category, hotkey) VALUES (?, ?, ?, ?, ?) `, [snippet.name, snippet.command, snippet.description, snippet.category, snippet.hotkey]); this.saveDatabase(); const result = this.runQuerySingle('SELECT last_insert_rowid() as id'); return { id: result.id }; } deleteSnippet(id) { this.sqliteDb.run('DELETE FROM snippets WHERE id = ?', [id]); this.saveDatabase(); return { success: true }; } // ========== 关闭数据库 ========== close() { if (this.sqliteDb) { this.saveDatabase(); this.sqliteDb.close(); } if (this.mysqlConnection) { this.mysqlConnection.end(); } } } module.exports = new DatabaseService();