事件频道

在设置了实时传输(Primus)的Feathers服务器上,事件通道确定要发送的连接客户端 事件 to以及发送数据的外观.

本章介绍:

重要

如果您没有使用实时传输服务器(例如,在制作仅REST API或在客户端上使用Feathers时),则通道功能将无法使用.

使用频道的一些示例:

  • 实时事件应仅发送给经过身份验证的用户

  • 用户只有在加入某个聊天室时才能获得有关消息的更新

  • 只有同一组织中的用户才能收到有关其数据更改的实时更新

  • 创建新用户时, 只应通知管理员

  • 当用户被创建,修改或删除时,非管理员应该只接收用户对象的 “安全” 版本(例如,只有 email, idavatar)

例如

下面的例子显示了生成的 channels.js 文件,说明了不同部分如何组合在一起:

module.exports = function(app) {
  app.on('connection', connection => {
    // On a new real-time connection, add it to the
    // anonymous channel
    app.channel('anonymous').join(connection);
  });

  app.on('login', (payload, { connection }) => {
    // connection can be undefined if there is no
    // real-time connection, e.g. when logging in via REST
    if(connection) {
      const { user } = connection;

      // The connection is no longer anonymous, remove it
      app.channel('anonymous').leave(connection);

      // Add it to the authenticated user channel
      app.channel('authenticated').join(connection);

      // Channels can be named anything and joined on any condition
      // E.g. to send real-time events only to admins use

      // if(user.isAdmin) { app.channel('admins').join(connection); }

      // If the user has joined e.g. chat rooms

      // user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(connection))
    }
  });

  // A global publisher that sends all events to all authenticated clients
  app.publish((data, context) => {
    return app.channel('authenticated');
  });
};

连接

连接是表示实时连接的对象.它与 socket.feathers 中的 socket.feathers 在一个 Primus 中间件中与 ./socketiosocket.request.feathers 相同.您可以向其中添加任何类型的信息,但最值得注意的是,在使用 认证 时,它将包含经过身份验证的用户.默认情况下,一旦客户端在套接字上进行了身份验证(通常通过调用 Feathers 客户端),它就位于 connection.user 中.

我们可以通过监听``app.on(‘connection’,connection => {})``或``app.on(‘login’,(payload,{connection})来访问``connection``对象. )=> {})``.

注解

当连接终止时,它将自动从所有通道中删除.

app.on(‘connection’)

``app.on(‘connection’,connection => {})``每次建立新的实时连接时都会被触发.这是为匿名用户添加通道连接的好地方(如果我们想要向他们发送任何实时更新):

app.on('connection', connection => {
  // On a new real-time connection, add it to the
  // anonymous channel
  app.channel('anonymous').join(connection);
});

app.on(‘login’)

app.on('login', (payload, info) => {}) is sent by the 认证 and also contains the connection in the info object that is passed as the second parameter.

注解

that it can also be undefined if the login happened through e.g. REST which does not support real-time connectivity.

这是添加与用户相关的频道的连接的好地方(例如聊天室,管理状态等)

app.on('login', (payload, { connection }) => {
  // connection can be undefined if there is no
  // real-time connection, e.g. when logging in via REST
  if(connection) {
    // The user attached to this connection
    const { user } = connection;

    // The connection is no longer anonymous, remove it
    app.channel('anonymous').leave(connection);

    // Add it to the authenticated user channel
    app.channel('authenticated').join(connection);

    // Channels can be named anything and joined on any condition `
    // E.g. to send real-time events only to admins use
    if(user.isAdmin) {
      app.channel('admins').join(connection);
    }

    // If the user has joined e.g. chat rooms
    user.rooms.forEach(room => {
      app.channel(`rooms/${room.id}`).join(connection);
    });
  }
});

注解

``(user,{connection})``是``(user,meta)=> {const connection = meta.connection; ``,参见`解构赋值<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment>`_.

app.on(‘logout’)

``app.on(‘logout’,(payload,info)=> {})``由:doc:`./authentication/server`发送,并且还包含``info``对象中的连接当注销发生时,将作为第二个参数传递.

如果套接字在注销时也没有断开连接,则应将用户从其频道中删除:

app.on('logout', (payload, { connection }) => {
  if(connection) {
    //When logging out, leave all channels before joining anonymous channel
    app.channel(app.channels).leave(connection);
    app.channel('anonymous').join(connection);
  }
});

频道

通道是包含许多连接的对象.它可以通过``app.channel``创建,并允许连接加入或离开.

app.channel(…names)

app.channel(name) -> Channel, 当给出单个名称时,返回现有或新命名的通道:

app.channel('admins') // the admin channel
app.channel('authenticated') // the authenticated channel

app.channel(name1,name2,... nameN) - > Channel,当给定多个名称时,将返回一个组合通道.组合通道包含所有连接的列表(没有重复),并重定向``channel.join``和``channel.leave``调用所有子通道.

// Combine the anonymous and authenticated channel
const combinedChannel = app.channel('anonymous', 'authenticated')

// Join the `anonymous` and `authenticated` channel
combinedChannel.join(connection);

// Join the `admins` and `chat` channel
app.channel('admins', 'chat').join(connection);

// Leave the `admins` and `chat` channel
app.channel('admins', 'chat').leave(connection);

// Make user with `_id` 5 leave the admins and chat channel
app.channel('admins', 'chat').leave(connection => {
  return connection.user._id === 5;
});

app.channels

``app.channels - > [string]``返回所有现有频道名称的列表.

app.channel('authenticated');
app.channel('admins', 'users');

app.channels // [ 'authenticated', 'admins', 'users' ]

app.channel(app.channels) // will return a channel with all connections

这对于例如从所有频道中删除连接:

// When a user is removed, make all their connections leave every channel
app.service('users').on('removed', user => {
  app.channel(app.channels).leave(connection => {
    return user._id === connection.user._id;
  });
});

channel.join(connection)

``channel.join(connection) - > Channel``添加了一个到这个频道的连接.如果频道是组合频道,请将连接添加到其所有子频道.如果连接已经在通道中,它什么都不做.返回通道对象.

app.on('login', (payload, { connection }) => {
  if(connection && connection.user.isAdmin) {
    // Join the admins channel
    app.channel('admins').join(connection);

    // Calling a second time will do nothing
    app.channel('admins').join(connection);
  }
});

channel.leave(connection|fn)

``channel.leave(connection | fn) - > Channel``删除该通道的连接.如果频道是组合频道,请从其所有子频道中删除该连接.还允许传递为每个连接运行的回调,如果应该删除连接则返回.返回通道对象.

// Make the user with `_id` 5 leave the `admins` channel
app.channel('admins').leave(connection => {
  return connection.user._id === 5;
});

channel.filter(fn)

``channel.filter(fn) - > Channel``返回由给定函数过滤的新通道,该通道将通过连接.

// Returns a new channel with all connections of the user with `_id` 5
const userFive = app.channel(app.channels)
  .filter(connection => connection.user._id === 5);

channel.send(data)

channel.send(data) -> Channel 返回此频道的副本,其中包含应为此事件发送的自定义数据.通常这应该通过修改服务方法结果或在``context.dispatch``中设置客户端“安全”数据来处理,但在某些情况下,仍然可以更改某些通道的事件数据.

将按照以下顺序确定的第一个数据将确定将发送哪些数据:

  1. data from channel.send(data)

  2. context.dispatch

  3. context.result

app.on('connection', connection => {
  // On a new real-time connection, add it to the
  // anonymous channel
  app.channel('anonymous').join(connection);
});

// Send the `users` `created` event to all anonymous
// users but use only the name as the payload
app.service('users').publish('created', data => {
  return app.channel('anonymous').send({
    name: data.name
  });
});

注解

If a connection is in multiple channels (e.g. users and admins) it will get the data from the first channel that it is in.

channel.connections

channel.connections -> [ object ] 包含此频道中所有连接的列表.

channel.length

channel.length -> integer 返回此通道中的连接总数.

出版

发布者是回调函数,用于返回将事件发送到的通道.它们可以在应用程序和服务级别以及所有或特定事件中注册.发布函数获取事件数据和上下文对象((data,context)=> {})并返回一个命名或组合通道,一个通道数组或``null``.一种类型只能注册一个发布者.除了标准:ref:events_service-events,事件名称也可以是:ref:events_custom-events. ``context``是来自服务调用的:doc:`./hooks`或包含``{path,service,app,result}``的自定义事件的对象.

service.publish([event,] fn)

service.publish([event,] fn) -> service 如果没有给出事件名称,则为特定事件或所有事件注册特定服务的发布功能.

app.on('login', (payload, { connection }) => {
  // connection can be undefined if there is no
  // real-time connection, e.g. when logging in via REST
  if(connection && connection.user.isAdmin) {
    app.channel('admins').join(connection);
  }
});

// Publish all messages service events only to its room channel
app.service('messages').publish((data, context) => {
  return app.channel(`rooms/${data.roomId}`);
});

// Publish the `created` event to admins and the user that sent it
app.service('users').publish('created', (data, context) => {
  return [
    app.channel('admins'),
    app.channel(app.channels).filter(connection =>
      connection.user._id === context.params.user._id
    )
  ];
});

// Prevent all events in the `password-reset` service from being published
app.service('password-reset').publish(() => null);

app.publish([event,] fn)

app.publish([event,] fn) -> app 如果没有给出事件名称,则为特定事件或所有事件的所有服务注册发布功能.

app.on('login', (payload, { connection }) => {
  // connection can be undefined if there is no
  // real-time connection, e.g. when logging in via REST
  if(connection) {
    app.channel('authenticated').join(connection);
  }
});

// Publish all events to all authenticated users
app.publish((data, context) => {
  return app.channel('authenticated');
});

// Publish the `log` custom event to all connections
app.publish('log', (data, context) => {
  return app.channel(app.channels);
});

发布者优先级

将使用按以下顺序找到的第一个发布者回调:

  1. 特定活动的服务发布者

  2. 所有活动的服务发布者

  3. 针对特定活动的应用发布商

  4. 适用于所有活动的应用发布商

保持频道更新

S每个应用程序都会有所不同,保持分配给频道的连接是最新的(例如,如果用户加入/离开房间)由您决定.

通常,渠道应反映您的持久性应用程序数据.这意味着它通常不需要例如要求直接加入频道的用户.这在运行应用程序的多个实例时尤为重要,因为通道只是当前实例的* local *.

相反,相关信息(例如,用户当前所在的房间)应该存储在数据库中,然后可以将事件连接重新分配到适当的通道中,听取正确的:doc:./events.

以下示例更新或删除用户对象(假定其“room”数组是用户已加入的房间ID列表)时给定用户的所有活动连接:

// Join a channel given a user and connection
const joinChannels = (user, connection) => {
  app.channel('authenticated').join(connection);
  // Assuming that the chat room/user assignment is stored
  // on an array of the user
  user.rooms.forEach(room =>
    app.channel(`rooms/${roomId}`).join(connection)
  );
}

// Get a user to leave all channels
const leaveChannels = user => {
  app.channel(app.channels).leave(connection =>
    connection.user._id === user._id
  );
};

// Leave and re-join all channels with new user information
const updateChannels = user => {
  // Find all connections for this user
  const { connections } = app.channel(app.channels).filter(connection =>
    connection.user._id === user._id
  );

  // Leave all channels
  leaveChannels(user);

  // Re-join all channels with the updated user information
  connections.forEach(connection => joinChannels(user, connection));
}

app.on('login', (payload, { connection }) => {
  if(connection) {
    // Join all channels on login
    joinChannels(connection.user, connection);
  }
});

// On `updated` and `patched`, leave and re-join with new room assignments
app.service('users').on('updated', updateChannels);
app.service('users').on('patched', updateChannels);
// On `removed`, remove the connection from all channels
app.service('users').on('removed', leaveChannels);

注解

活动连接数通常是一个(或没有),但除非您明确阻止它,否则Feathers不会阻止同一用户的多次登录(例如,有两个打开的浏览器窗口或移动设备).