当前位置: 首页 > news >正文

django+Vue3实现前后端分离式实时聊天室

技术栈:

前端:Vue3全家桶

后端:django、conda、channel

通信方式:websocket

业务场景:实现实时聊天系统,聊天内容广播至所有人,目前仅支持文字聊天,实时语音、屏幕共享等方式仍在钻研中

由于前端较为简单,所以先从后端入手

一、后端

1.1新建工程

创建django工程

使用下面这个命令检查电脑是否装了django

python -m django --version

如果这行命令输出了一个版本号,证明你已经安装了此版本的 Django;如果你得到的是一个“No module named django”的错误提示,则表明你还未安装。

执行下面这个命令使用django创建工程

django-admin startproject applications

这行代码将会在当前目录下创建一个 applications目录,。

在目录下创建apps文件夹以存放各个应用,然后cd到apps文件夹下创建应用

python manage.py startapp wordChat

对照下面的文件结构来补全缺失的文件

文件结构如图:

1.2、配置conda环境

这里提供链接:配置conda环境

1.3、安装Channels

由于django本身不支持websocket,所以我们使用django官方推荐的第三方库Channels

这里需要安装Channels和channels_redis

pip install channels==2.1.7
pip install channels_redis==2.3.3

1.4、环境配置

先来到settings.py手动添加channels和你的自定义app

INSTALLED_APPS = [# "daphne",'django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','channels',# 自定义app'apps.wordChat'
]

添加ASGI_APPLICATION

ASGI_APPLICATION = 'applications.routing.application'

配置channels_layers

CHANNEL_LAYERS = {"default": {"BACKEND": "channels_redis.core.RedisChannelLayer","CONFIG": {"hosts": [("localhost", 6379)],"capacity": 1500,"expiry": 10,},},
}

然后在applications文件夹下新建routing.py文件(与url文件用处一样,区别在于routing.py是走websocket连接的),有多少个app就导入多少个app到routingList里面

"""
@describe websocket路由相关
@author 
@date 
"""
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import apps.wordChat.routing as wordChatroutinglist = []
routinglist.extend(wordChat.websocket_urlpatterns)
# routinglist.extend(a.websocket_urlpatterns)
application = ProtocolTypeRouter({'websocket': AuthMiddlewareStack(URLRouter(routinglist))
})

1.5、功能实现

接下来在wordChat文件夹下新建routing.py文件和consumers.py文件,routing.py的作用是让前端发来的接口找到对应的位置,consumers.py文件的作用是写接口逻辑

在routing.py中做如下配置

from django.urls import path
from apps.wordChat.consumers import ChatConsumerwebsocket_urlpatterns = [path('ws/chat/', ChatConsumer),
]

在consumers.py中接口逻辑如下

from channels.generic.websocket import AsyncWebsocketConsumer
import json
import asyncio
import logging
import random
import string
from datetime import datetime
from urllib.parse import parse_qs
# 配置日志
logger = logging.getLogger(__name__)class ChatConsumer(AsyncWebsocketConsumer):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.room_group_name = 'ops_coffee'self.connected = Falseself.heartbeat_task = Noneself.client_id = None  # 每个客户端唯一标识self.client_name = "用户"  # 客户端显示名称async def connect(self):# 检查是否已经连接if self.connected:await self.close(code=4000)returnself.connected = True# 加入房间组await self.channel_layer.group_add(self.room_group_name,self.channel_name)# 获取查询字符串query_string = self.scope.get('query_string', b'').decode('utf-8')# 解析查询字符串query_params = parse_qs(query_string)# 获取 username 参数username_list = query_params.get('username', [])self.client_name = username_list[0] if username_list else "匿名用户"# 生成客户端唯一ID和名称self.client_id = self.generate_client_id()# self.client_name = self.scope['url_route']['kwargs'].get('username', None)await self.accept()logger.info(f"WebSocket 连接已建立, 客户端ID: {self.client_id}")# 发送欢迎消息await self.send_welcome_message()# 启动心跳任务self.heartbeat_task = asyncio.create_task(self.send_heartbeat())logger.info("心跳任务已启动")def generate_client_id(self):"""生成8位随机客户端ID"""return ''.join(random.choices(string.ascii_letters + string.digits, k=8))async def send_welcome_message(self):"""发送欢迎消息"""welcome_message = (f"欢迎来到聊天室!您的ID是 {self.client_id}, 名称是 {self.client_name}。\n""试试发送包含'帮助'、'时间'或'天气'的消息触发自动回复")await self.send(text_data=json.dumps({'type': 'system','message': welcome_message,'client_id': self.client_id,'client_name': self.client_name}))# 广播新用户加入消息await self.channel_layer.group_send(self.room_group_name,{'type': 'chat_message','message': f"{self.client_name} 加入了聊天室",'sender_id': 'system','sender_name': '系统通知','is_system': True})async def disconnect(self, close_code):# 标记连接已断开self.connected = Falselogger.info(f"WebSocket 断开连接,代码: {close_code}")# 广播用户离开消息if self.client_id:await self.channel_layer.group_send(self.room_group_name,{'type': 'chat_message','message': f"{self.client_name} 离开了聊天室",'sender_id': 'system','sender_name': '系统通知','is_system': True})# 取消心跳任务if self.heartbeat_task and not self.heartbeat_task.done():self.heartbeat_task.cancel()try:await self.heartbeat_taskexcept asyncio.CancelledError:logger.info("心跳任务已取消")except Exception as e:logger.error(f"等待心跳任务时出错: {str(e)}")# 离开房间组await self.channel_layer.group_discard(self.room_group_name,self.channel_name)async def receive(self, text_data):# 检查连接状态if not self.connected:logger.warning("尝试在断开连接后接收消息")await self.close(code=4001)returnlogger.debug(f"收到消息: {text_data}")try:data = json.loads(text_data)message = data.get('message', '')message_type = data.get('type', 'chat')if message_type == 'chat' and message:# 广播用户消息到所有客户端await self.broadcast_user_message(message)# 检查是否需要自动回复if self.should_reply(message):auto_reply = await self.generate_auto_reply(message)await asyncio.sleep(1.5)  # 添加延迟await self.broadcast_auto_reply(auto_reply)elif message_type == 'heartbeat':# 处理心跳响应logger.debug("收到心跳响应")except json.JSONDecodeError:logger.warning("消息JSON解析失败")await self.send_error_message('消息格式错误,请发送有效的JSON')except Exception as e:logger.error(f"处理消息时出错: {str(e)}")await self.send_error_message('处理消息时发生错误')async def broadcast_user_message(self, message):"""广播用户消息到所有客户端"""await self.channel_layer.group_send(self.room_group_name,{'type': 'chat_message','message': message,'sender_id': self.client_id,'sender_name': self.client_name,'is_auto_reply': False,'is_system': False,'timestamp': datetime.now().isoformat()})async def broadcast_auto_reply(self, message):"""广播自动回复消息"""await self.channel_layer.group_send(self.room_group_name,{'type': 'chat_message','message': message,'sender_id': 'system','sender_name': '客服助手','is_auto_reply': True,'is_system': False,'timestamp': datetime.now().isoformat()})async def send_error_message(self, message):"""发送错误消息到当前客户端"""await self.send(text_data=json.dumps({'type': 'error','message': message,'timestamp': datetime.now().isoformat()}))def should_reply(self, message):"""检查是否需要自动回复"""triggers = ["?", "help", "帮助", "怎么", "如何", "请问", "时间", "天气"]return any(trigger in message for trigger in triggers)async def generate_auto_reply(self, user_message):"""生成智能回复"""if any(word in user_message for word in ["你好", "您好", "hi", "hello"]):return "您好!我是客服助手,有什么可以帮您?"if "时间" in user_message:return f"现在是北京时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"if "天气" in user_message:return "您可以通过 https://www.weather.com 查询实时天气信息"return "感谢您的留言!如需人工帮助,请留言'人工客服'"async def chat_message(self, event):"""处理聊天消息广播"""if not self.connected:return# 准备消息数据message_data = {'type': 'chat','message': event['message'],'sender_id': event['sender_id'],'sender_name': event['sender_name'],'is_auto_reply': event.get('is_auto_reply', False),'is_system': event.get('is_system', False),'timestamp': event.get('timestamp', datetime.now().isoformat())}try:await self.send(text_data=json.dumps(message_data))except Exception as e:logger.error(f"发送消息时出错: {str(e)}")async def send_heartbeat(self):"""定期发送心跳包保持连接活跃"""logger.info("心跳任务开始运行")try:while self.connected:try:await asyncio.sleep(25)if not self.connected:breakawait self.send(text_data=json.dumps({'type': 'heartbeat','message': 'ping','timestamp': datetime.now().isoformat()}))logger.debug("发送心跳包")except asyncio.CancelledError:breakexcept Exception as e:logger.error(f"发送心跳包时出错: {str(e)}")except Exception as e:logger.error(f"心跳任务出错: {str(e)}")finally:logger.info("心跳任务结束")

执行如下命令启动工程

python manage.py runserver 0.0.0.0:8000

出现以下log说明工程已经在运行中

二、前端

前端要做的核心就是websocket的连接

<template><div id="websocket_test_box"><div id="show_msg_box"><div v-for="item in chat_data" :key="item.id">{{ item.sender_name }} : {{ item.message }}</div></div><div id="send_box"><div id="input_box"><input type="text" id="input_msg" v-model="msg"></div><div id="send_msg_box" @click="send_msg()">发送</div></div></div>
</template><script setup>
import { onBeforeUnmount, onMounted, ref } from 'vue'
let ws = null
let msg = ref()
let username=ref()
let chat_data=ref([])onMounted(() => {username.value='user'+Math.floor(Math.random()*1000)initSocket()
})const initSocket = () => {ws = new WebSocket("ws://后端IP:8000/ws/chat/?username="+username.value)ws.onopen = () => {console.log("websocket连接已打开");ws.send(JSON.stringify({message: 'test'}))}ws.onmessage = (e) => {// console.log(JSON.parse(e.data));// let res=JSON.parse(e.data)let msg=JSON.parse(e.data)if (msg.message!=="ping"&&msg.message!=="test") {chat_data.value.push({id:chat_data.value.length,sender_name:msg.sender_name,message:msg.message})}}ws.onclose = e => {console.log("websocket error", e);}ws.onerror = e => {console.log("websocket error", e);}
}const send_msg = () => {ws.send(JSON.stringify({user:username.value,message: msg.value}))
}
</script>

http://www.dtcms.com/a/338194.html

相关文章:

  • Java面试考点
  • ​Kali Linux 环境中的系统配置文件与用户配置文件大全
  • MySQL 自增主键满了咋办?
  • PowerBI CrossFilter解决关联关系过滤传播问题
  • 对象存储 COS 端到端质量系列 —— 终端网络诊断工具
  • 【大模型】RAG
  • 明远智睿 RK3588:以技术突破解锁开发新维度
  • 【Python】源码安装python后报错:ModuleNotFoundError: No module named ‘_lzma‘
  • Jenkins持续集成系统
  • github 如何在 readme 显示Star History
  • NL2SQL:从自然语言到SQL查询的深度解析
  • PostgreSQL 从参数调优到 AI 诊断的实战指南
  • Unity开发中的浅拷贝与深拷贝
  • Java获取京东评论数据的实战指南
  • 06.文件权限管理
  • quic协议与应用开发
  • 视觉语言导航(12)——LLM-VLN 4.2
  • 如何部署 PHPWind 8.5 UTF8 论坛?从下载到安装全流程(附安装包下载)
  • GraphPad Prism10.1安装包免费下载中文版下载以及详细安装教程!!
  • Tomcat Wrapper源码解析:深入理解Servlet生命周期与请求分发机制
  • SQL Server 基本语法
  • NodeJs 桌面开发学习 electron.js (一)
  • 黑马java入门实战笔记
  • 【从0到1制作一块STM32开发板】8. PCB添加丝印
  • c++中的auto自动类型推导
  • JVM-类加载详情
  • Mysql——分库分表后id冲突解决方案(即分布式ID的生成方案)
  • 静态网站与动态网站的区别
  • MySQL分库分表实战指南
  • 电子电气架构 --- 软件开发数字化转型