Electron连接本地数据库及Angular5+TypeScript操作SQLite/MySQL问询
嘿,作为Electron+Angular的新手,你碰到的核心问题其实是渲染进程(也就是你的Angular代码)没法直接访问Node.js的数据库模块——这是Electron的安全机制在起作用。不过别慌,我们可以通过「主进程-渲染进程IPC通信」的方式完美解决,下面分SQLite和MySQL两种方案给你一步步讲:
SQLite是文件型数据库,不需要单独启动服务,非常适合桌面应用的本地存储需求。
步骤1:安装依赖并处理原生模块
首先在项目根目录安装SQLite相关依赖,因为SQLite是原生模块,需要和Electron的Node版本匹配,所以要执行electron-rebuild:
npm install sqlite3 @types/sqlite3 --save npx electron-rebuild
步骤2:主进程编写数据库逻辑
在Electron的main.ts中,初始化数据库连接,编写增删改查逻辑,并通过IPC监听渲染进程的请求:
// main.ts import { app, BrowserWindow, ipcMain } from 'electron'; import * as sqlite3 from 'sqlite3'; import { join } from 'path'; let db: sqlite3.Database; function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, // 保持关闭,遵循安全规范 contextIsolation: true, preload: join(__dirname, 'preload.js') // 通过preload桥接IPC通信 } }); mainWindow.loadURL('http://localhost:4200'); // Angular开发服务器地址 } app.whenReady().then(() => { // 初始化SQLite数据库(存在则打开,不存在则创建) db = new sqlite3.Database(join(app.getPath('userData'), 'people.db'), (err) => { if (err) console.error('数据库连接失败:', err); else { // 创建人员表(不存在则创建) db.run(`CREATE TABLE IF NOT EXISTS people ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, email TEXT UNIQUE )`, (err) => { if (err) console.error('创建表失败:', err); }); } }); // 监听渲染进程的「添加人员」请求 ipcMain.handle('add-person', async (_, personData) => { return new Promise((resolve, reject) => { db.run(`INSERT INTO people (name, age, email) VALUES (?, ?, ?)`, [personData.name, personData.age, personData.email], function(err) { if (err) reject(err); else resolve({ id: this.lastID, success: true }); } ); }); }); // 监听渲染进程的「获取所有人员」请求 ipcMain.handle('get-all-people', async () => { return new Promise((resolve, reject) => { db.all(`SELECT * FROM people`, (err, rows) => { if (err) reject(err); else resolve(rows); }); }); }); createWindow(); });
步骤3:编写Preload脚本桥接IPC
因为开启了contextIsolation,渲染进程不能直接调用Electron的ipcRenderer,所以需要在preload脚本中暴露安全的API:
// preload.ts import { contextBridge, ipcRenderer } from 'electron'; // 向渲染进程暴露安全的数据库操作API contextBridge.exposeInMainWorld('electronAPI', { addPerson: (personData: {name: string, age?: number, email: string}) => ipcRenderer.invoke('add-person', personData), getAllPeople: () => ipcRenderer.invoke('get-all-people') });
记得在项目的tsconfig.json中配置preload脚本的编译规则,确保它能被正确编译为JS文件。
步骤4:Angular组件中调用API
先在src/typings.d.ts中声明全局window的类型,避免TypeScript报错:
declare global { interface Window { electronAPI: { addPerson: (personData: {name: string, age?: number, email: string}) => Promise<{id: number, success: boolean}>, getAllPeople: () => Promise<Array<{id: number, name: string, age?: number, email: string}>> } } } export {};
然后在Angular组件中调用API处理表单数据:
// person.component.ts import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-person', templateUrl: './person.component.html' }) export class PersonComponent implements OnInit { people: Array<{id: number, name: string, age?: number, email: string}> = []; newPerson = { name: '', age: null, email: '' }; ngOnInit(): void { this.loadPeople(); } async loadPeople() { try { this.people = await window.electronAPI.getAllPeople(); } catch (err) { console.error('加载人员数据失败:', err); // 这里可以给用户显示友好的错误提示 } } async addPerson() { try { await window.electronAPI.addPerson(this.newPerson); this.loadPeople(); // 重置表单 this.newPerson = { name: '', age: null, email: '' }; } catch (err) { console.error('添加人员失败:', err); // 这里可以给用户显示友好的错误提示 } } }
如果你的数据需要和其他设备共享,或者数据量较大,可以选择MySQL,步骤和SQLite类似,只是主进程的数据库实现不同。
步骤1:安装依赖
MySQL的客户端是纯JS模块,不需要rebuild,直接安装:
npm install mysql2 @types/mysql2 --save
步骤2:主进程编写MySQL逻辑
在main.ts中替换SQLite的初始化和IPC监听代码:
// main.ts 中替换SQLite相关代码 import * as mysql from 'mysql2/promise'; let dbConnection: mysql.Connection; app.whenReady().then(async () => { // 连接MySQL数据库 try { dbConnection = await mysql.createConnection({ host: 'localhost', // 你的MySQL服务器地址 user: 'your-username', // 数据库用户名 password: 'your-password', // 数据库密码 database: 'your-db-name' // 要使用的数据库名称 }); console.log('MySQL连接成功'); // 确保人员表存在 await dbConnection.execute(`CREATE TABLE IF NOT EXISTS people ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, age INT, email VARCHAR(255) UNIQUE )`); } catch (err) { console.error('MySQL连接失败:', err); } // 监听添加人员请求 ipcMain.handle('add-person', async (_, personData) => { try { const [result] = await dbConnection.execute( `INSERT INTO people (name, age, email) VALUES (?, ?, ?)`, [personData.name, personData.age, personData.email] ); return { id: (result as any).insertId, success: true }; } catch (err) { throw err; } }); // 监听查询所有人员请求 ipcMain.handle('get-all-people', async () => { try { const [rows] = await dbConnection.execute(`SELECT * FROM people`); return rows; } catch (err) { throw err; } }); createWindow(); });
步骤3-4:复用Preload和Angular代码
Preload脚本和Angular组件的代码和SQLite方案完全一致,因为渲染进程只关心调用的API接口,不关心底层用的是什么数据库。
- 安全优先:永远不要在渲染进程中处理数据库凭证(比如MySQL的用户名密码),所有数据库操作都放在主进程,渲染进程只负责传递用户输入的数据和接收结果。
- TypeScript类型:给数据库返回的数据定义明确的类型,避免大量使用
any,提高代码的可维护性。 - 打包注意:打包Electron应用时,SQLite需要确保
electron-rebuild执行成功;MySQL则不需要额外处理,因为是纯JS模块。 - 错误处理:在主进程和渲染进程都要做好错误捕获,给用户显示友好的提示,而不是只在控制台打印错误。
内容的提问来源于stack exchange,提问作者Mohammad Reza Farahani

