使用 IndexedDB、Redis 和 Node.js 构建笔记应用

在本文中,我将介绍如何创建一个基本的笔记应用程序,它可以在您在线时同步本地和远程内容,并在离线时默认保存到本地。

notes app sample

在服务器端使用 Redis

在 Redis 中添加记录时,我们不是像在 MySQL 或 PostgreSQL 中那样处理关系型数据库。我们处理的是类似于 IndexedDB 的结构,其中有键和值。那么,当我们只有一个键和值用于笔记应用程序时,我们需要什么?我们需要唯一 ID 来引用每个笔记,以及笔记元数据的哈希值。在本例中,元数据包含新的唯一 ID、创建时间戳和文本。

以下是在 Node 中使用 Redis 创建 ID 并保存笔记元数据的示例。

// Let's create a unique id for the new note.
client.incr('notes:counter', function (err, id) {

...

    // All note ids are referenced by the user's email and id.
    var keyName = 'notes:' + req.session.email + ':' + id;
    var timestamp = req.body.timestamp || Math.round(Date.now() / 1000);

    // Add the new id to the user's list of note ids.
    client.lpush('notes:' + req.session.email, keyName);

    // Add the new note to a hash.
    client.hmset(keyName, {
      id: id,
      timestamp: timestamp,
      text: finalText
    });

...

});

这将为服务器端的所有笔记提供以下键模式

  1. notes:counter 包含从 1 开始的所有唯一 ID。
  2. notes:<email> 包含用户拥有的所有笔记 ID。这是一个列表,我们在需要循环遍历所有用户的笔记以检索元数据时会引用它。
  3. notes:<email>:<note id> 包含笔记元数据。用户的电子邮件地址用于将此笔记引用到正确的拥有者。当用户删除笔记时,我们想要验证它是否与他们登录的相同电子邮件匹配,因此不会有人删除他们不拥有的笔记。

在客户端添加 IndexedDB

使用 IndexedDB 需要比 localStorage 更多的代码。但由于它是异步的,因此它更适合此应用程序。它更适合的两个主要原因是

  1. 您不希望等待所有笔记处理完毕后才渲染页面上的所有元素。想象一下,如果有数千个笔记,您必须等待所有笔记循环完毕才能在页面上显示任何内容。
  2. 您不能将笔记对象保存为对象,必须先将它们转换为字符串,这意味着在渲染之前必须将它们转换回对象。例如 { id: 1, text: 'my note text', timestamp: 1367847727 } 必须在 localStorage 中被字符串化,然后在之后被解析回来。现在想象一下,对很多笔记执行这个操作。

这两者都不等同于用户的理想体验,但如果我们想要 localStorage API 的易用性和 IndexedDB 的异步特性呢?我们可以使用 Gaia 的 async_storage.js 文件来帮助合并这两个世界。

如果我们离线,我们需要执行与服务器端类似的两件事

  1. 为笔记保存一个唯一 ID 并将其应用于 ID 数组中。由于我们无法引用由 Redis 创建的服务器端 ID,因此我们将使用时间戳。
  2. 保存笔记元数据的本地版本。
var data = {
  content: rendered,
  timestamp: id,
  text: content
};

asyncStorage.setItem(LOCAL_IDS, this.localIds, function () {
  asyncStorage.setItem(LOCAL_NOTE + id, data, function () {
    ...
  });
});

IndexedDB 键的结构与 Redis 的结构非常相似。模式如下

  1. 所有本地 ID 都保存在 localNoteIds 数组中
  2. 所有本地笔记对象都保存在 note:local:<id>
  3. 所有远程/同步 ID 都保存在 noteIds 数组中
  4. 所有远程/同步笔记对象都保存在 note:<id>
  5. 本地笔记使用时间戳作为其唯一 ID,并在 Redis 保存数据后将其转换为有效的服务器 ID

一旦我们在线,就可以上传本地笔记,在客户端保存远程笔记,然后删除本地笔记。

在客户端触发 note.js

每当我们刷新页面时,都需要尝试与服务器同步。如果我们离线,让我们标记它,只获取我们本地的内容。

/**
 * Get all local and remote notes.
 * If online, sync local and server notes; otherwise load whatever
 * IndexedDB has.
 */
asyncStorage.getItem('noteIds', function (rNoteIds) {
  note.remoteIds = rNoteIds || [];

  asyncStorage.getItem('localNoteIds', function (noteIds) {
    note.localIds = noteIds || [];

    $.get('/notes', function (data) {
      note.syncLocal();
      note.syncServer(data);

    }).fail(function (data) {
      note.offline = true;
      note.load('localNoteIds', 'note:local:');
      note.load('noteIds', 'note:');
    });
  });
});

快要完成了!

上面的代码提供了具有本地和远程同步支持的 CRD 笔记应用程序的基础。但我们还没完。

在 Safari 上,IndexedDB 不受支持,因为它们仍然使用 WebSQL。这意味着我们的 IndexedDB 代码将无法工作。为了使其跨浏览器兼容,我们需要包括一个 仅支持 WebSQL 的浏览器的 polyfill。在其他代码和 IndexedDB 支持之前包含它,应该可以正常工作。

最终产品

您可以在 http://notes.generalgoods.net 尝试使用该应用程序

源代码

要查看此应用程序的代码,请随时浏览 Github 上的仓库.

关于 Robert Nyman [名誉编辑]

Mozilla Hacks 的技术布道者和编辑。进行演讲并撰写有关 HTML5、JavaScript 和开放网络的博客。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直在从事网络前端开发工作——在瑞典和纽约市。他还经常在 http://robertnyman.com 上撰写博客,喜欢旅行和结识新朋友。

更多 Robert Nyman [名誉编辑] 的文章…


4 条评论

  1. Matrix

    哇,在我的 FirefoxOS 手机(Peak)上效果很好!笔记可以在手机重启后保存,这出乎我的意料——我以为应该保存到远程,但实际上没有必要。

    2013 年 5 月 17 日 下午 01:54

  2. Scott Turner

    您应该非常小心使用 Firefox 中的 IndexedDB。如果用户关闭浏览器,IndexedDB 不保证完成任何对数据库的写入操作。因此,在用户写入笔记然后退出浏览器的(非常现实的)情况下,笔记可能保存也可能不保存。

    我在此处针对此问题提交了一个错误:https://bugzilla.mozilla.org/show_bug.cgi?id=870645,如果您愿意,可以添加评论并鼓励开发人员解决此问题。

    2013 年 5 月 17 日 上午 11:50

  3. Aaron Shafovaloff

    我一直在开发一个希望更稳健的 IndexedDB 包装器

    https://github.com/aaronshaf/bongo.js

    它旨在使 IndexedDB 对开发人员友好,尤其是那些熟悉 MongoDB 查询语言的开发人员。适用于 FF、Chrome、IE10。

    2013 年 5 月 20 日 下午 08:41

    1. Robert Nyman [编辑]

      感谢您的告知!

      2013 年 5 月 21 日 下午 01:41

本文的评论已关闭。