Django + MySQL + WebSocket 实现在线聊天室与消息记录系统
Django MySQL WebSocket 在线聊天室实现我来为你构建一个完整的在线聊天室系统包含实时通信和消息记录功能。项目结构chat_project/ ├── chat_project/ │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── asgi.py ├── chat_app/ │ ├── __init__.py │ ├── admin.py │ ├── consumers.py │ ├── models.py │ ├── routing.py │ ├── urls.py │ ├── views.py │ └── templates/ │ └── chat_app/ │ ├── index.html │ └── room.html └── manage.py1. 安装依赖pip install django channels channels-redis mysqlclient2. 配置 settings.pyimport os from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent SECRET_KEY your-secret-key DEBUG True ALLOWED_HOSTS [*] INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, channels, # 添加 channels chat_app, # 聊天应用 ] MIDDLEWARE [ django.middleware.security.SecurityMiddleware, django.contrib.sessions.middleware.SessionMiddleware, django.middleware.common.CommonMiddleware, django.middleware.csrf.CsrfViewMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, django.contrib.messages.middleware.MessageMiddleware, django.middleware.clickjacking.XFrameOptionsMiddleware, ] ROOT_URLCONF chat_project.urls TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [], APP_DIRS: True, OPTIONS: { context_processors: [ django.template.context_processors.debug, django.template.context_processors.request, django.contrib.auth.context_processors.auth, django.contrib.messages.context_processors.messages, ], }, }, ] WSGI_APPLICATION chat_project.wsgi.application ASGI_APPLICATION chat_project.asgi.application # ASGI 配置 # 数据库配置 - MySQL DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: chat_db, USER: root, PASSWORD: your_password, HOST: localhost, PORT: 3306, OPTIONS: { charset: utf8mb4, } } } # Channels 层配置 - 使用 Redis CHANNEL_LAYERS { default: { BACKEND: channels_redis.core.RedisChannelLayer, CONFIG: { hosts: [(127.0.0.1, 6379)], }, }, } AUTH_PASSWORD_VALIDATORS [...] LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_I18N True USE_TZ True STATIC_URL /static/ LOGIN_URL /login/3. 创建数据模型 (models.py)from django.db import models from django.contrib.auth.models import User from django.utils import timezone class ChatRoom(models.Model): 聊天室 name models.CharField(max_length100, uniqueTrue, verbose_name房间名称) description models.TextField(blankTrue, verbose_name房间描述) created_by models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name创建者) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) class Meta: ordering [-created_at] verbose_name 聊天室 verbose_name_plural 聊天室 def __str__(self): return self.name class Message(models.Model): 消息记录 room models.ForeignKey(ChatRoom, on_deletemodels.CASCADE, related_namemessages, verbose_name所属房间) user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name发送者) content models.TextField(verbose_name消息内容) timestamp models.DateTimeField(defaulttimezone.now, db_indexTrue, verbose_name发送时间) class Meta: ordering [timestamp] verbose_name 消息 verbose_name_plural 消息记录 def __str__(self): return f{self.user.username}: {self.content[:50]}... class RoomMember(models.Model): 房间成员 room models.ForeignKey(ChatRoom, on_deletemodels.CASCADE, related_namemembers, verbose_name房间) user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用户) joined_at models.DateTimeField(auto_now_addTrue, verbose_name加入时间) class Meta: unique_together (room, user) verbose_name 房间成员 verbose_name_plural 房间成员4. WebSocket Consumer (consumers.py)import json from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from django.contrib.auth.models import User from .models import ChatRoom, Message, RoomMember class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_id self.scope[url_route][kwargs][room_id] self.room_group_name fchat_{self.room_id} # 验证用户是否登录 if not self.scope[user].is_authenticated: await self.close() return # 加入房间组 await self.channel_layer.group_add( self.room_group_name, self.channel_name ) # 记录用户加入 await self.add_user_to_room(self.scope[user], self.room_id) await self.accept() # 发送历史消息 history await self.get_history_messages(self.room_id) await self.send(text_datajson.dumps({ type: history, messages: history })) # 通知其他用户有人加入 await self.channel_layer.group_send( self.room_group_name, { type: user_joined, username: self.scope[user].username, user_id: self.scope[user].id } ) async def disconnect(self, close_code): # 离开房间组 await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) # 通知其他用户有人离开 if self.scope[user].is_authenticated: await self.channel_layer.group_send( self.room_group_name, { type: user_left, username: self.scope[user].username, user_id: self.scope[user].id } ) async def receive(self, text_data): data json.loads(text_data) message_type data.get(type, message) if message_type message: content data[content] # 保存消息到数据库 saved_message await self.save_message( self.scope[user], self.room_id, content ) # 广播消息给所有用户 await self.channel_layer.group_send( self.room_group_name, { type: chat_message, message: content, username: self.scope[user].username, user_id: self.scope[user].id, timestamp: saved_message[timestamp], message_id: saved_message[id] } ) async def chat_message(self, event): 接收聊天消息并发送给 WebSocket await self.send(text_datajson.dumps({ type: message, message: event[message], username: event[username], user_id: event[user_id], timestamp: event[timestamp], message_id: event[message_id] })) async def user_joined(self, event): 用户加入通知 await self.send(text_datajson.dumps({ type: system, message: f{event[username]} 加入了房间, username: event[username], user_id: event[user_id] })) async def user_left(self, event): 用户离开通知 await self.send(text_datajson.dumps({ type: system, message: f{event[username]} 离开了房间, username: event[username], user_id: event[user_id] })) database_sync_to_async def save_message(self, user, room_id, content): 保存消息到数据库 room ChatRoom.objects.get(idroom_id) message Message.objects.create( roomroom, useruser, contentcontent ) return { id: message.id, timestamp: message.timestamp.isoformat() } database_sync_to_async def get_history_messages(self, room_id, limit50): 获取历史消息 messages Message.objects.filter( room_idroom_id ).select_related(user).order_by(-timestamp)[:limit] return [{ id: msg.id, content: msg.content, username: msg.user.username, user_id: msg.user.id, timestamp: msg.timestamp.isoformat() } for msg in reversed(messages)] database_sync_to_async def add_user_to_room(self, user, room_id): 将用户添加到房间成员 room ChatRoom.objects.get(idroom_id) RoomMember.objects.get_or_create(roomroom, useruser)5. ASGI 配置 (asgi.py)import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack import chat_app.routing os.environ.setdefault(DJANGO_SETTINGS_MODULE, chat_project.settings) application ProtocolTypeRouter({ http: get_asgi_application(), websocket: AuthMiddlewareStack( URLRouter( chat_app.routing.websocket_urlpatterns ) ), })6. WebSocket 路由 (routing.py)from django.urls import re_path from . import consumers websocket_urlpatterns [ re_path(rws/chat/(?Proom_id\d)/$, consumers.ChatConsumer.as_asgi()), ]7. Views 视图 (views.py)from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth import authenticate, login, logout from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.http import JsonResponse from .models import ChatRoom, Message, RoomMember from django.db.models import Count login_required def index(request): 聊天室列表页 rooms ChatRoom.objects.annotate( member_countCount(members) ).all() return render(request, chat_app/index.html, {rooms: rooms}) login_required def room(request, room_id): 聊天室页面 room get_object_or_404(ChatRoom, idroom_id) return render(request, chat_app/room.html, { room: room, room_id_json: room_id }) login_required def create_room(request): 创建聊天室 if request.method POST: name request.POST.get(name) description request.POST.get(description, ) if ChatRoom.objects.filter(namename).exists(): return JsonResponse({error: 房间名已存在}, status400) room ChatRoom.objects.create( namename, descriptiondescription, created_byrequest.user ) return JsonResponse({id: room.id, name: room.name}) return JsonResponse({error: Invalid method}, status405) def login_view(request): 登录 if request.method POST: form AuthenticationForm(request, datarequest.POST) if form.is_valid(): username form.cleaned_data.get(username) password form.cleaned_data.get(password) user authenticate(usernameusername, passwordpassword) if user is not None: login(request, user) return redirect(index) else: form AuthenticationForm() return render(request, chat_app/login.html, {form: form}) def register(request): 注册 if request.method POST: form UserCreationForm(request.POST) if form.is_valid(): user form.save() login(request, user) return redirect(index) else: form UserCreationForm() return render(request, chat_app/register.html, {form: form}) def logout_view(request): 退出 logout(request) return redirect(login)8. URL 配置chat_app/urls.pyfrom django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(room/int:room_id/, views.room, nameroom), path(create-room/, views.create_room, namecreate_room), path(login/, views.login_view, namelogin), path(register/, views.register, nameregister), path(logout/, views.logout_view, namelogout), ]chat_project/urls.pyfrom django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(chat_app.urls)), ]9. 前端模板templates/chat_app/index.html{% extends base.html %} {% block content %} div classcontainer mt-4 h2聊天室列表/h2 button classbtn btn-primary mb-3 onclickshowCreateRoom()创建聊天室/button div classrow {% for room in rooms %} div classcol-md-4 mb-3 div classcard div classcard-body h5 classcard-title{{ room.name }}/h5 p classcard-text{{ room.description|default:暂无描述 }}/p p classtext-muted成员数: {{ room.member_count }}/p a href{% url room room.id %} classbtn btn-success进入房间/a /div /div /div {% empty %} div classcol-12 p暂无聊天室创建一个吧/p /div {% endfor %} /div /div script function showCreateRoom() { const name prompt(请输入聊天室名称); if (name) { fetch(/create-room/, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded, X-CSRFToken: {{ csrf_token }} }, body: name${encodeURIComponent(name)} }) .then(response response.json()) .then(data { if (data.error) { alert(data.error); } else { window.location.reload(); } }); } } /script {% endblock %}templates/chat_app/room.html{% extends base.html %} {% block content %} div classcontainer mt-4 div classrow div classcol-md-12 h2{{ room.name }}/h2 div idchat-box classborder rounded p-3 mb-3 styleheight: 500px; overflow-y: scroll; !-- 消息会动态加载到这里 -- /div div classinput-group input typetext idmessage-input classform-control placeholder输入消息... / button classbtn btn-primary onclicksendMessage()发送/button /div /div /div /div script const roomId {{ room_id_json }}; let chatSocket null; function connect() { chatSocket new WebSocket(ws://${window.location.host}/ws/chat/${roomId}/); chatSocket.onopen function(e) { console.log(WebSocket连接成功); }; chatSocket.onmessage function(e) { const data JSON.parse(e.data); const chatBox document.getElementById(chat-box); if (data.type history) { // 加载历史消息 data.messages.forEach(msg { appendMessage(msg); }); } else if (data.type message) { appendMessage(data); } else if (data.type system) { const div document.createElement(div); div.className text-center text-muted my-2; div.textContent data.message; chatBox.appendChild(div); } // 滚动到底部 chatBox.scrollTop chatBox.scrollHeight; }; chatSocket.onclose function(e) { console.log(WebSocket连接关闭); // 3秒后重连 setTimeout(function() { connect(); }, 3000); }; } function appendMessage(data) { const chatBox document.getElementById(chat-box); const div document.createElement(div); div.className message mb-2; const time new Date(data.timestamp).toLocaleTimeString(); div.innerHTML strong${data.username}/strong small classtext-muted${time}/smallbr ${data.message} ; chatBox.appendChild(div); } function sendMessage() { const input document.getElementById(message-input); const message input.value.trim(); if (message chatSocket chatSocket.readyState WebSocket.OPEN) { chatSocket.send(JSON.stringify({ type: message, content: message })); input.value ; } } // 按回车发送消息 document.getElementById(message-input).addEventListener(keypress, function(e) { if (e.key Enter) { sendMessage(); } }); // 连接WebSocket connect(); /script {% endblock %}templates/base.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title在线聊天室/title link hrefhttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css relstylesheet /head body nav classnavbar navbar-expand-lg navbar-dark bg-dark div classcontainer a classnavbar-brand href{% url index %}在线聊天室/a div classnavbar-nav ms-auto {% if user.is_authenticated %} span classnav-link text-light{{ user.username }}/span a classnav-link href{% url logout %}退出/a {% endif %} /div /div /nav {% block content %}{% endblock %} script srchttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js/script /body /htmltemplates/chat_app/login.html{% extends base.html %} {% block content %} div classcontainer mt-5 div classrow justify-content-center div classcol-md-6 div classcard div classcard-header h3登录/h3 /div div classcard-body form methodpost {% csrf_token %} {{ form.as_p }} button typesubmit classbtn btn-primary w-100登录/button /form p classmt-3 还没有账号a href{% url register %}注册/a /p /div /div /div /div /div {% endblock %}templates/chat_app/register.html{% extends base.html %} {% block content %} div classcontainer mt-5 div classrow justify-content-center div classcol-md-6 div classcard div classcard-header h3注册/h3 /div div classcard-body form methodpost {% csrf_token %} {{ form.as_p }} button typesubmit classbtn btn-primary w-100注册/button /form p classmt-3 已有账号a href{% url login %}登录/a /p /div /div /div /div /div {% endblock %}10. 运行项目# 1. 创建MySQL数据库 mysql -u root -p CREATE DATABASE chat_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 迁移数据库 python manage.py makemigrations python manage.py migrate # 3. 创建超级用户 python manage.py createsuperuser # 4. 启动Redis需要先安装 redis-server # 5. 运行开发服务器使用 Daphne 或 uvicorn pip install daphne daphne -b 0.0.0.0 -p 8000 chat_project.asgi:application主要特性✅实时聊天基于 WebSocket 实现即时消息推送✅消息持久化所有消息存入 MySQL 数据库✅历史消息新用户进入房间自动加载最近50条消息✅房间管理支持创建多个聊天室✅用户认证集成 Django 认证系统✅在线状态显示用户加入/离开通知✅断线重连WebSocket 断开后自动重连这个系统可以直接部署使用你也可以根据需要扩展更多功能比如文件传输、表情包、消息搜索等。