使用 AirCode,5分钟实现多人实时聊天

1,807 阅读3分钟

AirCode 是一个在线开发和部署 Node.js 应用的平台,为全栈工程师量身定制,目标是让开发变得简单。

最近我为 AirCode 封装了 Pusher API,Pusher是一家专注提供实时通信服务的 SaaS 服务商。

我将 AirCode 和 Pusher 结合起来,发现实现一个在线实时通信服务变得非常简单。

我们来看一下究竟如何简单。

创建服务

首先我们在AirCode上创建一个项目;

image.png

进入创建好的项目,我们安装 aircode-pusher:

image.png

然后改写一下hello.js

const {Pusher} = require('aircode-pusher');

const pusher = new Pusher(Pusher.DEBUG_CONFIG);

// Create a channel
const channel = pusher.channel('my-channel');

channel.subscribe('my-event', async ({data}) => {
  channel.trigger('my-event', {ack: 'received', raw: data});
});

module.exports = pusher.listen();

这样我们就通过几行代码实现了一个最简单的实时通信服务,在代码里,我们创建了一个频道my-channel,在这个频道里,我们订阅了my-event事件,在订阅事件的回调里,我们简单的把原始数据给推送(广播)回去,这样有一个客户端发送了这个事件,所有订阅该频道的客户端都会收到广播消息。

我们可以测试一下。

发布和测试

首先在AirCode的IDE中点击Deploy发布这个服务

image.png

发布后,我们就能获得服务的线上URL:

image.png

接着我们在码上掘金创建一个项目,选择添加依赖脚本,添加aircode-pusher的客户端代码。

image.png

接着我们通过线上服务的地址创建客户端对象:

const pusher = new Pusher('https://21j8c76823.us.aircode.run/hello');

然后我们建立连接,获得订阅的频道,并监听和发送事件:

(async() => {
  const channels = await pusher.connect();

  channels[0].bind('my-event', (event) => {
    console.log(event);
  });
  await channels[0].send('my-event', {message: 'Hello'});
})();

点击运行代码,稍等片刻就能看到控制台上服务端返回的内容:

image.png

实现聊天程序

接下来,我们来实现聊天程序,我们在AirCode项目里再创建一个chat.js的云函数,内容如下:

const {db} = require('aircode');
const {Pusher} = require('aircode-pusher');
const pusher = new Pusher(Pusher.DEBUG_CONFIG);

const historyTable = db.table('messages');
const channel = pusher.channel('chat-channel');

channel.subscribe('chat', async ({event, data}) => {
  await historyTable.save(data);
  await channel.trigger(event, data);
});

channel.subscribe('get-history', async ({event, data, channel}) => {
  // Get history messages
  const count = data.count || 10;

  const messages = await historyTable.where()
    .sort({createdAt: -1})
    .projection({user: true, message: true, datetime: true})
    .limit(10).find();

  channel.responseBody = {messages};
});

module.exports = pusher.listen();

上面的代码,我们订阅了chat事件和get-history事件,其中get-history事件我们是获取历史聊天记录,该事件只需要执行一次,并且立即返回结果,我们可以通过channel.responseBody直接返回,因为Pusher的客户端发送请求走的是HTTP,而监听数据才是WebSocket,所以我们直接可以通过responseBody拿到HTTP的响应数据,这样就省去了一次WebSocket数据发送。

对应的,我们实现客户端安代码:

<div id="chatbox">
</div>
<div class="chatbar">
  <input id="chat" placeholder="just say something..." disabled></input>
  <button id="postchat" disabled>post</button>
</div>
html, body {
  padding: 0;
  margin: 0;
}

#chatbox {
  box-sizing: border-box;
  width: 90vw;
  height: 80vh;
  margin: 10px;
  padding: 6px;
  border: solid 1px #ddd;
}

.chatbar {
  margin-top: 10px;
  padding-left: 10px;
}

.chatbar #chat {
  width: 80vw;
  height: 2rem;
  border: solid 1px #ddd;
}

#chat {
  padding-left: 4px;
}
const logger = JCode.logger(chatbox);
function log(data) {
    logger.log('%c %s: %c%s %c[%s]', 'color:blue', data.user, 'color:#333', data.message, 'color:#999', data.datetime);
}

const pusher = new Pusher('https://21j8c76823.us.aircode.run/chat');

(async () => {
  // 建立到服务的连接,并获取服务端订阅的所有频道
  const channels = await pusher.connect();

  const user = localStorage.getItem('userName') || 'user-' + Math.random().toString(16).slice(2, 8);
  localStorage.setItem('userName', user);

  // 通过 bind 监听数据
  channels[0].bind('chat', (data) => {
    console.log(data);
    log(data);
  });

  const {messages} = await channels[0].send('get-history', '');

  postchat.disabled = false;
  chat.disabled = false;
  logger.log('初始化完毕');
  logger.log('<hr/>');

  messages.reverse().forEach(log);

  postchat.addEventListener('click', (event) => {
    event.preventDefault();
    if(chat.value) {
      const now = (new Date()).toLocaleString();
      const data = {
        user,
        message: chat.value,
        datetime: now
      }
      channels[0].send('chat', data);
      chat.value = '';
    }
  });
  chat.addEventListener('keydown', (event) => {
    if(event.key === 'Enter') {
      const now = (new Date()).toLocaleString();
      if(chat.value) {
        const data = {
          user,
          message: chat.value,
          datetime: now
        }
        channels[0].send('chat', data);
        chat.value = '';
      }
    }
  });
})();

我们的输出使用了JCode-tools,所以添加这部分依赖。

image.png

这样我们就完成了所有的功能,最终效果如下:

一个聊天室就这么简简单单完成了,你是否也想亲手试一试,可以按照上面步骤操作。有任何问题,欢迎在评论区讨论。

不过,最后提醒一下,服务端的 const pusher = new Pusher(Pusher.DEBUG_CONFIG); 用的是测试配置,如果你想上线你自己的服务,最好自己去 Pusher.com 免费注册一个账号,然后将配置信息替换成你自己的配置。

具体使用方法,详见 AirCode 的 GitHub 仓库