聊天室开发过程——记一个淘宝美工前端,后端一起来。

服务端

服务端使用基于PHP的Workerman 里面的GatewayWorker,虚拟主机好像不能使用。
GatewayWorker的好处是可以客户端ID、用户ID、用户组为单位处理业务,而且有SESSION缓存功能,还可以在普通页面中调用,不同端口间通信。
官方有集成的数据库连接类,和TP差不多,可以连等、事务、查询等。

客户端

使用VUE编写(对于数据处理,JQ真心不行),因为是按移动端填写的,所以用的是有赞的Vant,这个组件其实是商城的组件。
刚开始想的是写成一个页面,然后不同组件调用。但后来发现不方便,所以引入了Vuex和Vue-routes。主要分成用户列表、用户私聊、用户群聊(暂时只实现大群聊天)、用户页面。因为网站有用户注册登录页面,所以用户页面纯是好看。

开发经过

工具

  1. 服务端
    -- 基本是GatewayWorker,自己写的代码不到100行(包含注释)。
  2. 客户端
    -- Vue Vant Vuex Vue-routes我是直接用的CDN,虽然开发我是用的NODE,但页面中我直接引入CDN,这样打包的JS文件小到想不到合计不到30K。
    -- 自己写的UI实在是不规范。不过个人认为比很多所谓专业的都写的好,至少功能实现了。
    -- 页面的东西不多,加载也算快。

响应原理

因为客户端运行过程中,WebSocket运行中能响应的只有function onMessage(message),所以一切业务逻辑都在这里面。
这个方法里是把所有响应封装成类。而传入中把类名和方法名传处,再到类里面自动调用。

客户端发送
let  tpl={
  module:'ChatService',//使用的类名
  action:'getUserInfo',//使用的方法
  data:{
    uid:uid,
    client_id:client_id
  }
};
this.ws.send(JSON.stringify(tpl));
服务端接收

传入的是发送客户端的ID,数据,和数据库实例

   /**
    * 当客户端发来消息时触发
    * @param int $client_id 连接id
    * @param mixed $message 具体消息
    */
    public static function onMessage($client_id, $message)
    {
        $data=json_decode($message,true);
        if(!empty($data['module']) && !empty($data['action'])){
            $class='\\Workerman\\Service\\'.$data['module'];
             //  只传入数组中的data
            $class::{$data['action']}($client_id,$data['data'],self::$db,self::$redis);
        }
    }
客户接收消息
ws.onmessage= (e)=> {
    let obj,fn,raw;
    //这里很重要,服务端会发送一些异常数据,一定要过滤
    if(typeof e.data =='string' &&  e.data.substring(0,1)=='{' ){
        //这里过滤一下PHP转码时的一些空格 
        raw = e.data.replace(/\\([^u])/g, '$1');
        //console.log(e.data);
        obj=JSON.parse(raw);
        fn='receive'+obj.type;//取得函数名
        this[fn](obj);
    }
}

用户确定

首先是在用最外面定义window.userInfo,包括 用户ID、用户昵称、用户名、用户头像、和session_id。如果是没有登录的用户,用户昵称会随机生成。头像为默认。因为每个用户在GatewayWorker中会对应一个UID,对于有用户ID的就用ID,没有用户ID的用session_id。这样注册用户可以在不同设备登录。但最大只能登录3台。
在连接一建立,时就发送用户信息,服务端响应,并绑定用户的$_SESSION信息,并分配UID。这里如果从安全来讲,还要有一个登录令牌数据库较验的。
在客户端发送数据后,服务端接收到数据会向所有用户发送用户上线消息。所以客户端还有一个收到某个用户上线的消息。

客户端用户上线发送
let tpl={
    module:'ChatService',
    action:'onOpen',
    data:{ userInfo:this.userInfo }
};
ws.send(JSON.stringify(tpl));
服务端收到后确定UID,踢掉3个以上客户端,并发布用户上线消息
public static function onOpen($client_id,$data ,$db)
{
    $data['state']='Login';
    $data['type']='Login';
    $uid=!empty($data['userInfo']['id'])?$data['userInfo']['id']:$data['userInfo']['session_id'];
    $_SESSION['userInfo']=$data['userInfo'];
    $arr=Gateway::getClientIdByUid($uid);
    if(count($arr)>2){
        Gateway::closeClient($arr[0]);
    }
    Gateway::bindUid($client_id,$uid);
    Gateway::joinGroup($client_id, self::$group);
    Gateway::sendToGroup(self::$group,json_encode($data,true));
}
客户端收到上线消息处理

用户上线后,把用户上线的对像加入到用户数组中。再对用户数组处理,得到用户列表。

receiveLogin(obj){
    this.$store.commit('userListAdd',obj.userInfo);
},

取得在线用户列表

初始用户登录后,或是登录一段时间后,要刷新用户列表。都采用的是,传入值里定义操作类、操作方法。

私聊

就是服务端将信息发送给指定UID,这里发送私聊是在子页面上,而接收私聊是在App.vue,父子组件间传递信息好实现,但不同页面间传递就不好实现。这里就用到了广播。
将所有私聊消息放到Vuex里,以数组的型式存放。到聊在窗口时,对数组过滤。

对话框中对自己说的话进行了左右浮动设置

判断消息是否是自己发出,然后对消息框进行左右浮动。

新消息提示

开发前觉得很难,开发中不知怎么的就想到了将消息ID放到一个数组,对话页面离开时将数组中对应ID全删除。在用户列表页面统计每个ID有几条信息。

群聊

群聊比较简单,是最基本的聊天室功能,暂时没有设计新建群和小群聊天。这次基本没有用到数据库,主要没用到数据库,后面考虑将聊天记录功能加上。用户添加好友的功能加上。

消息保存

重新安装了redis数据库,将聊天记保存到数 据库中,以两个用户名加前缀为KEY值,保存聊天记录,一对一聊天是保存7天,多对对聊天保存近200条,当进入群聊页面时,会自动加载聊天记录,一对一聊天需要下拉刷新。

用户列表

可以将登录过的用户生成用户列表,功能有待完成

总结

踩坑

  1. 一直报JSON错误。因为没有看过GatewayWorker源码,可能服务端会推送一些不知名数据过来,所以对接收数据进行过滤。开始还以为是PHP里面数组转字符串时错误造成的。
    猜测是WebSocket 会发一些不是字符串的数据过来。先判断是不是字符串,再判断第一个字符是不是“{”,最好是再判断最后一个是不是“}”,当然要根据具体反回值,因为我反回的是有下标的数组。
    ws.onmessage= (e)=> {
        let obj,fn,raw;
        if(typeof e.data =='string' &&  e.data.substring(0,1)=='{' ){
          raw = e.data.replace(/\\([^u])/g, '$1');
          //console.log(e.data);
          obj=JSON.parse(raw);
          fn='receive'+obj.type;
          this[fn](obj);
        }
      }
  1. 对于vue的计算属性computed。在私聊开发过程中,想的时收到私聊信息后,将信息以‘masssge’:{“用户1”:[ 记录1,记录2,……],“用户2”:[ 记录1,记录2,……]}的型式方到vuex里,再到页面中取 对应用户的值,结果vuex里面更新了,到页面的计算属性中没有更新。计算属性中只是对massage监听,而他里面的“用户”的数组改变并不会影响massage。开发的过程中就有想到这一点,没有百度就找到答案了。
  2. 新消息自动下滑。还好没有走什么弯路,开发前还想的是引入什么框架或是NPM找个轮子
    ,用到了scrollIntoView(false);这个方法。当有消息增加时,在updated()里添加一次下滑到底部。

心得

  1. 没有以前的那种什么都要做到无比优化的完美主义了,先实现再优化,前几天看到群里,一个码农说,他们老板在一个还没有写好的项目中不让用JQ的瀑布流插件,原因是JQ太耗性能了,怕手机上打开慢,在群里问原生瀑布流写法。这个聊天室我是先实现功能,再想着优化流程,要不然进行不下去。

结束语

写到最后了才附上聊天室地址。
手机打开效果好,地址
服务端地址:码云
客户端:码云
这个聊天室稍加改造可以成为客服工具,下一步准备写一个手机摇一摇比赛的项目。

你可能感兴趣的:(聊天室开发过程——记一个淘宝美工前端,后端一起来。)