layim在线客服 架构实现

公司网站,需要一个客服在线聊天系统,找了一个基于Workerman的php socket长连接服务代码,前端基于Layim的。
模块分类:

1. layim 前端

这里我用的是thinkcmfx 的服务器,所以有些逻辑是写在controller中的,比如:登录,session和cookie的设置。
这里的后台端,用户登录将用户信息登录后,写入session中。
前端访客的信息,用cookie保存。时间写长点。当然,用户信息也是自动生成的。

两者的区别不大,就是init 时用户信息取得。不一样。

前端访客 html代码:
<link rel="stylesheet"	href="__TMPL__Public/layui/css/layui.css" media="all">
<script src="__TMPL__Public/assets/js/jquery.min.js"></script>
<script src="__TMPL__Public/layui/layui.js"></script>
<script	src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script type="text/javascript">
    //设置 访客本人的信息 mine 和被访问的人的信息company
    var mine;
    var company
    var company_chat_id = {$company.chat_id};   //controller中要有 $this->assign() 方法
    $(function(){
    	$.ajax({ 
    		  type: "GET", //发送是以POST还是GET
    		  url: "{:U('portal/chat/getList')}", //发送的地址
    		  dataType: "json", //传输数据的格式
    		  data: {company_id:company_chat_id},
    		  //成功的回调函数
    		  success: function(msg) { 
    		    mine = msg.data.mine;
    		    company = msg.data.friend;
    		  }, 
    		  //失败的回调函数
    		  error: function() { 
    		    console.log("error") 
    		  } 
    		})    		
    });
    
layui.use('layim', function(layim){  
  layim.config({
	  init:{ 
		  mine:mine		 //访客信息
	  }
    //开启客服模式
    ,brief: true //是否简约模式(默认false,如果只用到在线客服,且不想显示主面板,可以设置 true)   
  });
  //打开一个客服面板
  layim.chat({
    name: '{$company.company_name}' //名称
    ,type: 'friend' //聊天类型
    ,avatar: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4106960980,3975620054&fm=26&gp=0.jpg' //头像
    ,id: company_chat_id //定义唯一的id方便你处理信息
  });
  layim.setChatMin(); //收缩聊天面板
 
//建立WebSocket通讯 服务器ip
	var socket = new WebSocket('ws://***.***.***.***:7272');

	//连接成功时触发
	socket.onopen = function(){
		// 登录		从cookie中取得访客信息
	var chatid = $.cookie('mxMKwl_cust_uid');
	var chatName = $.cookie('mxMKwl_cust_name');
	var chatAvatar = $.cookie('mxMKwl_cust_avatar');
	var chatSign = $.cookie('mxMKwl_cust_sign');
	
      var login_data = '{"type":"init","id":"'+chatid+'","username":"'+chatName+'","avatar":"'+chatAvatar+'","sign":"'+chatSign+'"}';
      socket.send( login_data );
		//console.log( login_data );
      console.log("websocket握手成功!"); 
	};

	//监听收到的消息
	socket.onmessage = function(res){
		//console.log(res.data);
		var data = eval("("+res.data+")");
      switch(data['message_type']){
          // 服务端ping客户端
          case 'ping':
          	socket.send('{"type":"ping"}');
              break;
          // 登录 更新用户列表
          case 'init':
              //console.log(data['id']+"登录成功");
              //layim.getMessage(res.data); //res.data即你发送消息传递的数据(阅读:监听发送的消息)
              break;
          //添加 用户
          case 'addUser':
              //console.log(data.data);
              layim.addList(data.data);
              break;
          //删除 用户
          case 'delUser':
              layim.removeList({
                  type: 'friend'
                  ,id: data.data.id //好友或者群组ID
              });
              break;
			// 添加 分组信息
          case 'addGroup':
             // console.log(data.data);
              layim.addList(data.data);
              break;
          case 'delGroup':
              layim.removeList({
                  type: 'group'
                  ,id: data.data.id //好友或者群组ID
              });
              break;
          // 检测聊天数据
          case 'chatMessage':
          	//console.log(data.data);
              layim.getMessage(data.data);
              break;
          // 离线消息推送
          case 'logMessage':
              setTimeout(function(){layim.getMessage(data.data)}, 1000);
              break;

          //聊天好有不在线
          case 'ctUserOutline':
          	console.log('11111');
          	//layer.msg('好友不在线', {'time' : 1000});
          	break;
             
      }
	};
	
      layim.on('sendMessage', function(res){
          //console.log(res);
          // 发送消息
          var mine = JSON.stringify(res.mine);
          var to = JSON.stringify(res.to);
          var login_data = '{"type":"chatMessage","data":{"mine":'+mine+', "to":'+to+'}}';
          socket.send( login_data );

      });
 	
});
    
 
</script>
后台用户端html代码:
<link rel="stylesheet" href="__TMPL__Public/static/layui/css/layui.css" media="all">

<script src="__TMPL__Public/static/admin/js/jquery.min.js"></script>
<script src="__TMPL__Public/static/layui/layui.js"></script>

<script type="text/javascript">
    //localStorage.clear();
layui.use('layim', function(layim){
	//基础配置
	layim.config({
		//获取主面板列表信息
		init: {
		  url: "{:U('user/chat/getList')}" //接口地址(返回的数据格式见下文)
		  ,type: 'get' //默认get,一般可不填
		  ,data: {} //额外参数
		}				
		,brief: false //是否简约模式(默认false,如果只用到在线客服,且不想显示主面板,可以设置 true ,为true时layim没有.on('ready', 方法。
		,title: '云商讯-访客' //主面板最小化后显示的名称
		,maxLength: 3000 //最长发送的字符长度,默认3000
		,isfriend: true //是否开启好友(默认true,即开启)
		,isgroup: false //是否开启群组(默认true,即开启)
		,right: '0px' //默认0px,用于设定主面板右偏移量。该参数可避免遮盖你页面右下角已经的bar。
		,find: "" //查找好友/群的地址(如果未填则不显示)
		,copyright: false //是否授权,如果通过官网捐赠获得LayIM,此处可填true
	});
  
	//建立WebSocket通讯 服务器ip
	var socket = new WebSocket('ws://***.***.***.***:7272');

	//连接成功时触发
	socket.onopen = function(){
		// 登录
		var chatid = {$chat_uid};
		var chatName = '{$chat_username}';
		var chatAvatar ={$chat_avatar};
		var chatSign ={$chat_sign};
        var login_data = '{"type":"init","id":"'+chatid+'","username":"'+chatName+'","avatar":"'+chatAvatar+'","sign":"'+chatSign+'"}';
        socket.send( login_data );
		//console.log( login_data );
        console.log("websocket握手成功!"); 
	};

	//监听收到的消息
	socket.onmessage = function(res){
		//console.log(res.data);
		var data = eval("("+res.data+")");
        switch(data['message_type']){
            // 服务端ping客户端
            case 'ping':
            	socket.send('{"type":"ping"}');
                break;
            // 登录 更新用户列表
            case 'init':
                //console.log(data['id']+"登录成功");
                //layim.getMessage(res.data); //res.data即你发送消息传递的数据(阅读:监听发送的消息)
                break;
            //添加 用户
            case 'addUser':
                //console.log(data.data);
                layim.addList(data.data);
                break;
            //删除 用户
            case 'delUser':
                layim.removeList({
                    type: 'friend'
                    ,id: data.data.id //好友或者群组ID
                });
                break;
			// 添加 分组信息
            case 'addGroup':
               // console.log(data.data);
                layim.addList(data.data);
                break;
            case 'delGroup':
                layim.removeList({
                    type: 'group'
                    ,id: data.data.id //好友或者群组ID
                });
                break;
            // 检测聊天数据
            case 'chatMessage':
            	//console.log(data.data);
                layim.getMessage(data.data);
                break;
            // 离线消息推送
            case 'logMessage':
                setTimeout(function(){layim.getMessage(data.data)}, 1000);
                break;
                // 用户退出 更新用户列表
            case 'online':
            	//layim.setFriendStatus(data.id, 'online');
            	//layim.setChatStatus('<span style="color:#FF5722;">在线</span>'); 
            	layim.setFriendStatus(data.id, 'online');
            	break;   
            // 用户退出 更新用户列表
            case 'logout':
            	layim.setFriendStatus(data.id, 'offline');
    	       	//layim.setChatStatus('<span style="color:#333;">离线</span>'); 
	           	break;
            // 用户隐身 更新用户列表	
 //           case 'hide':
//            	layim.setFriendStatus(res.id, 'offline');
//            	break;	
            //聊天好有不在线
            case 'ctUserOutline':
            	//console.log('11111');
            	layer.msg('好友不在线', {'time' : 1000});
            	break;
               
        }
	};

	//layim建立就绪
	layim.on('ready', function(res){
        layim.on('sendMessage', function(res){
            //console.log(res);
            // 发送消息
            var mine = JSON.stringify(res.mine);
            var to = JSON.stringify(res.to);
            var login_data = '{"type":"chatMessage","data":{"mine":'+mine+', "to":'+to+'}}';
            socket.send( login_data );

        });
    });
	layim.setChatMin(); //收缩聊天面板  
	
});    
</script>
关于 页面上的getList方法 ,在controller 中的写法
 //获取列表
    public function getList()
    {
        /*
         * 1.查询自己信息
         * 2.查询客户列表
         */
        $img_default = "http://pic.51yuansu.com/pic2/cover/00/44/04/5813a95730ef7_610.jpg";
        $uid = session('chat_uid');
        
        if(empty($uid)){
            $return = [
                'code' => 0,
                'msg'=> '',
                'data' => [
                    'mine' => [
                        'username' => '',
                        'id' => '',
                        'status' => 'offline',
                        'sign' => '您还没有开通此功能,联系云商讯',
                        'avatar' => $img_default
                    ],
                    'friend' => '',
                    'group'=>''
                ],
            ];
            echo json_encode($return);
            exit;
        }
        
        $mine = M('chatuser','snake_',$this->db)->field('id,username,avatar,sign')->where(['id'=>$uid])->find();
        $mine['avatar'] = empty($mine['avatar'])?$img_default:$mine['avatar'];
        
        //如果是客服登录
        
        $cust_list = M('rel_customer','snake_',$this->db)->where(['salesman_id'=>$uid])->select();
        
        $list_online = array();
        $online = 0;
        $list_offline = array();
        $offline = 0;
        foreach ($cust_list as  $vo){
            $cust =  M('chatuser','snake_',$this->db)->field('username,id,status,sign,avatar')->where(['id'=>$vo['customer_id']])->find();
            $cust['avatar'] = empty($cust['avatar'])?$img_default :$cust['avatar'];
           
            $list_online[$online]= $cust;
            $online ++;
           
        }
        
        /*
         * 取得以前的聊天记录
         * 将和user id  有关的人设为friend里的项
         * 生成friend列表
         *
         */
        $group =['groupname'=>'访客','id'=>1,'online'=>$online];
        
        
        $group['list'] =$list_online;
      
        
        $friends = [];
        array_push($friends, $group);
      
        $return = [
            'code' => 0,
            'msg'=> '',
            'data' => [
                'mine' => [
                    'username' => $mine['username'],
                    'id' => $mine['id'],
                    'status' => 'online',
                    'sign' => $mine['sign'],
                    'avatar' => $mine['avatar']
                ],
                'friend' => $friends,
                'group'=>[]
            ],
        ];
        echo json_encode($return);
        // $this->ajaxReturn ($return);
        
    }
2.workerman的socket服务端

注意:示例中的数据库 $db1 写在了switch 的case 中. 用的话写在switch前面吧!

张三 服务器Events.php 李四 你好!李四, 最近怎么样? 你好!李四, 最近怎么样? 我很好,谢谢! 我很好,谢谢! 张三 服务器Events.php 李四

代码贴一下吧!

<?php
use \GatewayWorker\Lib\Gateway;
use \GatewayWorker\Lib\Db;
/**
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 */
class Events
{
   /**
    * 当客户端发来消息时触发
    * @param int $client_id 连接id
    * @param mixed $message 具体消息
    */
   public static function onMessage($client_id, $data) {
       $message = json_decode($data, true);
       $message_type = $message['type'];
       $db1 = Db::instance('db1');  //数据库链接
       switch($message_type) {
           case 'init':
               // uid
               $uid = $message['id'];
               // 设置session
               $_SESSION = [
                   'username' => $message['username'],
                   'avatar'   => $message['avatar'],
                   'id'       => $uid,
                   'sign'     => $message['sign']
               ];

               // 将当前链接与uid绑定
               Gateway::bindUid($client_id, $uid);
               // 通知当前客户端初始化
               $init_message = array(
                   'message_type' => 'init',
                   'id'           => $uid,
               );
               Gateway::sendToClient($client_id, json_encode($init_message));
              
                              
               $status_message = [
                   'message_type' => 'online',
                   'id'           => $uid,
               ];              
               Gateway::sendToAll(json_encode($status_message));
               //查询最近1周有无需要推送的离线信息
              
               $time = time() - 7 * 3600 * 24;
               $resMsg = $db1->select('id,fromid,fromname,fromavatar,timeline,content')->from('snake_chatlog')
                   ->where("toid= {$uid} and timeline > {$time} and type = 'friend' and needsend = 1" )
                   ->query();
                   
               $db1->query("UPDATE `snake_chatuser` SET `status` = 'online' WHERE id=" . $uid);
                //var_export($resMsg);
               if( !empty( $resMsg ) ){

                   foreach( $resMsg as $key=>$vo ){

                       $log_message = [
                           'message_type' => 'logMessage',
                           'data' => [
                               'username' => $vo['fromname'],
                               'avatar'   => $vo['fromavatar'],
                               'id'       => $vo['fromid'],
                               'type'     => 'friend',
                               'content'  => htmlspecialchars( $vo['content'] ),
                               'timestamp'=> $vo['timeline'] * 1000,
                           ]
                       ];
                 
                       
                       Gateway::sendToUid( $uid, json_encode($log_message) );

                       //设置推送状态为已经推送
                       $db1->query("UPDATE `snake_chatlog` SET `needsend` = '0' WHERE id=" . $vo['id']);

                   }
               }

               //查询当前的用户是在哪个分组中,将当前的链接加入该分组
               /*
               $ret = $db1->query("select `groupid` from `snake_groupdetail` where `userid` = {$uid} group by `groupid`");
               if( !empty( $ret ) ){
                   foreach( $ret as $key=>$vo ){
                       Gateway::joinGroup($client_id, $vo['groupid']);  //将登录用户加入群组
                   }
               }
               unset( $ret );
               */
               return;
               break;
           case 'addUser' :
               //添加用户
               $add_message = [
                   'message_type' => 'addUser',
                   'data' => [
                       'type' => 'friend',
                       'avatar'   => $message['data']['avatar'],
                       'username' => $message['data']['username'],
                       'groupid'  => $message['data']['groupid'],
                       'id'       => $message['data']['id'],
                       'sign'     => $message['data']['sign']
                   ]
               ];
               Gateway::sendToAll( json_encode($add_message), null, $client_id );
               return;
               break;
           case 'delUser' :
               //删除用户
               $del_message = [
                   'message_type' => 'delUser',
                   'data' => [
                       'type' => 'friend',
                       'id'       => $message['data']['id']
                   ]
               ];
               Gateway::sendToAll( json_encode($del_message), null, $client_id );
               return;
               break;
           case 'addGroup':
               //添加群组
               $uids = explode( ',', $message['data']['uids'] );
               $client_id_array = [];
               foreach( $uids as $vo ){
                    $ret = Gateway::getClientIdByUid( $vo );  //当前组中在线的client_id
                    if( !empty( $ret ) ){
                        $client_id_array[] = $ret['0'];

                        Gateway::joinGroup($ret['0'], $message['data']['id']);  //将这些用户加入群组
                    }
               }
               unset( $ret, $uids );

               $add_message = [
                   'message_type' => 'addGroup',
                   'data' => [
                       'type' => 'group',
                       'avatar'   => $message['data']['avatar'],
                       'id'       => $message['data']['id'],
                       'groupname'     => $message['data']['groupname']
                   ]
               ];
               Gateway::sendToAll( json_encode($add_message), $client_id_array, $client_id );
               return;
               break;
           case 'joinGroup':
               //加入群组
               $uid = $message['data']['uid'];
               $ret = Gateway::getClientIdByUid( $uid ); //若在线实时推送
               if( !empty( $ret ) ){
                   Gateway::joinGroup($ret['0'], $message['data']['id']);  //将该用户加入群组

                   $add_message = [
                       'message_type' => 'addGroup',
                       'data' => [
                           'type' => 'group',
                           'avatar'   => $message['data']['avatar'],
                           'id'       => $message['data']['id'],
                           'groupname'     => $message['data']['groupname']
                       ]
                   ];
                   Gateway::sendToAll( json_encode($add_message), [$ret['0']], $client_id );  //推送群组信息
               }

               return;
               break;
           case 'addMember':
               //添加群组成员
               $uids = explode( ',', $message['data']['uid'] );
               $client_id_array = [];
               foreach( $uids as $vo ){
                   $ret = Gateway::getClientIdByUid( $vo );  //当前组中在线的client_id
                   if( !empty( $ret ) ){
                       $client_id_array[] = $ret['0'];

                       Gateway::joinGroup($ret['0'], $message['data']['id']);  //将这些用户加入群组
                   }
               }
               unset( $ret, $uids );

               $add_message = [
                   'message_type' => 'addGroup',
                   'data' => [
                       'type' => 'group',
                       'avatar'   => $message['data']['avatar'],
                       'id'       => $message['data']['id'],
                       'groupname'     => $message['data']['groupname']
                   ]
               ];
               Gateway::sendToAll( json_encode($add_message), $client_id_array, $client_id );  //推送群组信息
               return;
               break;
           case 'removeMember':
               //将移除群组的成员的群信息移除,并从讨论组移除
               $ret = Gateway::getClientIdByUid( $message['data']['uid'] );
               if( !empty( $ret ) ){

                   Gateway::leaveGroup($ret['0'], $message['data']['id']);

                   $del_message = [
                       'message_type' => 'delGroup',
                       'data' => [
                           'type' => 'group',
                           'id'       => $message['data']['id']
                       ]
                   ];
                   Gateway::sendToAll( json_encode($del_message), [$ret['0']], $client_id );
               }

               return;
               break;
           case 'delGroup':
               //删除群组
               $del_message = [
                   'message_type' => 'delGroup',
                   'data' => [
                       'type' => 'group',
                       'id'       => $message['data']['id']
                   ]
               ];
               Gateway::sendToAll( json_encode($del_message), null, $client_id );
               return;
               break;
           case 'chatMessage':
               $db1 = Db::instance('db1');  //数据库链接
               // 聊天消息
               $type = $message['data']['to']['type'];
               $to_id = $message['data']['to']['id'];
               $uid = $_SESSION['id'];
 
               $chat_message = [
                    'message_type' => 'chatMessage',
                    'data' => [
                        'username' => $_SESSION['username'],
                        'avatar'   => $_SESSION['avatar'],
                        'id'       => $type === 'friend' ? $uid : $to_id,
                        'type'     => $type,
                        'content'  => htmlspecialchars($message['data']['mine']['content']),
                        'timestamp'=> time()*1000,
                    ]
               ];
               //聊天记录数组
               $param = [
                   'fromid' => $uid,
                   'toid' => $to_id,
                   'fromname' => $_SESSION['username'],
                   'fromavatar' => $_SESSION['avatar'],
                   'content' => htmlspecialchars($message['data']['mine']['content']),
                   'timeline' => time(),
                   'needsend' => 0
               ];
               switch ($type) {
                   // 私聊
                   case 'friend':
                       // 插入
                       $param['type'] = 'friend';
                       if( Gateway::isUidOnline( $to_id )== 0  ){
                           $param['needsend'] = 1;  //用户不在线,标记此消息推送
                       }
                       $db1->insert('snake_chatlog')->cols( $param )->query();
                       return Gateway::sendToUid($to_id, json_encode($chat_message));
                   // 群聊
                   case 'group':
                       $param['type'] = 'group';
                       $db1->insert('snake_chatlog')->cols( $param )->query();
                       return Gateway::sendToGroup($to_id, json_encode($chat_message), $client_id);
                   case 'kefu':
                       // 插入
                       $param['type'] = 'kefu';
                       if( Gateway::isUidOnline( $to_id )== 0  ){
                           $param['needsend'] = 1;  //用户不在线,标记此消息推送
                       }
                       $db1->insert('snake_chatlog')->cols( $param )->query();
                       return Gateway::sendToUid($to_id, json_encode($chat_message));
               }
               return;
               break;
           case 'hide':
               $status_message = [
               'message_type' => $message_type,
               'id'           => $_SESSION['id'],
               ];
               $_SESSION['online'] = $message_type;
               Gateway::sendToAll(json_encode($status_message));
               return;
               break;
           case 'online':
               $status_message = [
                   'message_type' => $message_type,
                   'id'           => $_SESSION['id'],
               ];
               $_SESSION['online'] = $message_type;
               Gateway::sendToAll(json_encode($status_message));
               return;
               break;
           case 'logout':
               $uid = $message['id'];
               $status_message = [
               'message_type' => $message_type,
               'id'           => $_SESSION['id'],
               ];
               $_SESSION['online'] = $message_type;
              
               $db1->query("UPDATE `snake_chatlog` SET `needsend` = '0' WHERE id=" . $uid);
               Gateway::sendToAll(json_encode($status_message));
               return;
               break;
           case 'ping':
               return;
           default:
               echo "unknown message $data" . PHP_EOL;
       }
   }
   
   /**
    * 当用户断开连接时触发
    * @param int $client_id 连接id
    */
   public static function onClose($client_id) {
       $logout_message = [
           'message_type' => 'logout',
           'id'           => $_SESSION['id']
       ];
       $db1 = Db::instance('db1'); 
       $db1->query("UPDATE `snake_chatuser` SET `status` = 'offline' WHERE id=" . $_SESSION['id']);
       Gateway::sendToAll(json_encode($logout_message));
   }
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值