ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django校园聊天系统毕设实战:从零搭建到答辩避坑

Django校园聊天系统毕设实战:从零搭建到答辩避坑 简介这份资源是面向高校计算机相关专业学生与Python Web开发初学者的校园在线聊天系统完整项目采用Python语言、Django框架与MySQL 8.0数据库开发配套毕业论文与答辩PPT适合作为毕业设计参考或课程实践案例。系统区分管理员与用户两种角色管理员负责用户信息审核、主题场景管理交友、学习、生活服务可锁定场景及问答统计用户注册通过审核后可修改个人资料、选择主题场景聊天、添加好友并收发回复消息并引入深度学习方式辅助聊天交互。压缩包共385个文件约186.29MB包含46个py源码、44个js脚本、25个css与11个html页面、41个png与78个gif界面素材以及sql建库脚本、pkl模型文件、checkpoint与data分片等深度学习相关文件另有jar、xml、properties等配置资源目录结构完整。目前已有33人学习下载可帮助读者快速理解Django项目分层设计、用户审核流程与聊天模块实现思路。1. 校园 chat 在线聊天系统从一份毕业论文到一个能跑起来的 Django 项目很多同学做毕设时都会卡在同一个地方题目定了「基于 Python 的校园 chat 在线聊天系统」技术栈写了 DjangoPPT 也开了个头但真正打开 PyCharm 之后不知道第一行该敲什么。这个标题背后其实是一套非常典型的 Django 全栈项目用户注册登录、好友关系、单聊/群聊、消息持久化、在线状态再配上一份能讲清楚架构的毕业论文和一套答辩 PPT。它适合两类人一是需要交毕设的本科生二是想用一个完整项目把 Django 的 ORM、模板、WebSocket、认证体系串起来的 Python 入门者。我见过太多人把时间花在纠结「用不用 Redis」「要不要上 Docker」结果连django-admin startproject都没跑通。这篇笔记就按我实际带人做这类项目的顺序把选型、建表、聊天通道、避坑和答辩技巧一次讲透让你能照着复现也能在答辩时扛住老师的追问。2. 技术选型与项目骨架为什么是 Django 而不是 Flask2.1 校园聊天场景对框架的真实要求校园 chat 在线聊天系统的核心诉求不是高并发而是「功能完整 代码可讲 论文有东西写」。它需要内置的用户认证、Admin 后台、ORM、模板引擎和表单验证这些 Django 全都自带而 Flask 需要自己拼装。对于毕设来说Django 的django.contrib.auth直接给你一套用户表django.contrib.admin让你不用写后台就能管理用户和消息这在答辩演示时非常加分。另一个现实原因是热词里反复出现的「django项目实战新手」「django一站式教程」说明 Django 的学习资料密度远高于其他 Python Web 框架遇到问题更容易搜到答案。聊天功能本身分两层HTTP 层负责页面渲染、历史消息拉取、好友管理实时层负责消息推送。Django 在 3.0 之后原生支持 ASGI配合 Channels 就能做 WebSocket不需要引入额外的 Node 服务。这个组合的好处是整条链路都在 Python 里论文里画架构图时层次清晰浏览器 → Daphne/ASGI → Channels → Django ORM → 数据库。2.2 从零创建项目骨架的完整命令下面这套命令是我一般会用的初始化流程版本上 Django 选 4.2 LTS 或 5.x 都可以Python 用 3.10 以上。先建虚拟环境再装依赖避免污染系统 Python。# 创建项目目录并进入 mkdir campus_chat cd campus_chat # 创建虚拟环境Windows 用 python -m venv venv python3 -m venv venv source venv/bin/activate # 安装核心依赖 pip install django channels daphne pillow # 创建 Django 项目注意结尾的点表示在当前目录生成 django-admin startproject config . # 创建聊天核心 app python manage.py startapp chat # 创建用户扩展 app用于头像、昵称等 python manage.py startapp accounts命令执行完后目录结构是config/配置chat/聊天逻辑accounts/用户扩展manage.py。这里有个细节startproject后面跟config .而不是项目名是为了让项目根目录干净manage.py直接在顶层后面部署时路径少一层少踩很多坑。2.3 settings.py 里必须改的四个配置打开config/settings.py按下面改。第一处是注册 app把channels放在django.contrib.staticfiles之后chat和accounts放最后INSTALLED_APPS [ daphne, # 必须放在 staticfiles 之前用于接管 runserver django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, channels, chat, accounts, ] ASGI_APPLICATION config.asgi.application # 开发阶段用内存通道层部署时换成 Redis CHANNEL_LAYERS { default: { BACKEND: channels.layers.InMemoryChannelLayer, }, } # 中文和时区答辩演示时时间显示正常 LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ False # 静态文件和媒体文件 STATIC_URL static/ MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediadaphne放在INSTALLED_APPS第一位是为了让runserver自动变成 ASGI 服务器否则 WebSocket 连不上这是新手最常翻车的地方。InMemoryChannelLayer只适合单进程开发一旦你用python manage.py runserver起多个 worker 或者部署到服务器消息就会丢那时候必须换 Redis这个后面避坑章节会细说。2.4 数据库选型SQLite 够不够用毕设阶段 SQLite 完全够用settings.py默认就是它不用改。但如果你想让论文看起来更「工程化」或者老师明确要求 MySQL那就装mysqlclient并改DATABASES配置。我的建议是本地开发和答辩演示用 SQLite论文里写「生产环境可平滑迁移至 MySQL/PostgreSQL」这样既省事又有话说。迁移成本很低Django ORM 屏蔽了大部分差异唯一要注意的是 SQLite 对并发写入支持弱聊天消息高频写入时可能锁库但毕设演示的并发量根本到不了那个级别。3. 数据模型设计用户、好友、消息三张核心表3.1 用 Profile 扩展 Django 内置 UserDjango 自带的 User 表有 username、password、email但校园聊天需要头像、昵称、个性签名。常见做法是建一个 Profile 模型用 OneToOneField 关联 User而不是自定义 AbstractUser因为后者要改AUTH_USER_MODEL一旦项目跑起来再改会非常麻烦。accounts/models.py这样写from django.db import models from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver class Profile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) nickname models.CharField(昵称, max_length20, blankTrue) avatar models.ImageField(头像, upload_toavatars/, defaultavatars/default.png) signature models.CharField(签名, max_length100, blankTrue) is_online models.BooleanField(在线, defaultFalse) def __str__(self): return self.nickname or self.user.username # 用户创建时自动建 Profile避免手动处理 receiver(post_save, senderUser) def create_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(userinstance)related_nameprofile让你可以用user.profile.nickname直接取值模板里写{{ user.profile.avatar.url }}就能显示头像。信号post_save保证注册时自动建 Profile否则新用户访问聊天页会因为Profile.DoesNotExist报 500这是很典型的坑。is_online字段用于好友列表显示在线状态登录时置 True登出或断开 WebSocket 时置 False。3.2 好友关系的两种建模方式好友关系本质是「用户 A 和用户 B 互为好友」有两种建法。第一种是建一张 Friendship 表用from_user和to_user两个外键加一个status字段表示待确认/已通过。第二种是建一张 Friend 表存user和friend两条记录表示双向关系。我一般用第一种因为能表达「好友申请」这个状态论文里也好画状态流转图class Friendship(models.Model): STATUS_CHOICES ( (pending, 待确认), (accepted, 已通过), (rejected, 已拒绝), ) from_user models.ForeignKey(User, on_deletemodels.CASCADE, related_namesent_requests) to_user models.ForeignKey(User, on_deletemodels.CASCADE, related_namereceived_requests) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (from_user, to_user) # 防止重复申请unique_together是必须加的否则用户狂点「加好友」会插入一堆重复记录查询时好友列表出现重复项。查询「我的好友」时用Friendship.objects.filter(statusaccepted).filter(Q(from_userme) | Q(to_userme))再在 Python 里把对方提取出来虽然有点绕但逻辑清晰毕设够用。3.3 消息表与索引优化消息表是数据量最大的表字段设计直接影响查询速度class Message(models.Model): sender models.ForeignKey(User, on_deletemodels.CASCADE, related_namesent_messages) receiver models.ForeignKey(User, on_deletemodels.CASCADE, related_namereceived_messages) content models.TextField(内容) timestamp models.DateTimeField(auto_now_addTrue, db_indexTrue) is_read models.BooleanField(defaultFalse) class Meta: ordering [-timestamp] indexes [ models.Index(fields[sender, receiver, timestamp]), ]ordering [-timestamp]让默认查询按时间倒序拉最近消息时不用再写order_by。复合索引(sender, receiver, timestamp)是给「查我和某人的聊天记录」这个高频查询用的没有它消息一多查询就会慢到肉眼可见。is_read用于未读消息红点配合Message.objects.filter(receiverme, is_readFalse).count()就能显示未读数。3.4 迁移与 Admin 注册模型写完后执行迁移并把模型注册到 Admin 方便演示python manage.py makemigrations python manage.py migrate python manage.py createsuperuserchat/admin.py里注册from django.contrib import admin from .models import Message, Friendship admin.register(Message) class MessageAdmin(admin.ModelAdmin): list_display (sender, receiver, content, timestamp, is_read) list_filter (is_read, timestamp) search_fields (content,) admin.site.register(Friendship)list_display和list_filter让后台能按已读状态筛选、按内容搜索答辩时老师看到这个后台会觉得项目完成度不错。createsuperuser创建的账号记得在 Profile 里补昵称和头像否则前端显示会有点空。4. WebSocket 实时聊天Channels 消费者与路由配置4.1 ASGI 配置与路由分发Channels 的核心是把请求分成 HTTP 和 WebSocket 两类由asgi.py分发。config/asgi.py改成import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack from chat.routing import websocket_urlpatterns os.environ.setdefault(DJANGO_SETTINGS_MODULE, config.settings) application ProtocolTypeRouter({ http: get_asgi_application(), websocket: AuthMiddlewareStack( URLRouter(websocket_urlpatterns) ), })AuthMiddlewareStack是关键它把 Django 的 session 认证带进 WebSocket 连接这样在消费者里能用self.scope[user]拿到当前登录用户。没有它WebSocket 里拿到的永远是 AnonymousUser消息发不出去还找不到原因。chat/routing.py定义 URL 映射from django.urls import re_path from . import consumers websocket_urlpatterns [ re_path(rws/chat/(?Puser_id\d)/$, consumers.ChatConsumer.as_asgi()), ]这个路由表示「和某个用户聊天」的 WebSocket 地址user_id是聊天对象的 ID。4.2 消费者类的完整实现消费者是 WebSocket 的业务逻辑核心chat/consumers.pyimport 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 Message, Profile class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.user self.scope[user] if not self.user.is_authenticated: await self.close() return self.other_id self.scope[url_route][kwargs][user_id] # 用两个用户 ID 排序后拼成房间名保证双方进同一房间 ids sorted([self.user.id, int(self.other_id)]) self.room_group_name fchat_{ids[0]}_{ids[1]} await self.channel_layer.group_add(self.room_group_name, self.channel_name) await self.set_online(True) await self.accept() async def disconnect(self, close_code): if hasattr(self, room_group_name): await self.channel_layer.group_discard(self.room_group_name, self.channel_name) await self.set_online(False) async def receive(self, text_data): data json.loads(text_data) content data.get(content, ).strip() if not content: return msg await self.save_message(content) await self.channel_layer.group_send( self.room_group_name, { type: chat_message, content: content, sender: self.user.username, sender_id: self.user.id, timestamp: msg.timestamp.strftime(%H:%M:%S), } ) async def chat_message(self, event): await self.send(text_datajson.dumps({ content: event[content], sender: event[sender], sender_id: event[sender_id], timestamp: event[timestamp], })) database_sync_to_async def save_message(self, content): return Message.objects.create( senderself.user, receiver_idself.other_id, contentcontent ) database_sync_to_async def set_online(self, status): Profile.objects.filter(userself.user).update(is_onlinestatus)房间名用两个用户 ID 排序后拼接是为了让 A 找 B 和 B 找 A 落到同一个 group否则双方各在一个房间消息永远对不上。database_sync_to_async装饰器把同步 ORM 操作包成异步Channels 里直接调 ORM 会报SynchronousOnlyOperation这是必踩的坑。receive里先存库再广播保证消息不丢chat_message是 group_send 的回调负责把消息推给当前连接。4.3 前端 WebSocket 连接与心跳模板里用原生 WebSocket 连接chat/templates/chat/room.html的关键片段const userId {{ other_user.id }}; const myId {{ request.user.id }}; const wsScheme window.location.protocol https: ? wss : ws; const ws new WebSocket(${wsScheme}://${window.location.host}/ws/chat/${userId}/); ws.onopen function() { console.log(WebSocket 已连接); // 每 30 秒发一次心跳防止连接被中间层断开 setInterval(() ws.send(JSON.stringify({content: __ping__})), 30000); }; ws.onmessage function(e) { const data JSON.parse(e.data); if (data.content __ping__) return; appendMessage(data); }; ws.onclose function() { console.warn(连接断开3 秒后重连); setTimeout(connectWebSocket, 3000); }; function sendMessage() { const input document.getElementById(msg-input); if (input.value.trim()) { ws.send(JSON.stringify({content: input.value})); input.value ; } }心跳包用__ping__这种特殊内容后端收到后直接存库会污染数据所以要在receive里判断一下跳过。重连逻辑必须有否则网络抖动一下用户就得刷新页面体验很差。wsScheme根据当前协议自动选 ws 或 wss部署到 HTTPS 环境时不用改代码。5. 避坑与排查那些让答辩当场翻车的细节5.1 WebSocket 连接一直 404 或 500现象前端new WebSocket报错控制台显示WebSocket connection to ws://... failedNetwork 里看到 404 或 500。原因通常是三个一是INSTALLED_APPS里没加daphne或没放第一位runserver还是 WSGI 模式根本不认识 WebSocket 路由二是asgi.py里ProtocolTypeRouter没配websocket键三是routing.py的正则写错比如漏了结尾的$或user_id没加\d。解决先确认python manage.py runserver启动日志里有没有Starting ASGI/Daphne字样没有就是 daphne 没生效再检查asgi.py和routing.py的路径是否和settings.py里的ASGI_APPLICATION一致。5.2 消息发出去对方收不到现象自己发的消息能显示但对方页面没反应刷新后才看到。原因几乎都是房间名不一致。如果 A 的房间名是chat_1_2B 算出来是chat_2_1两人不在同一个 group广播自然收不到。解决房间名必须用排序后的 ID 拼接sorted([self.user.id, int(self.other_id)])这行不能省。另一个可能是CHANNEL_LAYERS用了InMemoryChannelLayer但起了多个进程内存层不跨进程多 worker 下消息会丢开发时用单进程runserver即可部署时换 Redis。5.3 数据库报 SynchronousOnlyOperation现象WebSocket 连接建立后一发消息就报You cannot call this from an async context - use a thread or sync_to_async。原因是在AsyncWebsocketConsumer的方法里直接调了同步 ORM比如Message.objects.create(...)。解决所有 ORM 操作都要用database_sync_to_async装饰或者用sync_to_async包一层。注意装饰器要加在方法上且方法内不要再用await调其他异步函数否则会嵌套出错。5.4 头像上传后前端显示 404现象Admin 里上传了头像但聊天页面{{ user.profile.avatar.url }}显示破图控制台 404。原因是开发环境下 Django 不会自动服务MEDIA_ROOT里的文件需要手动加路由。解决在config/urls.py末尾加from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)注意这段只在DEBUGTrue时生效部署到生产环境要交给 Nginx 处理否则会有安全隐患。5.5 中文消息乱码或时间显示不对现象消息里的中文变成\u4f60\u597d或者时间比实际早 8 小时。原因前者是前端JSON.parse之前没设ensure_asciiFalseDjango 的json.dumps默认转义非 ASCII后者是USE_TZTrue但TIME_ZONE没配对。解决后端json.dumps加ensure_asciiFalse或者前端解析后自然就正常了JSON.parse能正确处理转义。时间问题把USE_TZ False并设TIME_ZONE Asia/Shanghaiauto_now_add就会存本地时间显示不用再转换。6. 论文与 PPT 的加分技巧把代码讲成故事6.1 论文里最值得展开的三个技术点答辩老师不会逐行看代码他们看的是你有没有理解自己写的东西。第一个值得展开的是「为什么用 WebSocket 而不是轮询」轮询是客户端每隔几秒问服务器有没有新消息实时性差且浪费请求WebSocket 是长连接服务器主动推延迟低。画一张对比表放在论文里比大段文字有说服力。第二个是「Channels 的 channel layer 作用」它是不同消费者实例之间的消息中转站没有它两个用户的连接互相不知道对方存在。第三个是「数据库索引对聊天查询的优化」把你建的复合索引和EXPLAIN结果贴出来体现工程思维。6.2 PPT 结构十页讲完一个项目答辩 PPT 不要超过 12 页结构我一般这样排封面 1 页、选题背景与意义 1 页、技术选型对比 1 页、系统架构图 1 页、数据库 ER 图 1 页、核心功能演示截图 3 页登录注册、好友列表、实时聊天、难点与解决方案 1 页、总结与展望 1 页。架构图用 draw.io 画别用截图糊ER 图把三张核心表的关系画清楚。演示截图要提前录屏现场网络不稳时直接放视频比现场操作稳。热词里提到的「ppt动画」「ppt模板」可以用但别花哨答辩 PPT 干净清晰比炫技重要。6.3 一个能扛住追问的验证方法老师最爱问「你怎么证明消息真的实时到达了」。我的做法是准备两个浏览器窗口一个用普通模式登录 A一个用无痕模式登录 B同时打开聊天页。发消息时用手机秒表计时或者直接看两边时间戳延迟在 100ms 以内就能说明问题。更进一步可以在chat_message回调里打日志把channel_name和timestamp打出来答辩时展示日志文件证明消息确实经过了 channel layer 广播。这个习惯我保持了几年任何「实时」功能都要有可观测的证据不能只靠肉眼说「你看它到了」。论文里把这段日志截图放进去比任何文字描述都硬。做这类毕设项目最大的教训是别一上来就追求「高并发」「分布式」先把单机跑通、消息不丢、断线能重连这三件事做扎实论文和 PPT 自然有东西写。我见过太多人卡在环境配置上耗掉两周最后草草交差。按上面的顺序走一周内能出可演示的版本剩下的时间用来打磨细节和答辩话术。希望帮到你。本文还有配套的精品资源点击获取
返回列表