最近,在写一个简单的基于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>

    一个简单的聊天室就做好啦