uni-app+php+workman实现简单聊天功能之聊天模块封装

2022-09-08 16:32:01 浏览数 (1)

前面介绍了Laravel中Websocket基本使用(Workerman) 接下来利用uni-app laravel workman实现一个简单的聊天功能。 聊天功能主要涉及到以下场景

场景一

双方都处于聊天界面

这个时候我们要

  1. 将聊天数据渲染到页面
  2. 将产生的聊天数据放到本地存储用于历史记录等

2.1存储当前聊天数据(直接存储 key=chatdetail_当前用户id_聊天对象id

2.2存储当前聊天列表 (key=chatlist_当前用户id) 将当前聊天会话在消息列表置顶,更新最后一条消息,更新时间

如下图

场景二

当前用户不处于聊天界面如: 用户在其他页面或者当前用户正在与其他用户聊天,此时接受到消息 这个时候我们要

  1. 将消息渲染到聊天列表,展示最后一条消息,消息数量,时间等
  2. 将聊天数据放到本地存储

2.1存储聊天数据(直接存储 key=chatdetail_当前用户id_发送消息的用户id

2.2存储当前聊天列表 (key=chatlist_当前用户id) 将接受到的消息在消息列表置顶,更新最后一条消息,更新时间,更新tabbat角标展示

发送消息

  1. 将聊天数据存储到本地存储

1.1存储聊天数据(直接存储 key=chatdetail_当前用户id_聊天对象id

1.2存储当前聊天列表 (key=chatlist_当前用户id) 将当前会话置顶在消息列表置顶,更新最后一条消息,更新时间 1.3请求ajax发送消息

1.4渲染到页面

读取消息

  1. 写入本地存储 chatlist_当前用户id:获取将当前会话的未读书清零,相应的减少总未读书(总未读书展示在tabbar)渲染tabbar

可以看到有很多类似的操作,我们可以将其封装成一个聊天对象。 首先新键配置文件 config.js

代码语言:javascript复制
export default{
    //api请求前缀
    webUrl:'http://social.zihanzy.com/api/v1/',
    //websocket
    websocket:'wss://social.zihanzy.com/wss',
    //展示未读信息的tabbar
    TabbarIndex:2
}

新键chat.js(聊天相关操作)

代码语言:javascript复制
 import Config from './config.js';
 import User from './user.js';
 export default{
     //socket地址
      url:Config.websocket,
      //连接状态
      IsOpen:false,
      //SocketTask
      SocketTask:false,
      //是否上线(会员id绑定客户端id 验证用户身份 通过则绑定)
      IsOnline:false,
      //当前聊天对象(进入聊天页面获取)
      CurrentToUser:{
          userid:0,//判断userid是否为0,当前用户处于什么场景下
          username:"",
          userpic:""
      },
      //连接
      Open(){
          if(this.IsOpen)return;//防止重复连接
          //连接
          this.SocketTask = uni.connectSocket({
              url:this.url,
              complete:(e)=>{}
          });
          if(!this.SocketTask)return;
          //监听开启
          this.SocketTask.onOpen(()=>{
              //连接成功将连接状态设置为已连接
              this.IsOpen = true;
          });
          //监听消息
          this.Message();
          //监听关闭
          this.SocketTask.onClose(()=>{
              this.IsOpen = false;
            this.SocketTask = false;
          });
          //监听错误
          this.SocketTask.onError((e)=>{
              this.IsOpen=false;
            this.SocketTask = false;
          })
      },
        ...

1.我们初始化相关配置,引入相关文件,编写了websocket连接逻辑 当连接成功后我们会将状态设置为已连接。

代码语言:javascript复制
...
 //用户绑定
async UserBind(client_id){
let res = await this.$.post('chat/bind',{
    type:'bind',
    client_id:client_id,
},{
    header: {
        'Accept':'application/json',
        'Authorization':'Bearer ' uni.getStorageSync('access_token')
    },
})
console.log(res)
//错判断
 //成功处理
 return this.resultUserBind(res.data)
},
...

2.用户绑定将用户的token和消息类型发送给服务端,服务端会对用户进行验证在执行用户是否上线操作

代码语言:javascript复制
 //监听信息
Message(){
    //接受服务器消息
    this.SocketTask.onMessage((e)=>{
    //字符串转换json
    let res = JSON.parse(e.data);
    /*
    {
        type:"bind",
        status:true//绑定成功
    }
    */
    //绑定返回结束
if(res.type=='bind')return this.UserBind(res);//服务器返回client-id下一步执行用户绑定
    if(res.type!=='text') return;//接受的信息不是信息就return
    //全局通知接口
    uni.$emit('UserChat',res);
    //存储到chatdetail(与某用户的聊天记录)
    this.__UpdateChatdetail(res);//(默认接受信息)
    //更新chatlist(当前会话置顶,修改chatlist中当前会话的data和time显示
    this.__UpdateChatlist(res);
    //总未读数 1 修改tabbar信息数
    //当前聊天对象与from_id不同 未读数加1
    //只要当前用户与某一用户没有处于聊天界面时执行未读书 1
    if(this.CurrentToUser.userid!==res.from_id){
        this.__UpdateNoReadNum({type:"add"})
    }

 })
     console.log('监听信息')
},

3.当发送给服务器消息后我们就要接受服务器给我们的响应

  1. 将服务器给我们的响应进行编码
  2. 判断服务器的响应类型,如果是bind(绑定),我们会调用用户绑定函数(UserBind)函数
  3. 如果接受的响应类型是文本

3.1 全局通知接口,使每个页面都能接受到信息

3.2 将聊天记录存储到本地存储,调用(__UpdateChatdetail)默认是接受信息(发送消息也会调用该函数)

3.3更新消息列表,将当前会话置顶,修改chatlist中当前会话的data和time显示,调用(__UpdateChatlist)

3.4使总未读消息数 1,调用(__UpdateNoReadNum)

接下来分别介绍resultUserBind__UpdateChatdetail__UpdateChatlist__UpdateNoReadNum

Message > resultUserBind

代码语言:javascript复制
...
//用户绑定结果
resultUserBind(res){
    if(res.status && res.type=='bind'){
        //该为上线状态
        this.IsOnline = true;
        //获取总未读数,并且渲染到tabbar的badge
        this.initTabbarBadge();
        //获取未读信息
        // this.getChatMessages();
        return;
    }
    //绑定失败,断开连接
    uni.showToast({
        title:res.msg,
    icon:"none"
    })
    this.SocketTask.close();
},
...

该函数获取服务器的用户绑定结果

  1. 如果用户状态正常则使用户上线
  2. 初始化tabbar的未读总信息角标initTabbarBadge
  3. 获取未读的信息getChatMessages
  4. 绑定失败断开连接,并展示相关提示

Message > resultUserBind > initTabbarBadge

代码语言:javascript复制
//初始化tabber信息
    initTabbarBadge(){
        //获取总未读数
        console.log('初始化tabber未读数');
        let noreadnum = uni.getStorageSync('noreadnum' User.userinfo.id);
        if(noreadnum && noreadnum>0){
            //设置tabber角标
            return uni.setTabBarBadge({
                index:Config.TabbarIndex,
            text:noreadnum>99?'99 ':noreadnum.toString()
            });
        }
        return uni.removeTabBarBadge({
            index:Config.TabbarIndex
        })
    },

该函数用于初始化tabbar信息 获取总未读数 当未读数>0时渲染tabbar 当未读书=0时 清除tabbar的角标

Message > resultUserBind > getChatMessages

代码语言:javascript复制
...
getChatMessages(){
    console.log('test未读')
    $.post('chat/get',{},{
        header: {
            'Accept':'application/json',
            'Authorization':'Bearer ' uni.getStorageSync('access_token')
        }
    }).then(datas=>{
        let data = datas.data.data
    if(data.length>0){
        for(let i = 0;i<data.length;i  ){
             let msg = data[i]
             uni.$emit('UserChat',msg);
             //存储到chatdetail(与某用户的聊天记录)
             this.__UpdateChatdetail(msg);//(默认接受信息)
             //更新chatlist(当前会话置顶,修改chatlist中当前会话的msg和time显示
             this.__UpdateChatlist(msg);
             //总未读数 1 修改tabbar信息数
             //当前聊天对象与from_id不同 未读数加1
             //    //只要当前用户与某一用户没有处于聊天界面时执行未读书 1
             if(this.CurrentToUser.userid!==msg.from_id){
                this.__UpdateNoReadNum({type:"add"})
             }
        }
    }
    })
},
        ...

该函数用于获取未读信息,当用户离线时接受到的消息暂存在服务端缓存,当用户重新上线时触发该函数获取到未读消息并广播事件UserChat使页面获取到未读消息

Message > __UpdateChatdetail

代码语言:javascript复制
//存储到chatdetail(与某用户的聊天记录)
__UpdateChatdetail(res,issend = false){
    /*发送和接受都要调用函数存储,
    1.判断是不是发送 发送 issend=true 接受 issend=false (默认接受信息)
    如果是发送userid = 当前要发送用户id
    如果是接受userid = 发送消息的用户id
    */
    let userid = issend ? this.CurrentToUser.userid : res.from_id;
    //获取旧数据
    let list = uni.getStorageSync('chatdetail_' User.userinfo.id '_' userid);//与某用户的聊天记录
    list = list ? JSON.parse(list):[];
    //追加
    list.push(this.__format(res,{type:"chatdetail",isme:issend,olddata:list}));
    //存储
    uni.setStorage({
        key:'chatdetail_' User.userinfo.id '_' userid,
    data:JSON.stringify(list)
    })

},

该函数存储当前用户与某位用户的聊天记录,这里分两种情况接受消息和发送消息都要将消息存储,在Message函数体里面我们默认是接受消息

  1. 获取原来的消息历史记录
  2. 对消息历史记录进行追加,调用了函数__format进行数据格式化
  3. 将追加后的数据进行本地存储

Message > __UpdateChatdetail > __format 该函数用于数据的格式化,存储聊天记录和消息列表都会用到此函数

代码语言:javascript复制
  __format(data,options={}){
    /**
     * options={
         type:"chatdetail",//转换类型
         olddata:olddata,//旧数据(chatdetail中必填)
         isme:true //(true本人,false聊天对象,chatdetail中必填)
     }
     */
     switch(options.type){        
         case "chatdetail"://聊天详情记录
         let list = options.olddata; //旧数据
         let chattime = new Date().getTime();//获取当前时间
         let length = list.length;
         return {
            isme:options.isme,
            userpic:options.isme ? User.userinfo.userpic : data.from_userpic,
            type:data.type,
            data:data.data,
            time:chattime,
            gstime:chattime,
            //Time.gettime.getChatTime(chattime,(length)>0)?list[length-1].time:0)

        };
        break;

isme主要用于消息渲染的时候将哪个用户展示在右边如果(isme=true)那么这条消息应该展示在右边

Message >** UpdateChatlist**

代码语言:javascript复制
 //更新chatlist (将当前会话置顶,修改chatlist中当前会话的data和time显示)
__UpdateChatlist(res){
    //获取旧数据
    let chatlist = uni.getStorageSync('chatlist' User.userinfo.id);
    chatlist = chatlist ? JSON.parse(chatlist):[];
    //判断是否已经存在该会话 ,存在:将当前会话置顶,不存在;追加至头部
    let index = chatlist.findIndex((item)=>{
        //发送和接受消息的用户id
        return item.userid == res.to_id || item.userid == res.from_id;

    })
    //不存在
    if(index == -1){
     let obj = this.__format(res,{type:"chatlist"}); 
     //忽略本人发送
     if(res.from_id!==User.userinfo.id){
         obj.noreadnum=1;
     }
     chatlist.unshift(obj); 
    }else{
        //存在 将当前会话置顶,修改chatlist中当前会话的data和time显示
        chatlist[index].data = res.data;
        chatlist[index].type = res.type;
        chatlist[index].time = res.time;
        //当前聊天对象不是该id ,未读书加1(排除本人发送消息)
        if(res.from_id !== User.userinfo.id && this.CurrentToUser.userid!==chatlist[index].userid){
            chatlist[index].noreadnum  ;//不处在聊天对象中
        }
        //置顶当前会话
        chatlist = this.__toFirst(chatlist,index);
    }
    //存储到本地
    uni.setStorage({
        key:'chatlist' User.userinfo.id,
    data:JSON.stringify(chatlist)
    })
},

该函数用于存储消息列表,并进行消息列表内容的更新、时间更新、最新消息置顶等 包含以下步骤

  1. 获取之前的消息列表
  2. 对之前的消息列表进行判断,如果存在则置顶,调用__toFirst函数
  3. 不存在则调用__format函数进行数据格式化并将数据存储到消息列表数组头部
  4. 将列表存储到本地存储

Message > __UpdateChatlist > __format

代码语言:javascript复制
switch(options,type){
     ...
     case "chatlist"://新增会话
     let obj = {
         userid:data.from_id,
         userpic:data.from_userpic,
         username:data.from_username,
         time:data.time,//最新消息时间戳
         data:data.data,
         noreadnum:0 //未读数
     };
     //本人发送的消息
     if(data.from_id == User.userinfo.id){
         obj.userid = this.CurrentToUser.userid;
         obj.userpic = this.CurrentToUser.userpic;
         obj.username = this.CurrentToUser.username;
     }
     return obj;
     break;
     ...

Message > __UpdateChatlist > __toFirst

代码语言:javascript复制
//数组置顶
__toFirst(arr,index){
    if(index!=0){
        arr.unshift(arr.splice(index,1)[0]);
    }
    return arr;
}

Message > __UpdateNoReadNum

代码语言:javascript复制
__UpdateNoReadNum(options = {}){
    //获取总未读数
    let noreadnum = uni.getStorageSync('noreadnum' User.userinfo.id);
    noreadnum = noreadnum || 0;
    //接受信息增加
    if(options.type == 'add'){
        noreadnum  ;
        //响铃振动提示
        this.__Nofify();
    }else{
        noreadnum-=options.num;//读取信息减少 
    }
    noreadnum = noreadnum > 0 ? noreadnum : 0;
    //修改tabbar信息数
    this.__UpdateTabbarBadege(noreadnum);
    //存储
    uni.setStorage({
        key:'noreadnum' User.userinfo.id,
    data:noreadnum
    })
},

该函数用于计算总的消息未读数,有消息时进行响铃提示(__Nofify),渲染tabbar等(__UpdateTabbarBadege),比对最新的数据进行存储

Message > __UpdateNoReadNum > __Nofify

代码语言:javascript复制
//消息提示
__Nofify(){
uni.vibrateLong()  
},

Message > __UpdateNoReadNum > __UpdateTabbarBadege

代码语言:javascript复制
__UpdateTabbarBadege(num){
    if(num && num > 0){
        return uni.setTabBarBadge({
            index:Config.TabbarIndex,
        text:num > 99 ? '99 ':num.toString()
        });
    }
    return uni.removeTabBarBadge({
        index:Config.TabbarIndex
    })
}

该函数将未读数渲染到tabbar

Message函数封装完了,接下来封装发送消息函数

Send

代码语言:javascript复制
//发送消息
send(data){
    //发送的格式
    let senddata = this.__format(data,{type:"send"});
    //存储到chatdetail
    this.__UpdateChatdetail(senddata,true);
    //存储到chatlist(将当前会话置顶,修改时间内容)
    this.__UpdateChatlist(senddata);
    //发送服务器(交由页面做)
    return senddata;
},

该函数处理发送消息相关逻辑

  1. 数据格式化,调用__format函数
  2. 将消息存储到本地存储调用 __UpdateChatdetail函数,可参照前面的
  3. 将消息存储到chatlist调用__UpdateChatlist,参照前面
  4. 返回聊天数据,在页面进行ajax请求

Send > __format

代码语言:javascript复制
...
case "send":
    return {
        to_id:this.CurrentToUser.userid,
        from_id:User.userinfo.id,
        from_username:User.userinfo.username,
        from_userpic:User.userinfo.userpic,
        type:data.type,
        time:new Date().getTime()
    }
    break;
 }
},

接收消息函数 Read

代码语言:javascript复制
//读取当前会话
Read(item){
    if(!item.noreadnum)return;
    let chatlist = uni.getStorageSync('chatlist' User.userinfo.id);
    chatlist = chatlist ? JSON.parse(chatlist):[];
    //拿到当前会话
    let index = chatlist.findIndex((value)=>{
        return value.userid == item.userid;
    });
    let oldnoreadnum = chatlist[index].noreadnum
    //会话存在 未读消息=0
    if(index!==-1){
        chatlist[index].noreadnum = 0;
        //存储
        uni.setStorage({
            key:"chatlist" User.userinfo.id,
        data:JSON.stringify(chatlist)
        })
        //更新tabber的角标
        this.__UpdateNoReadNum({type:"read",num:item.noreadnum});
    }
},

该函数用于读取消息,主要包含以下

  1. 获取旧数据
  2. 如果该会话存在则使为读消息数清零,更新消息列表
  3. 重新渲染tabbar

到此chat对象封装完成,移步【聊天实现】

0 人点赞