349 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			349 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			PHP
		
	
	
|  | <?php | |||
|  | // +----------------------------------------------------------------------
 | |||
|  | // | LikeShop有特色的全开源社交分销电商系统
 | |||
|  | // +----------------------------------------------------------------------
 | |||
|  | // | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
 | |||
|  | // | 商业用途务必购买系统授权,以免引起不必要的法律纠纷
 | |||
|  | // | 禁止对系统程序代码以任何目的,任何形式的再发布
 | |||
|  | // | 微信公众号:好象科技
 | |||
|  | // | 访问官网:http://www.likemarket.net
 | |||
|  | // | 访问社区:http://bbs.likemarket.net
 | |||
|  | // | 访问手册:http://doc.likemarket.net
 | |||
|  | // | 好象科技开发团队 版权所有 拥有最终解释权
 | |||
|  | // +----------------------------------------------------------------------
 | |||
|  | // | Author: LikeShopTeam-段誉
 | |||
|  | // +----------------------------------------------------------------------
 | |||
|  | 
 | |||
|  | 
 | |||
|  | namespace app\common\websocket; | |||
|  | 
 | |||
|  | 
 | |||
|  | use app\common\enum\ChatMsgEnum; | |||
|  | use app\common\utils\Redis; | |||
|  | use Swoole\Server; | |||
|  | use Swoole\Websocket\Frame; | |||
|  | use think\App; | |||
|  | use think\Event; | |||
|  | use think\Request; | |||
|  | use think\swoole\Websocket; | |||
|  | use think\swoole\websocket\Room; | |||
|  | 
 | |||
|  | class Handler extends Websocket | |||
|  | { | |||
|  |     protected $server; | |||
|  | 
 | |||
|  |     protected $room; | |||
|  | 
 | |||
|  |     protected $parser; | |||
|  | 
 | |||
|  |     protected $cache; | |||
|  | 
 | |||
|  |     protected $prefix; | |||
|  | 
 | |||
|  |     public function __construct(App $app, Server $server, Room $room, Event $event, Parser $parser, Redis $redis) | |||
|  |     { | |||
|  |         $this->server = $server; | |||
|  |         $this->room = $room; | |||
|  |         $this->parser = $parser; | |||
|  |         $this->cache = $redis; | |||
|  |         $this->prefix = config('default.websocket_prefix'); | |||
|  |         parent::__construct($app, $server, $room, $event); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes open | |||
|  |      * @param int $fd | |||
|  |      * @param Request $request | |||
|  |      * @return bool|mixed|void | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:13 | |||
|  |      */ | |||
|  |     public function onOpen($fd, Request $request) | |||
|  |     { | |||
|  |         $token = $request->get('token/s'); // 客服
 | |||
|  |         $type = $request->get('type/s'); // user, kefu
 | |||
|  |         $client = $request->get('client/d'); | |||
|  |         $shop_id = $request->get('shop_id/d', 0); //当前对话的商家id
 | |||
|  | 
 | |||
|  |         try { | |||
|  |             $user = $this->triggerEvent('login', ['client' => $client, 'token' => $token, 'type' => $type]); | |||
|  | 
 | |||
|  |             if ($user['code'] == 20001 || empty($user['data']['id'])) { | |||
|  |                 throw new \Exception(empty($user['msg']) ? "未知错误" : $user['msg']); | |||
|  |             } | |||
|  |         } catch (\Throwable $e) { | |||
|  |             echo 'onOpen错误:' . $e->getMessage(); | |||
|  |             return $this->server->close($fd); | |||
|  |         } | |||
|  | 
 | |||
|  |         // 登录者绑定fd
 | |||
|  |         $this->bindFd($type, $user['data'], $fd, $shop_id); | |||
|  | 
 | |||
|  |         $this->ping($fd); | |||
|  | 
 | |||
|  |         return $this->pushData($fd, 'login', [ | |||
|  |             'msg' => '连接成功', | |||
|  |             'msg_type' => ChatMsgEnum::TYPE_TEXT | |||
|  |         ]); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes onMessage | |||
|  |      * @param Frame $frame | |||
|  |      * @return bool|mixed|void | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/20 10:53 | |||
|  |      */ | |||
|  |     public function onMessage(Frame $frame) | |||
|  |     { | |||
|  |         $param = $this->parser->decode($frame->data); | |||
|  | 
 | |||
|  |         try { | |||
|  |             // 回应ping
 | |||
|  |             if ('ping' === $param['event']) { | |||
|  |                 return $this->ping($frame->fd); | |||
|  |             } | |||
|  | 
 | |||
|  |             $param['handle'] = $this; | |||
|  |             $param['fd'] = $frame->fd; | |||
|  | 
 | |||
|  |             return $this->triggerEvent($param['event'], $param); | |||
|  | 
 | |||
|  |         } catch (\Throwable $e) { | |||
|  |             echo $e->getMessage(); | |||
|  |             return $this->pushData($frame->fd, 'error', [ | |||
|  |                 'msg' => $e->getMessage(), | |||
|  |                 'msg_type' => ChatMsgEnum::TYPE_TEXT | |||
|  |             ]); | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes onClose | |||
|  |      * @param int $fd | |||
|  |      * @param int $reactorId | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:03 | |||
|  |      */ | |||
|  |     public function onClose($fd, $reactorId) | |||
|  |     { | |||
|  |         $this->triggerEvent('close', ['handle' => $this, 'fd' => $fd]); | |||
|  |         $this->removeBind($fd); | |||
|  |         $this->server->close($fd); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 触发事件 | |||
|  |      * @param string $event | |||
|  |      * @param array $data | |||
|  |      * @return mixed | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:03 | |||
|  |      */ | |||
|  |     public function triggerEvent(string $event, array $data) | |||
|  |     { | |||
|  |         return $this->event->until('swoole.websocket.' . $event, $data); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 登录者的id绑定fd | |||
|  |      * @param $type | |||
|  |      * @param $user | |||
|  |      * @param $fd | |||
|  |      * @param $shop_id | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:02 | |||
|  |      */ | |||
|  |     public function bindFd($type, $user, $fd, $shop_id) | |||
|  |     { | |||
|  |         $uid = $user['id']; | |||
|  | 
 | |||
|  |         //检查当前用户当前终端是否已存在
 | |||
|  | //        $check = $this->getFdByUid($uid, $type);
 | |||
|  | //        if (!empty($check)) {
 | |||
|  | //            foreach ($check as $item) {
 | |||
|  | //                $info = $this->getDataByFd($item);
 | |||
|  | //                $client = $info['client'] ?? 0;
 | |||
|  | //                if ($client == $user['client'] && $fd != $item) {
 | |||
|  | //                    $this->del($this->prefix . 'fd_' . $item);
 | |||
|  | //                    $this->cache->srem($this->prefix . $type . '_' . $uid, $item);
 | |||
|  | //                }
 | |||
|  | //            }
 | |||
|  | //        }
 | |||
|  | 
 | |||
|  |         // socket_fd_{fd} => ['uid' => {uid}, 'type' => {type}]
 | |||
|  |         // 以fd为键缓存当前fd的信息
 | |||
|  |         $fdKey = $this->prefix . 'fd_' . $fd; | |||
|  |         $fdData = [ | |||
|  |             'uid' => $uid, | |||
|  |             'type' => $type, | |||
|  |             'nickname' => $user['nickname'], | |||
|  |             'avatar' => $user['avatar'], | |||
|  |             'client' => $user['client'], | |||
|  |             'shop_id' => $shop_id | |||
|  |         ]; | |||
|  |         $this->cache->set($fdKey, json_encode($fdData, true)); | |||
|  | 
 | |||
|  |         // socket_user_1(user_id) => {fd} 用户userid为1 的 fd
 | |||
|  |         // socket_kefu_2(kefu_id) => {fd} 客服kefu_id为2 的 fd
 | |||
|  |         $uidKey = $this->prefix . $type . '_' . $uid; | |||
|  |         $this->cache->sadd($uidKey, $fd); | |||
|  | 
 | |||
|  |         // socket_user => {fd} 在线用户的所有fd
 | |||
|  |         if ($type == 'kefu') { | |||
|  |             $groupKey = $this->prefix . 'shop_' . $shop_id . '_kefu'; | |||
|  |         } else { | |||
|  |             $groupKey = $this->prefix . 'user'; | |||
|  |         } | |||
|  |         $this->cache->sadd($groupKey, $uid); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 移除绑定 | |||
|  |      * @param $fd | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:02 | |||
|  |      */ | |||
|  |     public function removeBind($fd) | |||
|  |     { | |||
|  |         $data = $this->getDataByFd($fd); | |||
|  |         if ($data) { | |||
|  |             $key = $this->prefix . 'user'; | |||
|  |             if($data['type'] == 'kefu') { | |||
|  |                 $key = $this->prefix . 'shop_'. $data['shop_id'] . '_kefu'; | |||
|  |             } | |||
|  |             $this->cache->srem($key, $data['uid']); // socket_user => 11
 | |||
|  |             $this->cache->srem($this->prefix . $data['type'] . '_' . $data['uid'], $fd); // socket_user_uid => fd
 | |||
|  |         } | |||
|  |         $this->cache->del($this->prefix . 'fd_' . $fd); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 通过登录id和登录类型获取对应的fd | |||
|  |      * @param $uid | |||
|  |      * @param $type | |||
|  |      * @return bool | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:02 | |||
|  |      */ | |||
|  |     public function getFdByUid($uid, $type) | |||
|  |     { | |||
|  |         $key = $this->prefix . $type . '_' . $uid; | |||
|  |         return $this->cache->sMembers($key); | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 根据fd获取登录的id和登录类型 | |||
|  |      * @param $fd | |||
|  |      * @return mixed|string | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:02 | |||
|  |      */ | |||
|  |     public function getDataByFd($fd) | |||
|  |     { | |||
|  |         $key = $this->prefix . 'fd_' . $fd; | |||
|  |         $result = $this->cache->get($key); | |||
|  |         if (!empty($result)) { | |||
|  |             $result = json_decode($result, true); | |||
|  |         } | |||
|  |         return $result; | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes ping | |||
|  |      * @param $fd | |||
|  |      * @return bool | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/20 15:19 | |||
|  |      */ | |||
|  |     public function ping($fd) | |||
|  |     { | |||
|  |         $data = $this->getDataByFd($fd); | |||
|  |         if (!empty($data)) { | |||
|  |             return $this->pushData($fd, 'ping', ['client_time' => time()]); | |||
|  |         } | |||
|  |         return true; | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 推送数据 | |||
|  |      * @param $fd | |||
|  |      * @param $event | |||
|  |      * @param $data | |||
|  |      * @return bool | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/15 19:02 | |||
|  |      */ | |||
|  |     public function pushData($fd, $event, $data) | |||
|  |     { | |||
|  |         $data = $this->parser->encode($event, $data); | |||
|  | 
 | |||
|  |         // fd非数组时转为数组
 | |||
|  |         if (!is_array($fd)) { | |||
|  |             $fd = [$fd]; | |||
|  |         } | |||
|  | 
 | |||
|  |         // 向fd发送消息
 | |||
|  |         foreach ($fd as $item) { | |||
|  |             if ($this->server->exist($item)) { | |||
|  |                 $this->server->push($item, $data); | |||
|  |             } | |||
|  |         } | |||
|  |         return true; | |||
|  |     } | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @notes 在线fd | |||
|  |      * @param $fd | |||
|  |      * @return array | |||
|  |      * @author 段誉 | |||
|  |      * @date 2021/12/17 18:19 | |||
|  |      */ | |||
|  |     public function onlineFd($fd) | |||
|  |     { | |||
|  |         $result = []; | |||
|  | 
 | |||
|  |         if (empty($fd)) { | |||
|  |             return $result; | |||
|  |         } | |||
|  | 
 | |||
|  |         if (!is_array($fd)) { | |||
|  |             $fd = [$fd]; | |||
|  |         } | |||
|  | 
 | |||
|  |         foreach ($fd as $item) { | |||
|  | 
 | |||
|  | //            $fd_data = $this->getDataByFd($fd);
 | |||
|  | //            $fd_shop_id = $fd_data['shop_id'] ?? 0;
 | |||
|  | //            if ($fd_shop_id != $shop_id) {
 | |||
|  | //                continue;
 | |||
|  | //            }
 | |||
|  | 
 | |||
|  |             if ($this->server->exist($item)) { | |||
|  |                 $result[] = $item; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         return $result; | |||
|  |     } | |||
|  | 
 | |||
|  | } |