如何通过Django Channels操作Redis数据库并实现聊天室用户功能
Django Channels + Redis 实现聊天室在线人数与在场用户列表
一、用channels_layer.receive_count显示聊天室访问人数
Channels的Redis通道层提供group_receive_count方法,可直接统计指定组内的活跃连接数(即当前聊天室在线人数),核心逻辑是将每个进入聊天室的用户连接加入对应组,再通过该方法获取数量。
实现步骤:
复用现有Redis配置
在settings.py中配置Channels的Redis通道层,确保和Django现有Redis配置一致:CHANNEL_LAYERS = { "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { "hosts": [("127.0.0.1", 6379)], # 与Django Redis配置保持一致 # 有密码或指定数据库时补充参数 # "password": "your_redis_pwd", # "db": 0, }, }, }用户连接时加入聊天室组
在Consumer的connect方法中,将用户WebSocket连接绑定到对应聊天室的组:# consumers.py from channels.generic.websocket import AsyncWebsocketConsumer class ChatRoomConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_id = self.scope['url_route']['kwargs']['room_id'] self.room_group_name = f'chat_room_{self.room_id}' # 加入聊天室组 await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() async def disconnect(self, close_code): # 离开聊天室组 await self.channel_layer.group_discard( self.room_group_name, self.channel_name )获取并展示在线人数
在视图或API中,通过group_receive_count获取指定聊天室的人数:# views.py from channels.layers import get_channel_layer from asgiref.sync import async_to_sync def get_room_online_count(room_id): channel_layer = get_channel_layer() group_name = f'chat_room_{room_id}' # 异步方法转同步调用 count = async_to_sync(channel_layer.group_receive_count)(group_name) return count # 聊天室列表视图中调用 def chat_room_list(request): rooms = ChatRoom.objects.all() for room in rooms: room.online_count = get_room_online_count(room.id) return render(request, 'chat/room_list.html', {'rooms': rooms})
二、进入聊天室时列出当前在场用户信息
Channels组仅维护连接通道,不存储用户详情,需借助Redis单独维护每个聊天室的在线用户集合,存储用户ID、用户名等信息。
实现步骤:
连接时存储用户信息到Redis
在Consumer的connect方法中,认证用户后将其信息存入Redis哈希集合(按聊天室区分键名):# consumers.py import json from django.contrib.auth.models import User from django_redis import get_redis_connection from asgiref.sync import sync_to_async class ChatRoomConsumer(AsyncWebsocketConsumer): async def connect(self): # 验证用户登录状态 if self.scope['user'].is_anonymous: await self.close() return self.room_id = self.scope['url_route']['kwargs']['room_id'] self.room_group_name = f'chat_room_{self.room_id}' self.user = self.scope['user'] # 加入聊天室组 await self.channel_layer.group_add( self.room_group_name, self.channel_name ) # 同步存储用户信息到Redis await sync_to_async(self.save_online_user)() # 获取当前在线用户列表并发送给新用户 online_users = await sync_to_async(self.get_online_users)() await self.send(text_data=json.dumps({ 'type': 'online_users', 'users': online_users })) await self.accept() def save_online_user(self): redis_conn = get_redis_connection() redis_key = f'chat_room:{self.room_id}:online_users' # 用通道名作为哈希键,存储用户详情 redis_conn.hset(redis_key, self.channel_name, json.dumps({ 'id': self.user.id, 'username': self.user.username, 'avatar': self.user.profile.avatar.url if hasattr(self.user, 'profile') else '' })) # 设置过期时间,防止异常断开未清理 redis_conn.expire(redis_key, 86400) def get_online_users(self): redis_conn = get_redis_connection() redis_key = f'chat_room:{self.room_id}:online_users' # 解析所有在线用户信息 users_data = redis_conn.hvals(redis_key) return [json.loads(data) for data in users_data]断开连接时移除用户信息
在disconnect方法中,从Redis删除当前用户记录,并通知其他成员:async def disconnect(self, close_code): # 离开聊天室组 await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) # 同步删除Redis中的用户信息 await sync_to_async(self.remove_online_user)() # 通知所有成员更新用户列表 online_users = await sync_to_async(self.get_online_users)() await self.channel_layer.group_send( self.room_group_name, { 'type': 'online_users_update', 'users': online_users } ) def remove_online_user(self): redis_conn = get_redis_connection() redis_key = f'chat_room:{self.room_id}:online_users' redis_conn.hdel(redis_key, self.channel_name)处理用户列表更新通知
添加消息处理方法,将更新后的用户列表推送给所有成员:async def online_users_update(self, event): users = event['users'] await self.send(text_data=json.dumps({ 'type': 'online_users', 'users': users }))
前端渲染用户列表
前端WebSocket连接成功后,监听online_users类型消息,渲染在场用户:
const socket = new WebSocket('ws://' + window.location.host + '/chat/' + roomId + '/'); socket.onmessage = function(e) { const data = JSON.parse(e.data); if (data.type === 'online_users') { const userList = document.getElementById('online-users'); userList.innerHTML = ''; data.users.forEach(user => { const li = document.createElement('li'); li.innerHTML = `<img src="${user.avatar}" alt="avatar"> ${user.username}`; userList.appendChild(li); }); } };
内容的提问来源于stack exchange,提问作者KimGyeongMin-KR
相关产品推荐
相关产品推荐

