使用phpsocket.io开发web即时通讯系统(在线客服)记录

整理下目录 明天周六有时间写

开始

我司是做B端SaaS业务的,移动端比较有代表性的项目是办公微服务,下面涵盖了员工食堂、便利店、物业公司的入驻办公楼服务。既然有B端用户又有C端用户,那就避免不了两个端之间有交流,于是需要开发一款类似淘宝阿里旺旺功能的B2C的即时通讯客服系统。使用的websocket技术是基于workman开发的phpsocket.io。

业务场景分析

业务场景还是相对比较复杂的,因为我们公司的C端相当于是写字楼租户,有自己的后台,相当于说C端有前台、后台账号两个身份。B端也有前台、后台账号。排列组合,近B端与C端通信就有四种情况需要考虑。

第一版我们只考虑C端前台账号和B端所有的账号通信问题。不考虑C2C、B2B的情况。由于是客服系统,需要考虑到B端子账号所有有权限的账号都可被看做是客服,存在一个客服分配的情况。

数据表设计

数据表这块走了一些弯路,之前只用了一张表来存储消息记录。主流的C2C私聊的即时通讯一张表也许就够用了,但是这里分为B端和C端,且B端是店铺为单位的,后面的客服不是唯一的,一张表的话,逻辑不是很清晰。于是我对B端、C端的消息记录分开存放。

im_message_c2b
CREATE TABLE `im_message_c2b` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `uuid` char(36) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '全局唯一uuid',
  `store_id` int(11) unsigned NOT NULL COMMENT '店铺id',
  `store_name` varchar(100) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '店铺名称',
  `type` smallint(3) unsigned NOT NULL DEFAULT '0' COMMENT '消息类型(0-系统消息 1-用户消息)',
  `chat_id` int(11) DEFAULT NULL COMMENT '对话id',
  `sender_from` tinyint(1) DEFAULT NULL COMMENT '发送人 1前台  2后台',
  `sender_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '消息发送者ID(前台账号ID,若为系统消息此字段为0)',
  `sender_name` varchar(100) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '消息发送者名称(仅用于前台展示,不做字段关联)',
  `subject` varchar(255) NOT NULL DEFAULT '' COMMENT '消息标题(仅支持文字)',
  `content_type` int(11) NOT NULL DEFAULT '1' COMMENT '消息类型 1文字 2图片 3文件',
  `content` text COMMENT '消息内容(可支持文字、图片、表情、链接等)',
  `receiver_from` tinyint(1) DEFAULT NULL COMMENT '接收人 1前台 2后台',
  `receiver_id` int(11) unsigned DEFAULT NULL COMMENT '消息接收者ID(前台账号ID)',
  `receiver_name` varchar(100) CHARACTER SET utf8 DEFAULT '' COMMENT '消息接收者名称(仅用于前台展示,不做字段关联)',
  `disabled` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除(0-否 1-是)',
  `add_time` int(10) unsigned DEFAULT NULL COMMENT '发送时间(unix时间戳)',
  `state` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 默认0未发送  1发送成功  2发送失败',
  `is_read` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '默认0未读  1已读',
  PRIMARY KEY (`id`) USING BTREE,
  KEY ```receiver_id``` (`receiver_id`) USING BTREE,
  KEY ```add_time``` (`add_time`) USING BTREE,
  KEY ```mixkey``` (`sender_id`,`receiver_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='C端发给B端的消息';
im_message_b2c

CREATE TABLE `im_message_b2c` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `uuid` char(36) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '全局唯一uuid',
  `store_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '店铺id',
  `store_name` varchar(100) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '店铺名称',
  `type` smallint(3) unsigned NOT NULL DEFAULT '0' COMMENT '消息类型(0-系统消息 1-用户消息)',
  `chat_id` int(11) DEFAULT '0' COMMENT '对话id',
  `sender_from` tinyint(1) DEFAULT '0' COMMENT '发送人 1前台  2后台',
  `sender_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '消息发送者ID(前台账号ID,若为系统消息此字段为0)',
  `sender_name` varchar(100) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '消息发送者名称(仅用于前台展示,不做字段关联)',
  `subject` varchar(255) NOT NULL DEFAULT '' COMMENT '消息标题(仅支持文字)',
  `content_type` int(11) NOT NULL DEFAULT '1' COMMENT '消息类型 1文字 2图片 3文件',
  `content` text COMMENT '消息内容(可支持文字、图片、表情、链接等)',
  `receiver_from` tinyint(1) DEFAULT '0' COMMENT '接收人 1前台 2后台',
  `receiver_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '消息接收者ID(前台账号ID)',
  `receiver_name` varchar(100) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '消息接收者名称(仅用于前台展示,不做字段关联)',
  `disabled` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除(0-否 1-是)',
  `add_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '发送时间(unix时间戳)',
  `state` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 默认0未发送  1发送成功  2发送失败',
  `is_read` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '默认0未读  1已读',
  PRIMARY KEY (`id`) USING BTREE,
  KEY ```receiver_id``` (`receiver_id`) USING BTREE,
  KEY ```add_time``` (`add_time`) USING BTREE,
  KEY ```mixkey``` (`sender_id`,`receiver_id`) USING BTREE,
  KEY ```type``` (`type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=682754 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='B端发给C端的消息';

这里着重说明的一点是,为了让通信的uid逻辑性一致,receiver_id、sender_id都是取前台账号(后台账号与前台有对应关系),用receiver_from、sender_from区分是前台还是后台,uid用front、back前缀区分是前台还是后台。

另外还有记录会话id的表、聊天媒体文件的表,在这就不做记录了。

主要逻辑处理

  • 聊天列表

    • 对于C端聊天列表,肯定是group by store_id,按照店铺去分组。

    • 按照一般的逻辑,首先列表页排序肯定是按最后一次发送消息的时间戳倒序去排序的。并且要显示最新一条与店铺对话的消息。

    • 这里就很好玩了,因为mysql语句执行顺序group by是在order by前面的,所以兼顾这两个的用法是这里的重点。为实现上述逻辑,我的sql如下:

      SELECT * from (
          select * from (
              select * from (SELECT store_id,type,store_name,receiver_id as chat_user_id,receiver_name as chat_user_name,content,add_time FROM im_message_c2b WHERE sender_id = {$data['user_id']} AND type = 1 order by add_time desc limit 10000) as a group by store_id 
              union all 
              select * from (SELECT store_id,type,store_name,sender_id,sender_name,content,add_time FROM im_message_b2c WHERE receiver_id = {$data['user_id']} order by add_time desc limit 10000) as b group by b.store_id
          ) c order by add_time desc limit 10000
      ) as d GROUP BY store_id ORDER BY add_time desc LIMIT $data[offset],$data[limit]
    • (为什么加limit,因为mysql5.7后就得加,不加group by还是按最原始的排序)

    • B端对话逻辑列表逻辑类似,不过是以发送人id分组的。

  • 聊天记录

    • C端聊天记录:以店铺id分组,查找出与当前店铺所有聊天记录。
    • B端聊天记录:查看店铺与用户所有聊天记录(可能存在分配到其他客服,另一个客服要知道之前聊的内容)。
    • 聊天记录的显示涉及到一个问题,就是消息间隔大于三分钟的话,要显示一个发送时间。我这边想到的方案是,两两比对消息的时间戳,如果大于3分钟,则插入一条时间戳记录,回头把所有的数据放在json里面返给前端。要注意的是,遍历数组同时又插入数组,很容易导致条件判断的错误。贴一下我写的算法。
      // 两两比对,间隔大于两分钟插入时间显示
      $i = 0; 
      //$messageLog['rows']是消息数组
      foreach ($messageLog['rows'] as $k => $v) {
          $count = count($messageLog['rows']);
          if ($i < $count - 1) {
              $head_time = $messageLog['rows'][$i]['add_time'];
              $next_time = $messageLog['rows'][$i+1]['add_time'];
              if ($head_time - $next_time >= 120) {
                  $arr = array(
                      ['time' => $head_time,'mark' => 'middle']
                  );            
                  // 如果间隔大于2min,插一条记录显示时间
                  array_splice($messageLog['rows'], $i+1, 0, $arr);
                  $i += 2; //插入记录了,跳过时间显示这条数据的比对,永远只比对原始的消息时间戳
              } else {
                  $i ++;
              }
          }
      }
  • 标记为已读

    • 当点开聊天页面的时候,肯定要标记双方的消息记录为已读。
    • 当发送消息的时候,当前时间戳之前的双方消息,也要标记为已读。
聊天列表
聊天记录
标记为已读
分配客服

离线消息如何处理

群发消息-大数据量处理

发表评论

电子邮件地址不会被公开。 必填项已用*标注