tunnel.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. const { tunnel } = require('../qcloud')
  2. const debug = require('debug')('koa-weapp-demo')
  3. /**
  4. * 这里实现一个简单的聊天室
  5. * userMap 为 tunnelId 和 用户信息的映射
  6. * 实际使用请使用数据库存储
  7. */
  8. const userMap = {}
  9. // 保存 当前已连接的 WebSocket 信道ID列表
  10. const connectedTunnelIds = []
  11. /**
  12. * 调用 tunnel.broadcast() 进行广播
  13. * @param {String} type 消息类型
  14. * @param {String} content 消息内容
  15. */
  16. const $broadcast = (type, content) => {
  17. tunnel.broadcast(connectedTunnelIds, type, content)
  18. .then(result => {
  19. const invalidTunnelIds = result.data && result.data.invalidTunnelIds || []
  20. if (invalidTunnelIds.length) {
  21. console.log('检测到无效的信道 IDs =>', invalidTunnelIds)
  22. // 从 userMap 和 connectedTunnelIds 中将无效的信道记录移除
  23. invalidTunnelIds.forEach(tunnelId => {
  24. delete userMap[tunnelId]
  25. const index = connectedTunnelIds.indexOf(tunnelId)
  26. if (~index) {
  27. connectedTunnelIds.splice(index, 1)
  28. }
  29. })
  30. }
  31. })
  32. }
  33. /**
  34. * 调用 TunnelService.closeTunnel() 关闭信道
  35. * @param {String} tunnelId 信道ID
  36. */
  37. const $close = (tunnelId) => {
  38. tunnel.closeTunnel(tunnelId)
  39. }
  40. /**
  41. * 实现 onConnect 方法
  42. * 在客户端成功连接 WebSocket 信道服务之后会调用该方法,
  43. * 此时通知所有其它在线的用户当前总人数以及刚加入的用户是谁
  44. */
  45. function onConnect (tunnelId) {
  46. console.log(`[onConnect] =>`, { tunnelId })
  47. if (tunnelId in userMap) {
  48. connectedTunnelIds.push(tunnelId)
  49. $broadcast('people', {
  50. 'total': connectedTunnelIds.length,
  51. 'enter': userMap[tunnelId]
  52. })
  53. } else {
  54. console.log(`Unknown tunnelId(${tunnelId}) was connectd, close it`)
  55. $close(tunnelId)
  56. }
  57. }
  58. /**
  59. * 实现 onMessage 方法
  60. * 客户端推送消息到 WebSocket 信道服务器上后,会调用该方法,此时可以处理信道的消息。
  61. * 在本示例,我们处理 `speak` 类型的消息,该消息表示有用户发言。
  62. * 我们把这个发言的信息广播到所有在线的 WebSocket 信道上
  63. */
  64. function onMessage (tunnelId, type, content) {
  65. console.log(`[onMessage] =>`, { tunnelId, type, content })
  66. switch (type) {
  67. case 'speak':
  68. if (tunnelId in userMap) {
  69. $broadcast('speak', {
  70. 'who': userMap[tunnelId],
  71. 'word': content.word
  72. })
  73. } else {
  74. $close(tunnelId)
  75. }
  76. break
  77. default:
  78. break
  79. }
  80. }
  81. /**
  82. * 实现 onClose 方法
  83. * 客户端关闭 WebSocket 信道或者被信道服务器判断为已断开后,
  84. * 会调用该方法,此时可以进行清理及通知操作
  85. */
  86. function onClose (tunnelId) {
  87. console.log(`[onClose] =>`, { tunnelId })
  88. if (!(tunnelId in userMap)) {
  89. console.log(`[onClose][Invalid TunnelId]=>`, tunnelId)
  90. $close(tunnelId)
  91. return
  92. }
  93. const leaveUser = userMap[tunnelId]
  94. delete userMap[tunnelId]
  95. const index = connectedTunnelIds.indexOf(tunnelId)
  96. if (~index) {
  97. connectedTunnelIds.splice(index, 1)
  98. }
  99. // 聊天室没有人了(即无信道ID)不再需要广播消息
  100. if (connectedTunnelIds.length > 0) {
  101. $broadcast('people', {
  102. 'total': connectedTunnelIds.length,
  103. 'leave': leaveUser
  104. })
  105. }
  106. }
  107. module.exports = {
  108. // 小程序请求 websocket 地址
  109. get: async ctx => {
  110. const data = await tunnel.getTunnelUrl(ctx.req)
  111. const tunnelInfo = data.tunnel
  112. userMap[tunnelInfo.tunnelId] = data.userinfo
  113. ctx.state.data = tunnelInfo
  114. },
  115. // 信道将信息传输过来的时候
  116. post: async ctx => {
  117. const packet = await tunnel.onTunnelMessage(ctx.request.body)
  118. debug('Tunnel recive a package: %o', packet)
  119. switch (packet.type) {
  120. case 'connect':
  121. onConnect(packet.tunnelId)
  122. break
  123. case 'message':
  124. onMessage(packet.tunnelId, packet.content.messageType, packet.content.messageContent)
  125. break
  126. case 'close':
  127. onClose(packet.tunnelId)
  128. break
  129. }
  130. }
  131. }