最近,在写一个简单的基于websocket和django的聊天室,来分享一下我这个过程
首先是思路,聊天室说起来简单,但实际操作起来完全不是那么回事了,除了简单的连接之外,完全没有一点头绪
把聊天室的功能梳理了一下,然后拆开,分成了前端连接,前端发送,后端连接,实时推送消息,记录保存这五个方面
先来说一说前端连接的问题,其实也就是一个简单的
websocket
连接,不过是增加了一个断开重连websocketlink(uid){ if('WebSocket' in window){ // 生成websocket链接 console.log('支持') var ws = new WebSocket('ws://192.168.1.157:8000/chat_room_websocket/?uid='+uid); // var ws = new WebSocket('ws://192.168.1.157:8000/chat_room_websocket/'); // 连接成功 ws.onopen = function(){ ws.send(uid); { // 收到数据 ws.onmessage=(evt)=>{ // 将获取信息打印 var received_msg = evt.data this.msglist.push(JSON.parse(received_msg)) console.log(evt) // 连接关闭 ws.onclose=()=>{ console.log('链接已关闭') this.websocketlink(localStorage.getItem('uid')) { // 连接报错 ws.error=()=>{ this.websocketlink(localStorage.getItem('uid')) {
这里,我把连接封装起来,在报错或者关闭时,重新连接
页面操作上,点击按钮,发送消息,这都是简单的请求
API
接口的操作,这里就不多详述了后端连接,也是一个简单的
dwebsocket
连接clients = {{ # 链接websocket接口 @accept_websocket def chat_room_websocket(request): if request.is_websocket(): while True: message = request.websocket.wait() if not message: break else: # 连接的用户的id uid = message.decode() # 加入到字典中 clients[uid] = request.websocket
和之前一样,定义公共变量,将连接的id和连接对象放进去
存储,发送消息不难,存储其实也不难,无论是存到mysql还是redis还是文件都可以自行选择,这里,我存的是自己的文件,根据用户的id生成文件,时间戳+消息追加性的存储
# 时间戳,毫秒,为了之后取消息记录,做一下准备 t = time.time() timestamp1 = int(round(t * 1000)) # 加个换行,直接存 s = str(timestamp1)+ ':' + msg + '\n' with open('chat_record/%s.txt' % uid, 'a', encoding='utf-8') as f: f.write(s)
实时推送消息,如果有人在聊天室发了消息,却看不到,这就非常的伤脑筋,为了实现简单的推送消息,将公共变量中的所有连接,遍历一下,然后发送给所有人
# 遍历所有的连接用户 for client in clients: # 构造返回数据,需要编一下码 message = json.dumps({'username':username,'msg':msg{,ensure_ascii=False) # 发送消息 clients[client].send(message.encode('utf-8'))
后端推送,这里我是传了图片,为了更加的人性化。只发文字不发图片的聊天室是没有灵魂的
另外,我做了一个
redis
限流,这个可以忽略不计另外的另外, 因为我前端稀碎, 有些多余的代码可以自行过滤一下
class SendMessage(APIView): def post(self,request): # 传图片和发送消息是两个接口,所以,获取用户id的方法也不一样,这里写的多余了 msg = request.data.get('msg') uid = request.data.get('uid') image = request.FILES.get('file') if image: uid = request.GET.get('uid') # redis限流 redis_client = get_redis_connection('chat_room') try: redis_client.get(uid) except Exception as e: print(e) return Response({'code': 1001, 'msg': '发送频繁'{) redis_client.hincrby(uid,'num') redis_client.expire(uid, 5) # 这里的名字是根据连接查了一下数据库,可以忽略 username = User.objects.filter(id=uid).first().username # 如果要获取两个人的聊天记录,在创建文件时,可以用两个人的id来组成文件名 # 构造时间戳 t = time.time() timestamp1 = int(round(t * 1000)) # 如果传的是消息,发送消息顺便存储到本地 if msg: for client in clients: message = json.dumps({'username':username,'msg':msg{,ensure_ascii=False) clients[client].send(message.encode('utf-8')) s = str(timestamp1)+ ':' + msg + '\n' with open('chat_record/%s.txt' % uid, 'a', encoding='utf-8') as f: f.write(s) # 写图片文件,如果传了文件,则写入到文件中,然后拼接一个url返回 elif image: with open(os.path.join(CHAT_RECORD_ROOT, '', image.name), 'wb') as f: for chunk in image.chunks(): f.write(chunk) message = json.dumps({'username': username, 'msg':('http://192.168.1.157:8000/static/chat_record/'+image.name){, ensure_ascii=False) for client in clients: clients[client].send(message.encode('utf-8')) s = str(timestamp1)+':'+'http://192.168.1.157:8000/static/chat_record/'+str(image.name) + '\n' with open('chat_record/%s.txt' % uid, 'a', encoding='utf-8') as f: f.write(s) return Response({'message': 'ok'{)
发送与渲染
判断:indexOf() —>判断是否有某个元素,格式为 字符串&&字符串.indexOf(‘子串’)
如果不存在,则返回一个**-1**
<a-modal v-model='show' @ok="myok"> <span v-for="i in msglist" :key="i.username"> <p v-if='i.msg && i.msg.indexOf(".jpg")!=-1'>{{i.username{{:<a-avatar :size="44" :src=i.msg style='margin-left: 20px'></a-avatar></p> <p v-else>{{i.username{{:{{i.msg{{</p> </span> <span> <a-form-item label="发送消息" v-bind="formlayout" > <a-input v-model="msg" /> </a-form-item> <a-form-item v-bind="buttonlayout"> <a-button type='primary' @click="send_msg">发送消息</a-button> <a-upload name="file" :multiple="true" :action=uid :headers="headers" @change="handleChange" > <a-button> <a-icon type="upload" /> 发送文件 </a-button> </a-upload> </a-form-item> </span> </a-modal>
一个简单的聊天室就做好啦
- Post link: https://www.godhearing.cn/liao-tian-shi/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.