【问题标题】:How do I make a database call from an Electron front end?如何从 Electron 前端进行数据库调用?
【发布时间】:2021-04-27 07:29:27
【问题描述】:

(这里是全新的学习 Electron,所以我确信这是一个基本问题,我缺少一些基本的东西......)

如何从 Electron 应用程序前端与本地数据库(我正在使用 Sqlite)进行交互?我有一个非常基本的数据库管理器类,在我的 Electron 应用程序的 index.js 文件中使用它没有问题。但是从前端(我使用的是 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;

我可以调用它并从 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();
});

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

<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>

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

【问题讨论】:

    标签: sqlite electron svelte


    【解决方案1】:

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


    这是一个相当广泛的主题,需要阅读。我会尝试为您提供入门知识并链接一些资源。

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

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

    一种方法是在创建BrowserWindow 时设置webPreferences.nodeIntegration = true(注意:nodeIntegrationis deprecated and weird。这允许您使用前端代码中的所有 Node API,并且您的 sn-p 可以工作。但是您可能不应该这样做,因为 BrowserWindow 能够加载外部 URL,并且这些页面中包含的任何代码都可以在您或您用户的计算机上执行任意代码。

    另一种方法是使用预加载脚本。预加载脚本在渲染器进程中运行,但可以访问 Node 运行时以及浏览器的 window 对象(除非 nodeIntegration 为 true,否则在实际前端代码运行之前,Node 全局变量会从范围中删除)。您可以简单地设置 window.require = require 并在前端文件中使用 Node 代码。但你可能也不应该这样做,即使你对暴露的内容很小心,因为仍然很容易留下漏洞并允许潜在的攻击者利用某些暴露的 API 进行完全访问,as demonstrated hereMore on Electron security.

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

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

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

    这会安全地公开一个Accounts 对象,并在前端使用window 中的指定方法。所以在你的组件中你可以写:

    const accountAdded = await Accounts.addAccount({id: 123, username: 'foo'});
    

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

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

    【讨论】:

    • 超级有用的概述!感谢您为我指明正确的方向。现在开始变得更有意义了。
    • 在渲染/主进程上执行计算量大的数据库任务是否正确?
    • @Frozenex 与任何 Node 应用程序一样,这取决于任务的繁重程度。如果它们可能会延迟重要的 UI 事件,那么最好使用工作进程。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    • 2021-09-11
    • 1970-01-01
    • 2020-04-06
    • 1970-01-01
    相关资源
    最近更新 更多