From 938f758920f830e24860be8b3886c014221db402 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sun, 6 Oct 2019 16:20:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8E=89=E7=BA=BF=E9=87=8D?= =?UTF-8?q?=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/Room.vue | 64 +++++++++++---- views/chat.py | 157 +++++++++++++++++++++--------------- 2 files changed, 139 insertions(+), 82 deletions(-) diff --git a/frontend/src/views/Room.vue b/frontend/src/views/Room.vue index 9e7fad2..3b87b79 100644 --- a/frontend/src/views/Room.vue +++ b/frontend/src/views/Room.vue @@ -7,7 +7,7 @@ import config from '@/api/config' import ChatRenderer from '@/components/ChatRenderer' import * as constants from '@/components/ChatRenderer/constants' -// const COMMAND_HEARTBEAT = 0 +const COMMAND_HEARTBEAT = 0 const COMMAND_JOIN_ROOM = 1 const COMMAND_ADD_TEXT = 2 const COMMAND_ADD_GIFT = 3 @@ -27,7 +27,12 @@ export default { cfg.maxSpeed = 0 return { config: cfg, + websocket: null, + retryCount: 0, + isDestroying: false, + heartbeatTimerId: null, + messagesBufferTimerId: null, nextId: 0, messagesBuffer: [], // 暂时不显示的消息,可能会丢弃 @@ -35,28 +40,17 @@ export default { paidMessages: [] } }, - async created() { - // 开发时使用localhost:12450 - const url = process.env.NODE_ENV === 'development' ? 'ws://localhost:12450/chat' : `ws://${window.location.host}/chat` - this.websocket = new WebSocket(url) - this.websocket.onopen = this.onWsOpen - this.websocket.onmessage = this.onWsMessage - + created() { + this.wsConnect() if (this.$route.query.config_id) { - try { - let cfg = await config.getRemoteConfig(this.$route.query.config_id) - cfg.blockKeywords = cfg.blockKeywords.split('\n').filter(val => val) - cfg.blockUsers = cfg.blockUsers.split('\n').filter(val => val) - this.config = cfg - } catch (e) { - this.$message.error('获取配置失败:' + e) - } + this.updateConfig(this.$route.query.config_id) } }, beforeDestroy() { if (this.messagesBufferTimerId) { window.clearInterval(this.messagesBufferTimerId) } + this.isDestroying = true this.websocket.close() }, watch: { @@ -66,12 +60,37 @@ export default { this.messagesBufferTimerId = null } if (val.maxSpeed > 0) { - this.messagesBufferTimerId = window.setInterval(this.handleMessagesBuffer.bind(this), 1000 / val.maxSpeed) + this.messagesBufferTimerId = window.setInterval(this.handleMessagesBuffer, 1000 / val.maxSpeed) } } }, methods: { + async updateConfig(configId) { + try { + let cfg = await config.getRemoteConfig(configId) + cfg.blockKeywords = cfg.blockKeywords.split('\n').filter(val => val) + cfg.blockUsers = cfg.blockUsers.split('\n').filter(val => val) + this.config = cfg + } catch (e) { + this.$message.error('获取配置失败:' + e) + } + }, + wsConnect() { + // 开发时使用localhost:12450 + const url = process.env.NODE_ENV === 'development' ? 'ws://localhost:12450/chat' : `ws://${window.location.host}/chat` + this.websocket = new WebSocket(url) + this.websocket.onopen = this.onWsOpen + this.websocket.onclose = this.onWsClose + this.websocket.onmessage = this.onWsMessage + this.heartbeatTimerId = window.setInterval(this.sendHeartbeat, 10 * 1000) + }, + sendHeartbeat() { + this.websocket.send(JSON.stringify({ + cmd: COMMAND_HEARTBEAT + })) + }, onWsOpen() { + this.retryCount = 0 this.websocket.send(JSON.stringify({ cmd: COMMAND_JOIN_ROOM, data: { @@ -79,6 +98,17 @@ export default { } })) }, + onWsClose() { + if (this.heartbeatTimerId) { + window.clearInterval(this.heartbeatTimerId) + this.heartbeatTimerId = null + } + if (this.isDestroying) { + return + } + window.console.log(`掉线重连中${++this.retryCount}`) + this.wsConnect() + }, onWsMessage(event) { let {cmd, data} = JSON.parse(event.data) let message = null diff --git a/views/chat.py b/views/chat.py index 7c14b7d..4ab8722 100644 --- a/views/chat.py +++ b/views/chat.py @@ -181,76 +181,49 @@ class RoomManager: def __init__(self): self._rooms: Dict[int, Room] = {} - def add_client(self, room_id, client: 'ChatHandler'): - if room_id in self._rooms: - room = self._rooms[room_id] - else: - logger.info('Creating room %d', room_id) - room = Room(room_id) - self._rooms[room_id] = room - room.start() + async def add_client(self, room_id, client: 'ChatHandler'): + if room_id not in self._rooms: + if not await self._add_room(room_id): + client.close() + return + room = self._rooms[room_id] room.clients.append(client) + logger.info('%d clients in room %s', len(room.clients), room_id) if client.application.settings['debug']: - self.__send_test_message(room) + client.send_test_message() def del_client(self, room_id, client: 'ChatHandler'): if room_id not in self._rooms: return room = self._rooms[room_id] room.clients.remove(client) + logger.info('%d clients in room %s', len(room.clients), room_id) if not room.clients: - logger.info('Removing room %d', room_id) - room.stop_and_close() - del self._rooms[room_id] + self._del_room(room_id) - # 测试用 - @staticmethod - def __send_test_message(room): - base_data = { - 'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h', - 'timestamp': time.time(), - 'authorName': 'xfgryujk', - } - text_data = { - **base_data, - 'authorType': 0, - 'content': '我能吞下玻璃而不伤身体', - 'privilegeType': 0, - 'isGiftDanmaku': False, - 'authorLevel': 20, - 'isNewbie': False, - 'isMobileVerified': True - } - member_data = base_data - gift_data = { - **base_data, - 'giftName': '摩天大楼', - 'giftNum': 1, - 'totalCoin': 450000 - } - sc_data = { - **base_data, - 'price': 30, - 'content': 'The quick brown fox jumps over the lazy dog', - 'id': 1 - } - room.send_message(Command.ADD_TEXT, text_data) - text_data['authorName'] = '主播' - text_data['authorType'] = 3 - text_data['content'] = "I can eat glass, it doesn't hurt me." - room.send_message(Command.ADD_TEXT, text_data) - room.send_message(Command.ADD_MEMBER, member_data) - room.send_message(Command.ADD_SUPER_CHAT, sc_data) - sc_data['price'] = 100 - sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗' - sc_data['id'] = 2 - room.send_message(Command.ADD_SUPER_CHAT, sc_data) - # room.send_message(Command.DEL_SUPER_CHAT, {'ids': [1, 2]}) - room.send_message(Command.ADD_GIFT, gift_data) - gift_data['giftName'] = '小电视飞船' - gift_data['totalCoin'] = 1245000 - room.send_message(Command.ADD_GIFT, gift_data) + async def _add_room(self, room_id): + if room_id in self._rooms: + return True + logger.info('Creating room %d', room_id) + room = Room(room_id) + self._rooms[room_id] = room + if await room.init_room(): + room.start() + return True + else: + self._del_room(room_id) + return False + + def _del_room(self, room_id): + if room_id not in self._rooms: + return + logger.info('Removing room %d', room_id) + room = self._rooms[room_id] + for client in room.clients: + client.close() + room.stop_and_close() + del self._rooms[room_id] room_manager = RoomManager() @@ -266,18 +239,21 @@ class ChatHandler(tornado.websocket.WebSocketHandler): logger.info('Websocket connected %s', self.request.remote_ip) def on_message(self, message): - if self.room_id is not None: - return body = json.loads(message) - if body['cmd'] == Command.JOIN_ROOM: + cmd = body['cmd'] + if cmd == Command.HEARTBEAT: + pass + elif cmd == Command.JOIN_ROOM: + if self.room_id is not None: + return self.room_id = int(body['data']['roomId']) logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id) - room_manager.add_client(self.room_id, self) + asyncio.ensure_future(room_manager.add_client(self.room_id, self)) else: - logger.warning('Unknown cmd: %s data: %s', body['cmd'], body['data']) + logger.warning('Unknown cmd: %s body: %s', cmd, body) def on_close(self): - logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, self.room_id) + logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, str(self.room_id)) if self.room_id is not None: room_manager.del_client(self.room_id, self) @@ -286,3 +262,54 @@ class ChatHandler(tornado.websocket.WebSocketHandler): if self.application.settings['debug']: return True return super().check_origin(origin) + + # 测试用 + def send_test_message(self): + base_data = { + 'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h', + 'timestamp': time.time(), + 'authorName': 'xfgryujk', + } + text_data = { + **base_data, + 'authorType': 0, + 'content': '我能吞下玻璃而不伤身体', + 'privilegeType': 0, + 'isGiftDanmaku': False, + 'authorLevel': 20, + 'isNewbie': False, + 'isMobileVerified': True + } + member_data = base_data + gift_data = { + **base_data, + 'giftName': '摩天大楼', + 'giftNum': 1, + 'totalCoin': 450000 + } + sc_data = { + **base_data, + 'price': 30, + 'content': 'The quick brown fox jumps over the lazy dog', + 'id': 1 + } + self.send_message(Command.ADD_TEXT, text_data) + text_data['authorName'] = '主播' + text_data['authorType'] = 3 + text_data['content'] = "I can eat glass, it doesn't hurt me." + self.send_message(Command.ADD_TEXT, text_data) + self.send_message(Command.ADD_MEMBER, member_data) + self.send_message(Command.ADD_SUPER_CHAT, sc_data) + sc_data['price'] = 100 + sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗' + sc_data['id'] = 2 + self.send_message(Command.ADD_SUPER_CHAT, sc_data) + # self.send_message(Command.DEL_SUPER_CHAT, {'ids': [1, 2]}) + self.send_message(Command.ADD_GIFT, gift_data) + gift_data['giftName'] = '小电视飞船' + gift_data['totalCoin'] = 1245000 + self.send_message(Command.ADD_GIFT, gift_data) + + def send_message(self, cmd, data): + body = json.dumps({'cmd': cmd, 'data': data}) + self.write_message(body)