处理数据

现在我们可以 添加身份验证,我们将处理数据,清理我们从客户端获得的输入并添加额外的信息.在本章中,我们需要一个空数据库,可以通过删除 data/ 文件夹(rm -rf data/)来完成.

消除新消息

创建新消息时,我们会自动清理输入,添加发送消息的用户并包含消息创建日期,然后再将其保存到数据库中.这就是 钩子 开始发挥作用的地方.在这种特定情况下, before 钩子.要创建一个新的钩子,我们可以运行:

feathers generate hook

我们把这个钩子叫做 process-message.我们希望预处理客户提供的数据.因此,在下一个询问何种钩子的提示中,选择 before 并按Enter键.

接下来显示我们所有服务的列表.对于这个钩子,只选择 messages 服务.使用箭头键导航到该条目,然后使用空格键选择它.

一个钩子可以在任何数量之前运行 服务.对于这个特定的钩子,只选择 create.在确认最后一个提示后,您应该看到如下内容:

The process-message hook prompts

进程消息钩子提示

生成一个挂钩并连接到所选服务.现在是时候添加一些代码了.更新 src/hooks/process-message.js 看起来像这样:

// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html

module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  return async context => {
    const { data } = context;

    // Throw an error if we didn't get a text
    if(!data.text) {
      throw new Error('A message must have a text');
    }

    // The authenticated user
    const user = context.params.user;
    // The actual message text
    const text = context.data.text
      // Messages can't be longer than 400 characters
      .substring(0, 400);

    // Override the original data (so that people can't submit additional stuff)
    context.data = {
      text,
      // Set the user id
      userId: user._id,
      // Add the current date
      createdAt: new Date().getTime()
    };

    // Best practice: hooks should always return the context
    return context;
  };
};

此验证码包括:

  1. 检查数据中是否有 text, 如果没有则抛出错误

  2. 将消息的 text 属性截断为400个字符

  3. 更新提交到数据库的数据以包含:

  • 新的截断文本

  • 当前经过身份验证的用户(因此我们始终知道是谁发送的)

  • 当前(创建)日期

添加用户头像

让我们生成另一个钩子来添加一个链接到与用户的电子邮件地址相关联的 Gravatar image,这样我们就可以显示一个头像了. 运行:

feathers generate hook

选择与我们之前的钩子几乎相同:

  • 调用钩子 gravatar

  • 这是一个 before 钩子

  • … 在 users 服务上

  • … 用于 create 方法

The gravatar hook prompts

gravatar 钩子提示

然后我们使用以下代码更新 src/hooks/gravatar.js:

// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks, see: http://docs.feathersjs.com/api/hooks.html

// We need this to create the MD5 hash
const crypto = require('crypto');

// The Gravatar image service
const gravatarUrl = 'https://s.gravatar.com/avatar';
// The size query. Our chat needs 60px images
const query = 's=60';

module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  return async context => {
    // The user email
    const { email } = context.data;
    // Gravatar uses MD5 hashes from an email address (all lowercase) to get the image
    const hash = crypto.createHash('md5').update(email.toLowerCase()).digest('hex');

    context.data.avatar = `${gravatarUrl}/${hash}?${query}`;

    // Best practice: hooks should always return the context
    return context;
  };
};

这里我们使用 Node的加密库 来创建用户电子邮件地址的MD5哈希值.这就是Gravatar用作与电子邮件地址关联的头像的URL.创建新用户时,此gravatar挂钩会将“头像”属性设置为头像图像链接.

填充邮件发件人

process-message 钩子中,我们当前只是将用户的 _id 添加为消息中的 userId 属性.我们希望在UI中显示的不仅仅是 _id,因此我们需要在消息响应中填充更多数据.要显示用户的详细信息,我们需要在邮件中包含额外信息.

因此我们创建另一个钩子:

  • 调用钩子 populate-user

  • 这是一个 after 钩子

  • … 在 messages 服务上

  • … 对于 all 方法

The populate-user hook

populate-user钩子

创建后,将 src/hooks/populate-user.js 更新为:

// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks, see: http://docs.feathersjs.com/api/hooks.html

module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  return async context => {
    // Get `app`, `method`, `params` and `result` from the hook context
    const { app, method, result, params } = context;

    // Make sure that we always have a list of messages either by wrapping
    // a single message into an array or by getting the `data` from the `find` method's result
    const messages = method === 'find' ? result.data : [ result ];

    // Asynchronously get user object from each message's `userId`
    // and add it to the message
    await Promise.all(messages.map(async message => {
      // Also pass the original `params` to the service call
      // so that it has the same information available (e.g. who is requesting it)
      message.user = await app.service('users').get(message.userId, params);
    }));

    // Best practice: hooks should always return the context
    return context;
  };
};

注解

Promise.all 确保所有调用并行运行,而不是顺序运行.

下一步是什么?

在本节中,我们添加了三个钩子来处理我们的消息和用户数据.我们现在有一个完整的API来发送和检索消息,包括身份验证.

现在我们准备构建一个前端 建立一个前端.

有关 React, React Native, Angular 或 VueJS 等特定框架的更多资源,请参阅 与前端框架集成.您可以找到创建完整聊天前端的指南,其中包括注册,日志记录,用户列表和消息.还有一些链接可以使用一些流行的前端框架构建完整的聊天应用程序.

您还可以浏览 API,了解有关使用Feathers及其数据库适配器的详细信息.