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

服务端

服务端使用基于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(client_id,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太耗性能了,怕手机上打开慢,在群里问原生瀑布流写法。这个聊天室我是先实现功能,再想着优化流程,要不然进行不下去。

结束语

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容