如何从 Electron 前端进行数据库调用?

Dav*_*eer 0 sqlite electron svelte

(这里是全新的学习电子,所以我确定这是一个基本问题,我错过了一些基本的东西......)

如何从 Electron 应用程序前端与本地数据库(我使用的是 Sqlite)交互?我有一个非常基本的数据库管理器类,index.js在我的 Electron 应用程序的文件中使用它没有问题。但是从前端(我使用的是 Svelte,但我可能可以翻译其他前端框架的解决方案),如何与数据库交互?这似乎很基本,但我试图找到一个基本的例子。

由于一切都是本地的,似乎没有必要设置一个完整的 API 来来回编组数据,但也许是这样?但如果是这样,如何告诉 Electron“后端”(如果这是正确的术语)做某事并将结果返回到前端?我看到了一些关于 IPC 的东西,但现在这没有多大意义,而且似乎有点矫枉过正。

这是我的简单数据库管理器类:

const sqlite3 = require("sqlite3").verbose();

class DbManager {
  #db;
  open() {
    this.#db = new sqlite3.Database("testing.db", sqlite3.OPEN_READWRITE);
  }
  close() {
    this.#db.close();
  }
  run(sql, param) {
    this.#db.run(sql, param);
    return this;
  }
}

const manager = new DbManager();
module.exports = manager;
Run Code Online (Sandbox Code Playgroud)

我可以调用它并从 Electron 入口点做任何没有问题的事情index.js

const { app, BrowserWindow, screen } = require("electron");
require("electron-reload")(__dirname);
const db = require("./src/repository/db");

const createWindow = () => {
   ...
};

let window = null;

app.whenReady().then(() => {
  db.open();
  createWindow();
});

app.on("window-all-closed", () => {
  db.close();
  app.quit();
});
Run Code Online (Sandbox Code Playgroud)

但是我的组件该怎么做呢?

<script>
  // this won't work, and I wouldn't expect it to, but not sure what the alternative is
  const db = require("./repository/db");  
  let accountName;
  function addAccount() {
    db.run("INSERT INTO accounts (name) VALUES ($name);", { $name: accountName });
  }
</script>

<main>
  <form>
    <label for="account_name">Account name</label>
    <input id="account_name" bind:value={accountName} />
    <button on:click={addAccount}>Add account</button>
  </form>
</main>
Run Code Online (Sandbox Code Playgroud)

如果有人知道做类似事情的样板实现,那将非常有帮助。显然这就像这里的应用程序 101;我只是不确定如何在 Electron 中解决这个问题,希望有人指出我正确的方向。

szy*_*ski 6

如果您绝对 100% 确定您的应用程序不会访问任何远程资源,那么您可以require通过预加载脚本公开以及您可能需要的任何其他内容,只需编写const nodeRequire = require; window.require = nodeRequire;.


这是一个相当广泛的话题,需要一些阅读。我会尽量给你一个入门和链接一些资源。

Electron 在两个(或更多,如果您打开多个窗口)进程上运行 - 主进程和渲染器进程。主进程处理诸如打开新窗口、启动和关闭整个应用程序、托盘图标、窗口可见性等事情,而渲染器进程基本上就像浏览器中的 JS 代码。更多关于电子过程

默认情况下,渲染器进程无权访问 Node 运行时,但可以允许它访问。您可以通过两种方式做到这一点,但有很多注意事项。

一种方法是webPreferences.nodeIntegration = true在创建BrowserWindow(注意:nodeIntegration 不推荐使用和奇怪的)时进行设置。这允许您使用前端代码中的所有 Node API,并且您的代码段可以工作。但您可能不应该这样做,因为 aBrowserWindow能够加载外部URL 以及这些页面上包含的任何代码都可以在您或您用户的机器上执行任意代码。

另一种方法是使用预加载脚本。预加载脚本在渲染器进程中运行,但可以访问 Node 运行时以及浏览器的window对象(Node 全局变量在实际前端代码运行之前从作用域中移除,除非nodeIntegration为真)。您可以简单地window.require = require在前端文件中设置并基本上使用 Node 代码。但是您可能也不应该这样做,即使您对暴露的内容很小心,因为仍然很容易留下漏洞并允许潜在攻击者利用某些公开的 API 进行完全访问,如此处所示更多关于电子安全

那么如何安全地做到这一点呢?设置webPreferences.contextIsolationtrue。这明确地将预加载脚本上下文与渲染器上下文分开,而不是导致不可靠的 Node API 剥离nodeIntegration: false,因此您几乎可以确定没有恶意代码可以完全访问 Node。

然后,您可以从预加载到前端向前端公开特定功能contextBridge.exposeInMainWorld。例如:

contextBridge.exposeInMainWorld('Accounts', {
  addAccount: async (accountData) => {
    // validate & sanitize...
    const success = await db.run('...');
    return success;
  }
}
Run Code Online (Sandbox Code Playgroud)

这在前端安全地公开了Accounts具有指定方法的对象window。所以在你的组件中你可以写:

const accountAdded = await Accounts.addAccount({id: 123, username: 'foo'});
Run Code Online (Sandbox Code Playgroud)

请注意,您仍然可以公开像runDbCommand(command) { db.run(command) }甚至 之类的方法evalInNode(code) { eval(code) },这就是我说几乎可以肯定的原因。

您实际上不需要将 IPC 用于处理文件或数据库之类的事情,因为这些 API 在预加载中可用。仅当您想从渲染器进程操作窗口或触发主进程上的任何其他内容时,才需要 IPC。