dot 快速的未来即将在您的城市举办的活动中到来。

加入我们在 Redis 发布会

如何使用 NodeJS 和 Redis 构建音乐分享应用程序

音乐是魔法。它有能力创造新的记忆、朋友和难忘的感觉。 

我们都喜欢音乐,但对于一些人来说,它是一种热情。聆听和分享不同的曲调,这些曲调触动了我们的情感,是一种爱好,它加强了社会纽带,并将许多友谊联系在一起。

但是,鉴于我们生活在一个更加全球化的时代,我们的许多朋友都散布在全球各地,试图与他们组织任何社交活动都可能很困难。 

Franco Chen 既是音乐爱好者又是创新者,他接受了这一挑战,创建了一个应用程序,允许用户无论身在何处,都可以与朋友一起收听、分享和发现新音乐。 

为了最大程度地提高用户的体验,该应用程序需要使用能够实时传输、处理和检索数据的数据库。借助 Redis,Franco 能够创建一个低延迟应用程序,其中用户之间的命令和交互具有超高响应能力。 

让我们看看 Franco 是如何将这个应用程序变为现实的。但在我们继续之前,我们想指出,我们还提供了一系列激动人心的应用程序,供您在 Redis Launchpad 上查看。

https://www.youtube.com/embed/GHf4Ngl6Qfk
  1. 你将构建什么?
  2. 你需要什么?
  3. 架构
  4. 入门
  5. 数据建模
  6. 如何访问/存储数据
  7. 工作原理

1. **你将构建什么?**

您将构建一个平台,允许您在线收听音乐并与朋友分享音乐。该应用程序通过创建私人或公共在线房间来工作,您可以在其中邀请不同品味的人加入体验。 

从本质上讲,该应用程序是深入了解不同类型、品味和偏好的门户,通过与对音乐有共同欣赏的人互动来实现。下面我们将向您展示如何从头开始创建这个应用程序,重点介绍不同的组件以及引导您完成实现过程的每个阶段。 

准备好开始了吗?

好的,让我们开始吧!

2. 你需要什么?

Socket.IO: 用作实时网络应用程序的 Javascript 库。 

Javascript: 用作首选编程语言,允许您使网页具有交互性。 

RedisJSON: Redis 的 JSON 数据类型,允许从 Redis 键存储、更新和获取 JSON 值

RediSearch: 用于 Redis 的查询、辅助索引和全文搜索。 

Chakra UI: 为您提供基本构建块,可以帮助您构建应用程序的前端。 

Redux: 用作管理和集中应用程序状态的开源 Javascript 库。 

Axios: 用作 Node.js 和浏览器的基于 Promise 的 HTTP 客户端。 

Node.js: 用作开源、跨平台,在 Web 浏览器之外执行 JavaScript 代码。

Express: 用作灵活的 Node.js Web 应用程序框架,为 Web 和移动应用程序提供一套强大的功能。

3. **架构**

  • 客户端使用 HTTP 请求和 Socket.IO 从我们的 Node.js 服务器请求和接收数据。 
  • Redis 存储所有应用程序数据,包括持久性和非持久性数据。这使应用程序能够以低延迟检索数据,从而创建一个更加流畅且响应迅速的应用程序。对于此应用程序而言,实现低延迟尤其重要,因为服务器和客户端之间存在大量数据移动。
  • 所有 Socket.IO 数据都通过 RedisPubSub 发送,从而最大限度地减少了添加更多 Node.js 服务器的需要。

4. 入门

先决条件

  • Node v14.16.1
  • npm v6.14.12
  • Redis v6.2.3,带有 RediSearch v2.0 和 RedisJSON v1.0
  • Docker
  • Docker Compose

步骤 1:克隆存储库

git clone https://github.com/spatialdj/frontend 

步骤 2:设置前端 

使用 RedisMod docker 镜像来设置 Redis 模块。在前端的根目录中,键入以下代码以安装前端依赖项: 

npm install

步骤 3:设置后端

转到后端的根目录并创建一个名为 config.js 的文件,内容如下

export default {
  redisHost: 'localhost',
  redisPassword: 'your_password_for_redis_here',
  sessionSecret: 'somesessionsecret',
  passwordSaltRounds: 10,
  youtube_key: 'youtube_api_key'
}

键入以下命令以安装后端依赖项: 

npm install

在后端的根目录中运行以下命令: 

npm start

步骤 4:访问应用程序

您的应用程序应在 localhost:3000 运行

5. 数据建模 

房间

  • 前缀: room:
  • 类型: HASH
  • 字段
    • messages: 指向此房间的消息的 Redis 键
    • id: 此房间的 ID
    • json: 此房间的 JSON 表示形式(由于 RediSearch 还不支持 JSON,因此使用此方法)
    • numMembers: 目前房间中的成员数量
    • description: 房间的描述
    • name: 房间的名称
    • private: (True | False) 房间是否可搜索

genres: 房间面向的类型

索引(RediSearch)

  • name: TEXT
  • description: TEXT
  • genres: TAG
  • numMembers: NUMERIC
  • private: TAG

private: TAG

命令

获取此房间的 JSON 表示形式

HGET ${roomId} json

创建新房间或更新房间(并非所有字段都必需)

HSET room:${roomId} id ${roomId} name ${name} description ${description} private ${private} genres ${genres} numMembers ${numMembers} json ${roomJson}

检查房间是否存在(在尝试加入房间时使用):

EXISTS ${roomId}

用于房间页面的搜索

FT.SEARCH @name|description:(${searchQuery}) @private:{false} @genres:{${genres}} SORTBY numMembers DESC LIMIT ${offset} ${limit}:

**用户队列** 

  • 前缀: queue
  • 类型: LIST
  • 数据:与相关房间关联的队列中用户用户名列表
  • 命令
    • RPUSH queue:${roomId} ${username}: 将用户添加到队列或创建队列
    • LRANGE queue:${roomId} 0 -1: 获取队列中的所有用户
    • LREM queue:${roomId} -1, ${username}: 从队列中删除用户
    • LMOVE queue:${roomId} queue:${roomId} LEFT RIGHT: 循环遍历队列(将用户从队列前端移动到队列后端)

会话

  • 前缀: sess:
  • 类型: STRING
  • 数据:存储用于身份验证的会话数据
  • 命令
    • SET sess:${sessionId} ${data}: 创建新会话。由 connect-redis 使用
    • GET sess:${sessionId}: 获取会话数据。由 connect-redis 使用
    • EXISTS sess:${sessionId}: 检查会话是否存在。由 connect-redis 使用

用户

  • 前缀: user
  • 类型: JSON
  • 字段
    • username: 用户的用户名
    • password: 使用 bcrypt 进行散列和加盐的密码
    • profilePicture: 图像的 URL
    • selectedPlaylist: 用户当前选择的播放列表
    • playlist: 播放列表对象

播放列表示例

"db168000-fa58-491b-81d0-1287d866fcf7": {
  "id": "db168000-fa58-491b-81d0-1287d866fcf7",
  "name": "Text playlist",
  "user": "exampleuser",
  "queue": [
      {
      "videoId": "TT4PHY0_hwE",
      "title": "Ekcle - Pearl Jigsaw",
      "thumbnails": {
          "default": {
          "url": "https://i.ytimg.com/vi/TT4PHY0_hwE/default.jpg",
          "width": 120,
          "height": 90
          }
      },
      "channelTitle": "Ekcle",
      "id": "2f7473db-2aec-4ec1-a2bd-623ed6b3ce48",
      "duration": 348000
      }
  ]
}

用户示例

{
    "username": ...,
    "password": ...,
    "profilePicture": ...,
    "playlist": { ... },
    "selectedPlaylist": …
 }
  • 命令
    • 创建新用户或更新用户数据
JSON.SET user:${username} ${path} ${userJson}
  • 获取用户数据
JSON.GET user:${username} ${path}

消息

  • 前缀: message
  • 类型: LIST
  • 数据:JSON 字符串化的对象,带有字段 id, timeSent, text, sender(包含字段 usernameprofilePicture
  • 命令
    • 将新消息添加到房间的消息历史记录中
LPUSH message:${messagesId} ${data}
  • 获取房间的消息历史记录,从最新的开始,然后向后
LRANGE message:${messagesId} ${start} ${end}

套接字

  • 前缀: socket
  • 类型: STRING
  • 数据:此套接字所属的用户名
  • 命令
    • 创建一个新的套接字
SET socket:${socketId} ${username}
  • 获取与套接字关联的用户名
GET socket:${socketId}
  • 删除套接字
DEL socket:${socketId}

6. 数据如何访问或存储

  • 对于用户
    • 当用户注册时,会创建一个用户,例如
JSON.SET user:${username} . ${userJson}
  • 要登录用户,会创建一个新的会话
SET sess:${sessionId} ${data}
  • 要检索用户信息
JSON.GET user:${username} ${path}
  • 对于套接字
    • 当新的套接字连接到服务器并且请求经过身份验证(用户已登录)时,会创建一个新的套接字并存储关联的用户名
SET socket:${socketId} ${username}
  • 当套接字从服务器断开连接时,它会被删除
DEL socket:${socketId}
  • 对于房间
    • 当搜索房间时,使用 RedisSearch 进行搜索
FT.SEARCH @name|description:(${searchQuery}) @private:{false} @genres:{${genres}} SORTBY numMembers DESC LIMIT ${offset} ${limit}
  • 当创建一个新房间时,会创建一个房间,如下所示
HSET room:${roomId} id ${roomId} name ${name} description ${description} private ${private} genres ${genres} numMembers ${numMembers} json ${roomJson}
  • 当更新房间时,会像下面这样更新(所有字段都是可选的)
HSET room:${roomId} id ${roomId} name ${name} description ${description} private ${private} genres ${genres} numMembers ${numMembers} json ${roomJson}
  • 对于队列
    • 当用户加入队列时,会创建一个队列/将用户添加到队列中
RPUSH queue:${roomId} ${username}
  • 当轮到用户播放歌曲时,用户会被移到队列的末尾
LMOVE queue:${roomId} queue:${roomId} LEFT RIGHT
  • 同时,播放的歌曲会被移到用户播放列表的末尾
song = JSON.ARRPOP user:${username} .playlist.${playlistId}.queue 0
JSON.ARRAPPEND user:${username} .playlist.${playlistId}.queue song
  • 对于播放列表
    • 当创建一个新的播放列表/更新现有的播放列表时
JSON.SET user:${username} .playlist.${playlistId} ${playlistJson}
  • 当删除播放列表时
JSON.DEL user:${username} .playlist.${playlistId}
  • 当用户选择播放列表时
JSON.SET user:${username} .selectedPlaylist ${playlistId})
  • 当将歌曲添加到播放列表时
JSON.ARRAPPEND user:${username} .playlist.${playlistId}.queue ${song}
  • 要从播放列表中获取歌曲
JSON.GET user:${username} .playlist.${playlistId}.queue

代码示例:从用户的播放列表中删除特定歌曲

const songs = JSON.parse(await jsonGetAsync(getUserKey(username), `.playlist.${playlistId}.queue`))
const songIndex = songs.findIndex(song => song.id === songId)
const success = songIndex !== -1
 
if (success) {
  songs.splice(songIndex, 1)
}
 
try {
  await jsonSetAsync(getUserKey(username), `.playlist.${playlistId}.queue`, JSON.stringify(songs))
} catch (error) {
  return res.status(400).json(error)
}

7. 工作原理

登录或注册帐户

在主页上,您可以通过点击屏幕右上角导航栏上的任一图标登录或创建新帐户。

创建房间

房间是根据主持人概述的标准托管各种音乐的在线社区。在这里,您可以与不同的人联系并互动,根据您的喜好和类型发现新音乐。

点击主页上的“创建房间”图标后,您需要提供有关要托管的房间的描述。在这里,您可以添加一些描述中的个性来传达您希望加入房间的氛围和人员类型,以及应该在此处共享的音乐类型(见下文)。

**创建播放列表**

加入房间的朋友将以他们的角色图标显示(见下文)。

所有人加入后,您需要创建一个播放列表,以便您可以分享音乐。点击屏幕左下角的箭头开始此过程。接下来,将出现一个连接到 YouTube 的搜索栏。

从这里,您可以搜索要包含的不同歌曲,并通过点击加号将它们添加到播放列表中。RediSearch 将根据输入搜索引擎的短语来识别并将歌曲添加到您的播放列表中。

收听彼此的歌曲并提供反馈

播放每首歌曲时,音乐视频将出现在屏幕中央。您还将在屏幕右侧看到一个聊天窗口,房间中的每个人都可以在这里分享他们对每首歌曲的意见。

在屏幕左下角,您可以点赞或不喜欢一首歌曲。如果房间里超过一半的人不喜欢这首歌,那么应用程序将跳到播放列表中的下一首歌曲。

结论:实现低延迟以最大限度地提高音乐体验

在不同组件之间以超高效率传输大量数据一直是 Franco 需要克服的最大障碍之一。在当今的数字化领域,用户期望命令和响应实时进行,他们期望没有更低的要求。

鉴于服务器和客户端之间移动的数据量,这对该应用程序尤其重要。利用 Redis 的高级数据功能,该应用程序实现了低延迟数据检索,创造了一个完全流畅且响应迅速的应用程序,使用户能够实时收听、分享和评论彼此的音乐。

要更直观地了解此应用程序的创建过程,请务必观看 Franco 的 YouTube 视频

如果您喜欢这篇文章,我们还有更多文章供您在 Redis Launchpad 上深入了解。在这里,您可以访问一系列对世界各地日常生活产生影响的令人兴奋的应用程序。

这些包括实时车辆跟踪系统、加速献血过程的应用程序、拆分测试软件等等。

查看它们。获得灵感。加入 Redis 的乐趣。

谁构建了这个应用程序?

Franco Chen


Franco 在软件工程方面拥有超过九年的经验,目前正在滑铁卢大学学习。如果您想了解他所有项目的信息,请务必在 GitHub 上关注他。