常问问题

我们一直在收集一些常见问题.我们要么直接更新指南, 要么在这里提供答案, 要么两者兼而有之.

Feathers支持哪些Node版本

最新版本的Feathers和所有插件适用于Node 6及更高版本.当官方支持周期结束时, 将支持 node 6, 直到 2019-04-18.

CLI的 Feathers指南 和应用程序(@feathersjs/cli)使用更新的语言功能, 如 async/await, 并要求 node 8 或以后.

如何创建自定义方法?

关于Feathers的一个重要事项是, 它只向客户公开官方 服务. 虽然您可以在服务器上添加和使用任何服务方法, 但是不能将这些自定义方法公开给客户端.

Feathers是围绕 REST架构约束 构建的, 并且有很多很好的理由. 通常, 几乎任何可能需要自定义方法或RPC样式操作的东西也可以通过创建 服务 或通过 钩子 来完成.

到目前为止, 这些好处(如安全性, 可预测性, 发送定义良好的实时事件)远远超过了概念化应用程序逻辑时所需思想的微小变化.

例子:

  • 发送不在数据库中存储邮件消息的电子邮件操作.

资源(服务)不必是数据库记录. 它可以是任何类型的资源(例如城市的当前天气或创建将发送它的电子邮件). 发送电子邮件通常使用单独的电子邮件 服务:

app.use('/email', {
  create(data) {
    return sendEmail(data);
  }
})

或者在 钩子 中.

  • 在电子商务网站下订单.在幕后, 将在一个事务中插入许多记录: order_item, order_header, voucher_tracking等.

这就是 钩子 适用于.在创建新订单时, 您还有一个定义良好的钩链:

app.service('orders').hooks({
  before: {
    create: [
      validateData(),
      checkStock(),
      checkVoucher()
    ]
  },
  after: {
    create: [
      chargePayment(), // hook that calls `app.service('payment').create()`
      sendEmail(), // hook that calls `app.service('email').create()`
      updateStock() // Update product stock here
    ]
  }
})
  • 一个 userService.resetPassword 方法

这也可以实现为在 create 方法中重置密码的密码服务:

const crypto = require('crypto');

class PasswordService {
  create(data) {
    const userId = data.user_id;
    const userService = this.app.service('user');

    return userService.patch(userId, {
      passwordToken: crypto.randomBytes(48)
    }).then(user => sendEmail(user))
  }

  setup(app) {
    this.app = app;
  }
}

如何进行嵌套或自定义路由?

通常情况下,我们发现实际上并不需要它们,并且保持路线尽可能平坦要好得多.例如 users/:userId/posts 之类的东西 - 虽然很适合人类阅读 - 但实际上并不像等价的那样容易解析和处理 查询. 另外,当通过没有路线概念的websocket连接使用Feathers时,这也会更好.

但是, 仍然可以通过在嵌套路由上注册现有服务并将route参数映射到这样的查询参数来创建服务的嵌套路由.:

app.use('/posts', postService);
app.use('/users', userService);

// re-export the posts service on the /users/:userId/posts route
app.use('/users/:userId/posts', app.service('posts'));

// A hook that updates `data` with the route parameter
function mapUserIdToData(context) {
  if(context.data && context.params.route.userId) {
    context.data.userId = context.params.route.userId;
  }
}

// For the new route, map the `:userId` route parameter to the query in a hook
app.service('users/:userId/posts').hooks({
  before: {
    find(context) {
      context.params.query.userId = context.params.route.userId;
    },
    create: mapUserIdToData,
    update: mapUserIdToData,
    patch: mapUserIdToData
  }
})

现在转到 /users/123/posts 将调用 postService.find({query:{userId:123}}) 并返回该用户的所有帖子.

有关URL路由和参数的更多信息, 请参阅 Express.

注解

URL永远不应包含更改数据的操作(如 post/publishpost/delete).这一直是HTTP协议的重要组成部分,而Feathers比大多数其他框架更严格地执行此操作.例如,要发布一个帖子,你会称之为 .patch(id,{published:true}).

你能支持另一个数据库吗?

Feathers 数据库适配器 实现了将Feathers用于某些数据库和ORM所需的90%的功能.但是,即使您喜欢的数据库或ORM不在列表中,或者适配器不支持您正在寻找的特定功能,Feathers仍然可以通过以下方式满足您的所有需求 服务.

重要

要正确使用Feathers, 了解服务如何工作以及所有现有数据库适配器只是与数据库本身通信的服务非常重要.

编写自己的服务的原因和方法包括 Feathers 基础知识.在生成器中,可以通过运行 feathers generate service,选择“自定义服务”,然后编辑 <servicename>/<servicename> .class.js 文件来创建自定义服务,以生成相应的数据库调用.

如果您想发布自己的数据库适配器, 首先要确保该数据库还没有一个 社区维护适配器 ( 许多维护者都是很高兴得到一些帮助). 如果没有, 你可以运行 feathers generate plugin 来创建一个新的插件. 可以在 Feathers 记忆库 中找到数据库适配器的参考实现. 社区维护的适配器总是有可能毕业于 官方 Feathers适配器, 但目前还没有计划直接添加对Feathers团队的任何新数据库的支持.

我没有得到实时事件

Feathers Buzzard (@feathersjs/feathers@v3.0.0)引入了一个新的,更安全的事件系统,默认情况下 不会 发送实时事件.如果您没有在客户端上获得实时事件,则通常是 事件频道 设置存在问题.

看一下 feathersjs.com 的例子 实时API事件频道.如果要从以前的版本迁移,请参阅 migrationing_adding-channels.

生成的应用程序已经设置了一个 channels.js 文件, 默认情况下只向经过身份验证的用户发送事件, 但可以根据 事件频道 修改为您的需求.

为什么我没有收到JSON错误?

如果你得到一个纯文本错误和一个500状态代码来处理应该返回不同状态代码的错误,请确保你有 @feathersjs/express 模块配置的 express.errorHandler() ../api/express#expresserrorhandler 章节.

为什么我没有收到正确的HTTP错误代码

见上面的答案.

我怎样才能做 findOrCreate 这样的自定义方法?

自定义功能几乎总是可以使用挂钩映射到现有服务方法. 例如, findOrCreate 可以作为服务的 get 方法的前挂钩实现. 请参阅此要点 以获取如何在前挂钩中实现此功能的示例.

为什么使用JWT进行会话

Feathers使用 JSON web tokens(JWT) 作为其标准身份验证机制.一些文章,如 停止使用JWT进行会话 促进使用标准cookie和HTTP会话.虽然它提出了一些有效点,但并非所有这些都适用于Feathers,并且Feathers依赖于JWT还有其他很好的理由:

  • Feathers旨在支持许多不同的传输机制,其中大多数不依赖于HTTP,但是作为身份验证机制可以很好地与JWT配合使用.对于通常不受传统HTTP会话保护的已建立连接的websockets,情况就已如此.

  • 默认情况下,Feathers存储在JWT有效内容中的唯一内容是用户ID.这是一个有状态的标记.您可以通过将更多数据放入JWT有效负载来更改此设置并使令牌无状态,但这由您自行决定.目前,在JWT被验证为未过期或被篡改之后,每个请求都会查找用户.

  • 您需要确保撤消JWT令牌或设置较低的过期日期或添加自定义逻辑以验证用户的帐户是否仍然有效/有效.目前默认的到期时间是1天.我们为大多数应用选择了合理的默认设置,但根据您的应用,这可能太长或太短.

此外,通过使用:doc:../api/express,仍然可以将Feathers与现有的* traditional * Express会话机制一起使用.例如,来自传统Express会话的所有服务调用的 params.user 可以像这样传递:

app.use(function(req,res, next) {
  // Set service call `param.user` from `session.user`
  req.feathers.user = req.session.user;
});

如何渲染模板?

Feathers就像Express一样工作,所以它完全相同.我们创建了一个 使用视图引擎.有关使用身份验证保护Express视图的信息,请参阅 认证Express中间件(SSR).

如何创建频道或房间

在Feathers 事件频道 是向某些客户发送 事件 的方式.

我该如何进行验证?

如果您的数据库/ORM支持模型或模式(即Mongoose或Sequelize), 那么您有2个选项.

首选方式

You perform validation at the service level 钩子. This is better because it keeps your app database agnostic so you can easily swap databases without having to change your validations much.

如果你编写了一堆小钩子来验证特定的东西, 那么它更容易测试, 而且性能稍微高一些, 因为你可以提前退出验证链, 而不必一直到将数据插入到数据库中找出该数据是否无效.

如果您没有模型或模式, 那么使用钩子进行验证是您唯一的选择.如果您想出一些不同的东西, 请随时提交PR!

ORM方式

使用ORM适配器, 您可以在模型级别执行验证:

关于模型级验证的好处是Feathers会以一种很好的一致格式将验证错误返回给客户端.

如何做协会?

与验证类似, 它取决于您的数据库/ORM是否支持模型.

首选方式

对于任何Feathers数据库/ORM适配器, 您只需使用 钩子 从其他服务获取数据.

This is a better approach because it keeps your application database agnostic and service oriented. By referencing the services (using app.service().find(), etc.) you can still decouple your app and have these services live on entirely separate machines or use entirely different databases without having to change any of your fetching code. We show how to associate data in a hook in the 处理数据. An alternative are the fastJoin or populate in feathers-hooks-common.

ORM方式

使用mongoose, 您可以使用 $populate 查询参数来填充嵌套文档.

// Find Hulk Hogan and include all the messages he sent
app.service('user').find({
  query: {
    name: 'hulk@hogan.net',
    $populate: ['sentMessages']
  }
});

使用Sequelize, 你可以做到这一点:

// Find Hulk Hogan and include all the messages he sent
app.service('user').find({
  name: 'hulk@hogan.net',
  sequelize: {
    include: [{
      model: Message,
      where: { sender: Sequelize.col('user.id') }
    }]
  }
});

或者 如此处所述 将其设置在钩子中.

Sequelize模型和协会

如果您正在使用 Sequelize adapter, 首先了解SQL和Sequelize非常重要.有关如何使用Sequelize Feathers适配器关联模型的更多信息, 请参阅feathers-sequelize文档中的 associations部分.

Koa/Hapi/X 怎么样?

还有许多其他 node 服务器框架, 如Koa, 一个*“Node.JS的下一代Web框架”*使用ES6生成器函数而不是Express中间件或HapiJS等. 目前, Feathers与框架无关, 但仅为HTTP API提供 Express 集成. 未来可能会支持更多的框架, 直接 Node HTTP 是最有可能的.

如何在挂钩或服务中访问请求对象?

简而言之, 你不应该这样做.如果你看一下 钩子 , 你会看到钩子上可用的所有参数.

如果您仍然需要请求对象中的某些内容(例如, 请求的IP地址), 您只需将其添加到 req.feathers 对象 ../api/express#params 或 :doc :../API/socketio#appconfiguresocketiocallback.

如何挂载子应用程序?

它与Express几乎完全相同.可以找到更多信息 ../api/express #sub-apps.

将响应发送给用户后如何进行处理?

钩子工作流程允许您非常优雅地处理这些情况.这取决于你在钩子里回来的承诺.这是一个发送电子邮件但不等待成功消息的钩子的示例.

function (context) {

  // Send an email by calling to the email service.
  context.app.service('emails').create({
    to: 'user@email.com',
    body: 'You are so great!'
  });

  // Send a message to some logging service.
  context.app.service('logging').create(context.data);

  // Return a resolved promise to immediately move to the next hook
  // and not wait for the two previous promises to resolve.
  return Promise.resolve(context);
}

如何调试我的应用程序

它与调试任何其他NodeJS应用程序没有什么不同, 但您可以参考 此博客文章 获取更多特定的Feathers技巧和窍门.

可能检测到EventEmitter内存泄漏 警告

This warning is not as bad as it sounds. If you got it from Feathers you most likely registered more than 64 services and/or event listeners on a Socket. If you don’t think there are that many services or event listeners you may have a memory leak. Otherwise you can increase the number in the Socket.io via io.sockets.setMaxListeners(number) and with Primus via primus.setMaxListeners(number). number can be 0 for unlimited listeners or any other number of how many listeners you’d expect in the worst case.

为什么我不能从客户端传递 params?

当你拨打电话时:

const params = { foo: 'bar' };
client.service('users').patch(1, { admin: true }, params).then(result => {
  // handle response
});

on the client the context.params object will only be available in your client side hooks. It will not be provided to the server. The reason for this is because context.params on the server usually contains information that should be server-side only. This can be database options, whether a request is authenticated, etc. If we passed those directly from the client to the server this would be a big security risk. Only the client side context.params.query and context.params.headers objects are provided to the server.

如果您需要将信息从客户端传递到不属于查询的服务器, 则需要将其添加到客户端的 context.params.query 并明确地将其从``context.params中拉出来.在服务器端查询``.这可以这样实现:

// client side
client.hooks({
  before: {
    all: [
      context => {
        context.params.query.$client = {
          platform: 'ios',
          version: '1.0'
        };

        return context;
      }
    ]
  }
});

// server side, inside app.hooks.js
const hooks = require('feathers-hooks-common');

module.exports = {
  before: {
    all: [
      // remove values from context.params.query.$client and move them to context.params
      // so context.params.query.$client.version -> context.params.version
      // and context.params.query.$client is removed.
      hooks.client('version', 'platform')
    ]
  }
}

我的带有空值的查询无效

When making a request using REST (HTTP) query string values don’t have any type information and will always be strings. Some database adapters that have a schema (like feathers-mongoose or feathers-sequelize) will try to convert values to the correct type but others (like feathers-mongodb) can’t. Additionally, null will always be a string and always has to be converted if you want to query for null. This can be done in a before 钩子:

app.service('myservice').hooks({
  before: {
    find(context) {
      const { params: { query = {} } } = context;

      if(query.phone === 'null') {
        query.phone = null;
      }

      context.params.query = query;

      return context;
    }
  }
});

另见 this issue.

注解

使用websockets时不会发生此问题, 因为它保留所有类型信息.

为什么带有数组的查询失败?

如果你正在使用REST和大型数组的查询(确切地说超过21个项目)失败, 那么你可能会遇到 querystring module的问题.默认情况下, 将数组的大小限制为21项 推荐的解决方案是通过 app.set('query parser', parserFunction) 实现自定义查询字符串解析器函数, 并将 arrayLimit 选项设置为更高的值:

var qs = require('qs');

app.set('query parser', function (str) {
  return qs.parse(str, {
    arrayLimit: 100
  });
});

For more information see the Express application settings @feathersjs/rest#88 and feathers-mongoose#205.

我的自定义中间件总是得到404

Just like in Express itself, the order of middleware matters. If you registered a custom middleware outside of the generator, you have to make sure that it runs before the notFound() error midlleware.

如何让OAuth跨不同的域工作

标准的Feathers oAuth设置将JWT设置在cookie中, 该cookie只能在同一个域之间传递.如果您的前端在不同的域上运行, 则必须使用查询字符串重定向, 如 此要点 中所述.

我的配置未加载

If you are running or requiring the Feathers app from a different folder 配置 needs to be instructed where the configuration files for the app are located. Since it uses node-config this can be done by setting the NODE_CONFIG_DIR envorinment variable.

如何设置HTTPS?

查看Feathers ../api/express#https.

Feathers 生产准备好了吗?

Yes! It’s being used in production by a bunch of companies from startups to fortune 500s. For some more details see this answer on Quora.