import json from channels.db import database_sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer from .models import BoardSession class BoardConsumer(AsyncJsonWebsocketConsumer): async def connect(self): self.session_id = self.scope["url_route"]["kwargs"]["session_id"] self.group_name = f"board_{self.session_id}" user = self.scope["user"] if not user.is_authenticated or not await self._is_participant(user.id): await self.close(code=4403) return await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() await self.send_json( { "type": "connected", "session_id": str(self.session_id), } ) async def disconnect(self, close_code): if hasattr(self, "group_name"): await self.channel_layer.group_discard( self.group_name, self.channel_name, ) async def receive_json(self, content, **kwargs): event_type = content.get("type") if event_type == "ping": await self.send_json({"type": "pong"}) return if event_type not in {"canvas", "geometry"}: await self.send_json({"type": "error", "message": "不支持的画板消息"}) return if not await self._is_active(): await self.send_json({"type": "error", "message": "画板尚未开始或已经结束"}) return payload = content.get("payload") if event_type == "canvas": valid = ( isinstance(payload, str) and payload.startswith("data:image/") and len(payload) <= 700_000 ) else: valid = isinstance(payload, dict) and len( json.dumps(payload, ensure_ascii=False) ) <= 100_000 if not valid: await self.send_json({"type": "error", "message": "画板消息无效或过大"}) return await self.channel_layer.group_send( self.group_name, { "type": "board.update", "event_type": event_type, "payload": payload, "user_id": str(self.scope["user"].id), }, ) async def board_update(self, event): await self.send_json( { "type": event["event_type"], "payload": event["payload"], "user_id": event["user_id"], } ) async def board_state(self, event): await self.send_json( { "type": "state", "reason": event["reason"], "session_id": str(self.session_id), } ) @database_sync_to_async def _is_participant(self, user_id): return BoardSession.objects.filter(id=self.session_id).filter( host_id=user_id ).exists() or BoardSession.objects.filter( id=self.session_id, guest_id=user_id, ).exists() @database_sync_to_async def _is_active(self): return BoardSession.objects.filter( id=self.session_id, status=BoardSession.Status.ACTIVE, ).exists()