钩子

正如我们在 服务 中看到的,Feathers服务是实现数据存储和修改的好方法.从技术上讲,我们可以在服务中实现所有应用程序逻辑,但通常应用程序需要跨多个服务的类似功能.例如,如果允许用户甚至调用服务方法或将当前日期添加到我们正在保存的所有数据,我们可能希望检查所有服务.只需使用服务,我们就必须每次都重新实现这一点.

这是Feathers钩子的用武之地.钩子是可插入的中间件函数,可以在 before, before 或服务方法的 __error__s 之后注册.您可以注册单个钩子函数或创建它们的链,以创建复杂的工作流程.

就像服务本身一样,钩子是 独立于传输的.它们通常也是服务不可知的,这意味着它们可以与 any服务一起使用.此模式使您的应用程序逻辑保持灵活,可组合,并且更容易跟踪和调试.

注解

有关hook API的完整概述,请参阅 钩子.

钩子通常用于处理诸如验证,授权,日志记录,填充相关实体,发送通知等内容.

小技巧

有关钩子背后的设计模式的更多信息,请参阅 此博客文章.

快速举例

这是一个钩子的快速示例,它在调用实际的 create 服务方法之前向数据添加 createdAt 属性:

app.service('messages').hooks({
  before: {
    create (context) {
      context.data.createdAt = new Date();

      return context;
    }
  }
})

钩子函数

作为参数并返回该上下文或什么都不返回.挂钩函数按照它们注册的顺序运行,并且只有在当前挂钩函数完成后才会继续到下一个.如果钩子函数抛出错误,将跳过所有剩余的挂钩(可能还有服务调用),并返回错误.

使钩子更易于重复使用的常见模式(例如,使上面的示例中的 createdAt 属性名称可配置)是创建一个包装函数,它接受这些选项并返回一个钩子函数:

const setTimestamp = name => {
  return async context => {
    context.data[name] = new Date();

    return context;
  }
}

app.service('messages').hooks({
  before: {
    create: setTimestamp('createdAt'),
    update: setTimestamp('updatedAt')
  }
});

现在我们有一个可重用的钩子,可以在任何属性上设置时间戳.

钩子上下文

钩子 context 是一个包含服务方法调用信息的对象.它具有只读和可写属性.只读属性是:

  • context.app - Feathers应用程序对象

  • context.service - 此钩子当前正在运行的服务

  • context.path - 服务的路径

  • context.method - 服务方式

  • context.type - 钩型 (before, after 或者 error)

可写属性是:

  • context.params - 服务方法调用 params. 对于外部调用, params 通常包含:

    • context.params.query - 服务调用的查询(例如,REST的查询字符串)

    • context.params.provider - 传输的名称(我们将在下一章中看到)调用已完成. 通常是 rest, socketio, primus. 内部调用将是 undefined.

  • context.id - 用于 get, remove, updatepatch 服务方法调用的 id

  • context.data - 用户在 create, updatepatch 服务方法调用中发送的 data

  • context.error - 抛出的错误(在 error 钩子中)

  • context.result - 服务方法调用的结果(在 after 钩子中)

注解

有关钩子上下文的更多信息, 请参阅 钩子.

注册钩子

注册挂钩的最常用方法是在这样的对象中:

const messagesHooks = {
  before: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
  after: {
    all: [],
    find: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  }
};

app.service('messages').hooks(messagesHooks);

这样可以一目了然地查看执行挂钩的顺序以及使用哪种方法.

注解

all 是一个特殊的关键字,这意味着这些钩子将在此链中特定于方法的钩子之前运行.

例如,如果挂钩是这样注册的:

const messagesHooks = {
  before: {
    all: [ hook01() ],
    find: [ hook11() ],
    get: [ hook21() ],
    create: [ hook31(), hook32() ],
    update: [ hook41() ],
    patch: [ hook51() ],
    remove: [ hook61() ],
  },
  after: {
    all: [ hook05() ],
    find: [ hook15(), hook16() ],
    create: [ hook35() ],
    update: [ hook45() ],
    patch: [ hook55() ],
    remove: [ hook65() ],
  }
};

app.service('messages').hooks(messagesHooks);

此图说明了何时执行每个挂钩:

Hook flow

钩流

验证数据

如果钩子抛出错误,将跳过所有后续钩子并将错误返回给用户. 这使得 before 挂钩成为一个很好的地方,通过抛出无效数据的错误来验证传入的数据.我们可以抛出正常的 JavaScript错误错误 具有一些额外的功能(比如返回REST调用的正确错误代码).

@feathersjs/errors 是一个单独的模块,因此您必须在需要之前将其添加到项目中:

npm install @feathersjs/errors --save

我们只需要 create, updatepatch 的钩子,因为这些是允许用户提交数据的唯一服务方法:

const { BadRequest } = require('@feathersjs/errors');

const validate = async context => {
  const { data } = context;

  // Check if there is `text` property
  if(!data.text) {
    throw new BadRequest('Message text must exist');
  }

  // Check if it is a string and not just whitespace
  if(typeof data.text !== 'string' || data.text.trim() === '') {
    throw new BadRequest('Message text is invalid');
  }

  // Change the data to be only the text
  // This prevents people from adding other properties to our database
  context.data = {
    text: data.text.toString()
  }

  return context;
};

app.service('messages').hooks({
  before: {
    create: validate,
    update: validate,
    patch: validate
  }
});

注解

抛出一个合适的 错误 允许添加更多信息并返回正确的HTTP状态代码.

应用程序挂钩

有时我们想在Feathers应用程序中为每个服务自动添加一个钩子.这是应用程序挂钩可用于的内容.它们与服务特定挂钩的工作方式相同,但以更具体的顺序运行:

  • before 应用程序挂钩总是在 所有服务 before 挂钩之前运行

  • after 应用程序挂钩总是在 所有服务 after 挂钩后运行

  • error 应用程序挂钩总是在 所有服务 error 挂钩后运行

记录错误

应用程序挂钩的一个很好用途是记录任何服务方法调用错误.以下示例使用路径和方法名称以及错误堆栈记录每个服务方法错误:

app.hooks({
  error: async context => {
    console.error(`Error in '${context.path}' service method '${context.method}'`, context.error.stack);
  }
});

更多例子

chat/readme 将显示更多示例,例如如何关联数据和添加由以下创建的钩子的用户信息 Feathers生成器(CLI).

下一步是什么?

在本章中,我们学习了如何将Feathers钩子用作服务方法调用的中间件,以验证和操作传入和传出数据,而无需更改我们的服务.在下一章中,我们将把消息服务转换为 REST APIs.