在 Electron 框架中实现数据库的连接、读取和写入,核心思路是在主进程中处理数据库逻辑(避免敏感信息暴露),通过 IPC(进程间通信)与渲染进程交互。以下是详细实现方案(以本地嵌入式数据库 SQLite 为例,兼顾其他数据库思路)。
一、技术选型与准备
1. 数据库选择
- 推荐 SQLite:嵌入式数据库,无需独立服务器,数据存储在本地文件,适合 Electron 桌面应用。
- 其他数据库(MySQL/PostgreSQL):需额外部署服务器,适合多端共享数据场景。
2. 核心依赖
electron:Electron 框架核心。sqlite3:SQLite 的 Node.js 驱动(异步操作)。electron-rebuild:解决原生模块(如sqlite3)与 Electron 的兼容性问题。
3. 项目初始化
bash
# 创建项目
mkdir electron-db-demo && cd electron-db-demo
npm init -y
# 安装依赖
npm install electron sqlite3 --save
npm install electron-rebuild --save-dev
4. 重建原生模块(关键步骤)
sqlite3是原生模块,需与 Electron 的 Node 版本匹配,执行重建:
bash
# 配置重建脚本(package.json中添加)
"scripts": {
"rebuild": "electron-rebuild -f -w sqlite3"
}
# 执行重建
npm run rebuild
二、实现核心逻辑
1. 项目结构
plaintext
electron-db-demo/
├─ main.js # 主进程(数据库操作+窗口管理)
├─ preload.js # 预加载脚本(安全暴露IPC接口)
├─ index.html # 渲染进程(UI+用户交互)
└─ package.json
2. 主进程(main.js):数据库操作 + IPC 监听
主进程负责数据库连接、CRUD 操作,并通过ipcMain接收渲染进程的请求。
javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
// 数据库文件路径(存放在Electron默认的用户数据目录,避免权限问题)
const dbPath = path.join(app.getPath('userData'), 'mydb.db');
// 初始化数据库连接
let db;
function initDB() {
return new Promise((resolve, reject) => {
db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('数据库连接失败:', err.message);
reject(err);
} else {
console.log('数据库连接成功');
// 创建示例表(如users表)
db.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)`, (err) => {
if (err) reject(err);
else resolve();
});
}
});
});
}
// 封装数据库操作(Promise化,避免回调地狱)
const dbHelper = {
// 插入数据
insert: (table, data) => {
return new Promise((resolve, reject) => {
const keys = Object.keys(data).join(',');
const placeholders = Object.keys(data).map(() => '?').join(',');
const values = Object.values(data);
const sql = `INSERT INTO ${table} (${keys}) VALUES (${placeholders})`;
db.run(sql, values, function(err) {
if (err) reject(err);
else resolve({ id: this.lastID }); // 返回插入的ID
});
});
},
// 查询数据(全部)
queryAll: (table) => {
return new Promise((resolve, reject) => {
const sql = `SELECT * FROM ${table}`;
db.all(sql, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
};
// 创建窗口
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'), // 预加载脚本
contextIsolation: true // 开启上下文隔离(安全要求)
}
});
mainWindow.loadFile('index.html');
}
// 初始化流程
app.whenReady().then(async () => {
await initDB(); // 等待数据库初始化完成
createWindow();
// IPC监听:处理渲染进程的数据库请求
// 1. 插入数据
ipcMain.handle('db-insert', async (event, table, data) => {
try {
return await dbHelper.insert(table, data);
} catch (err) {
throw err; // 抛出错误,让渲染进程捕获
}
});
// 2. 查询所有数据
ipcMain.handle('db-query-all', async (event, table) => {
try {
return await dbHelper.queryAll(table);
} catch (err) {
throw err;
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
3. 预加载脚本(preload.js):安全暴露 IPC 接口
由于 Electron 开启上下文隔离后,渲染进程无法直接访问ipcRenderer,需通过预加载脚本暴露有限接口。
javascript
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的IPC接口给渲染进程(仅允许必要操作)
contextBridge.exposeInMainWorld('electronAPI', {
// 插入数据
dbInsert: (table, data) => ipcRenderer.invoke('db-insert', table, data),
// 查询所有数据
dbQueryAll: (table) => ipcRenderer.invoke('db-query-all', table)
});
4. 渲染进程(index.html):UI 交互 + 调用数据库
渲染进程负责用户交互(输入数据、触发操作),通过预加载暴露的接口调用主进程的数据库逻辑。
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron数据库示例</title>
</head>
<body>
<h1>用户管理</h1>
<!-- 插入数据表单 -->
<div>
<input type="text" id="name" placeholder="姓名">
<input type="number" id="age" placeholder="年龄">
<button onclick="addUser()">添加用户</button>
</div>
<!-- 数据展示 -->
<div>
<h3>用户列表</h3>
<ul id="userList"></ul>
</div>
<script>
// 调用预加载暴露的API
const { electronAPI } = window;
// 添加用户(写入数据库)
async function addUser() {
const name = document.getElementById('name').value;
const age = document.getElementById('age').value;
if (!name || !age) return alert('请输入姓名和年龄');
try {
const result = await electronAPI.dbInsert('users', { name, age: Number(age) });
alert(`添加成功!ID: ${result.id}`);
loadUsers(); // 刷新列表
} catch (err) {
alert(`添加失败:${err.message}`);
}
}
// 加载用户(读取数据库)
async function loadUsers() {
try {
const users = await electronAPI.dbQueryAll('users');
const list = document.getElementById('userList');
list.innerHTML = users.map(u => `<li>${u.id}: ${u.name} (${u.age}岁)</li>`).join('');
} catch (err) {
alert(`查询失败:${err.message}`);
}
}
// 页面加载时初始化列表
window.onload = loadUsers;
</script>
</body>
</html>
三、运行与测试
在package.json中添加启动脚本:
json
"scripts": {
"start": "electron .",
"rebuild": "electron-rebuild -f -w sqlite3"
}
执行npm start即可启动应用,测试添加用户和查询功能。
四、其他数据库适配思路
1. MySQL/PostgreSQL
-
安装对应驱动:
npm install mysql2(MySQL)或npm install pg(PostgreSQL)。 -
主进程中通过驱动连接数据库(需配置 host、user、password 等):
javascript// MySQL示例(main.js中) const mysql = require('mysql2/promise'); const connection = await mysql.createConnection({ host: 'localhost', user: 'root', password: '123456', database: 'mydb' }); -
后续 CRUD 操作通过 SQL 语句执行(与 SQLite 逻辑类似,替换
dbHelper即可)。
2. 关键注意事项
- 安全性:数据库凭证(如 MySQL 密码)不要硬编码,可加密存储在本地配置文件。
- 路径处理 :本地数据库文件(如 SQLite)优先使用
app.getPath('userData')(用户数据目录),避免权限问题。 - 错误处理:所有数据库操作需捕获错误并返回给渲染进程,避免应用崩溃。
通过以上方案,可在 Electron 中实现安全、稳定的数据库读写功能,适配本地或远程数据库场景。