const { tunnel } = require('../qcloud') const debug = require('debug')('koa-weapp-demo') /** * 这里实现一个简单的聊天室 * userMap 为 tunnelId 和 用户信息的映射 * 实际使用请使用数据库存储 */ const userMap = {} // 保存 当前已连接的 WebSocket 信道ID列表 const connectedTunnelIds = [] /** * 调用 tunnel.broadcast() 进行广播 * @param {String} type 消息类型 * @param {String} content 消息内容 */ const $broadcast = (type, content) => { tunnel.broadcast(connectedTunnelIds, type, content) .then(result => { const invalidTunnelIds = result.data && result.data.invalidTunnelIds || [] if (invalidTunnelIds.length) { console.log('检测到无效的信道 IDs =>', invalidTunnelIds) // 从 userMap 和 connectedTunnelIds 中将无效的信道记录移除 invalidTunnelIds.forEach(tunnelId => { delete userMap[tunnelId] const index = connectedTunnelIds.indexOf(tunnelId) if (~index) { connectedTunnelIds.splice(index, 1) } }) } }) } /** * 调用 TunnelService.closeTunnel() 关闭信道 * @param {String} tunnelId 信道ID */ const $close = (tunnelId) => { tunnel.closeTunnel(tunnelId) } /** * 实现 onConnect 方法 * 在客户端成功连接 WebSocket 信道服务之后会调用该方法, * 此时通知所有其它在线的用户当前总人数以及刚加入的用户是谁 */ function onConnect (tunnelId) { console.log(`[onConnect] =>`, { tunnelId }) if (tunnelId in userMap) { connectedTunnelIds.push(tunnelId) $broadcast('people', { 'total': connectedTunnelIds.length, 'enter': userMap[tunnelId] }) } else { console.log(`Unknown tunnelId(${tunnelId}) was connectd, close it`) $close(tunnelId) } } /** * 实现 onMessage 方法 * 客户端推送消息到 WebSocket 信道服务器上后,会调用该方法,此时可以处理信道的消息。 * 在本示例,我们处理 `speak` 类型的消息,该消息表示有用户发言。 * 我们把这个发言的信息广播到所有在线的 WebSocket 信道上 */ function onMessage (tunnelId, type, content) { console.log(`[onMessage] =>`, { tunnelId, type, content }) switch (type) { case 'speak': if (tunnelId in userMap) { $broadcast('speak', { 'who': userMap[tunnelId], 'word': content.word }) } else { $close(tunnelId) } break default: break } } /** * 实现 onClose 方法 * 客户端关闭 WebSocket 信道或者被信道服务器判断为已断开后, * 会调用该方法,此时可以进行清理及通知操作 */ function onClose (tunnelId) { console.log(`[onClose] =>`, { tunnelId }) if (!(tunnelId in userMap)) { console.log(`[onClose][Invalid TunnelId]=>`, tunnelId) $close(tunnelId) return } const leaveUser = userMap[tunnelId] delete userMap[tunnelId] const index = connectedTunnelIds.indexOf(tunnelId) if (~index) { connectedTunnelIds.splice(index, 1) } // 聊天室没有人了(即无信道ID)不再需要广播消息 if (connectedTunnelIds.length > 0) { $broadcast('people', { 'total': connectedTunnelIds.length, 'leave': leaveUser }) } } module.exports = { // 小程序请求 websocket 地址 get: async ctx => { const data = await tunnel.getTunnelUrl(ctx.req) const tunnelInfo = data.tunnel userMap[tunnelInfo.tunnelId] = data.userinfo ctx.state.data = tunnelInfo }, // 信道将信息传输过来的时候 post: async ctx => { const packet = await tunnel.onTunnelMessage(ctx.request.body) debug('Tunnel recive a package: %o', packet) switch (packet.type) { case 'connect': onConnect(packet.tunnelId) break case 'message': onMessage(packet.tunnelId, packet.content.messageType, packet.content.messageContent) break case 'close': onClose(packet.tunnelId) break } } }