Chat.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <?php
  2. namespace addons\shopro\library\chat;
  3. use addons\shopro\exception\ShoproException;
  4. use addons\shopro\library\chat\traits\DebugEvent;
  5. use addons\shopro\library\chat\traits\Helper;
  6. use addons\shopro\library\chat\traits\Session;
  7. use addons\shopro\library\chat\traits\NspData;
  8. use addons\shopro\library\chat\traits\BindUId;
  9. use PHPSocketIO\SocketIO;
  10. use PHPSocketIO\Socket;
  11. use PHPSocketIO\Nsp;
  12. class Chat
  13. {
  14. /**
  15. * session 存储助手
  16. */
  17. use Session;
  18. /**
  19. * 绑定 UID 助手
  20. */
  21. use BindUId;
  22. /**
  23. * 绑定数据到 nsp 作为全局数据
  24. */
  25. use NspData;
  26. /**
  27. * 助手方法
  28. */
  29. use Helper;
  30. /**
  31. * debug 方式注册事件
  32. */
  33. use DebugEvent;
  34. /**
  35. * 当前 phpsocket.io 实例
  36. *
  37. * @var SocketIO
  38. */
  39. public $io;
  40. /**
  41. * 当前连接实例
  42. *
  43. * @var Socket
  44. */
  45. public $socket;
  46. /**
  47. * 当前 namespace 实例
  48. *
  49. * @var Nsp
  50. */
  51. public $nsp;
  52. /**
  53. * 当前发送实例
  54. *
  55. * @var Sender
  56. */
  57. public $sender;
  58. /**
  59. * 当前获取数据
  60. *
  61. * @var Getter
  62. */
  63. public $getter;
  64. /**
  65. * chat 操作类
  66. *
  67. * @var ChatService
  68. */
  69. public $chatService;
  70. protected $auth = [
  71. 'user',
  72. 'admin',
  73. ];
  74. /**
  75. * 初始化 chat 系统
  76. *
  77. * @param SocketIo $io
  78. * @param Socket $socket
  79. * @param Nsp $nsp
  80. */
  81. public function __construct(SocketIo $io, Nsp $nsp, Socket $socket = null)
  82. {
  83. $this->io = $io;
  84. $this->socket = $socket;
  85. $this->nsp = $nsp;
  86. // 初始化获取更改数据实例
  87. $this->getter = new Getter($socket, $io, $nsp);
  88. // 初始化发送实例
  89. $this->sender = new Sender($socket, $io, $nsp, $this->getter);
  90. // 初始化 客服公共方法实例
  91. $this->chatService = new ChatService($socket, $io, $nsp, $this->getter);
  92. }
  93. public function on()
  94. {
  95. // on 方法只有在连接的时候走一次
  96. $this->register('test', function ($data, $callback) {
  97. // $class = "\\app\\chat\\library\\provider\\auth\\User";
  98. // $provider = new $class($this);
  99. // $this->socket->removeAllListeners('message');
  100. // 注册相关身份事件
  101. // $provider->customerEvent();
  102. $customer_service_room = $this->getRoomName('customer_service_room', ['room_id' => 'admin']);
  103. $customerServices = $this->getter('socket')->getSessionsByRoom($customer_service_room, 'customer_service');
  104. $this->sender->successSocket($callback, '连接成功', [
  105. 'msg' => '恭喜鏈接成功',
  106. 'bind' => $this->nsp->bind ?? [],
  107. 'nsp_room_ids' => $this->nspData('room_ids'),
  108. 'customer_service' => $customerServices,
  109. 'nsp_data' => $this->nsp->nspData ?? [],
  110. 'rooms' => isset($this->nsp->adapter->rooms) ? $this->nsp->adapter->rooms : [],
  111. 'current_rooms' => $this->socket->rooms,
  112. 'session' => $this->session(),
  113. 'client_id' => $this->socket->id,
  114. 'session_ids' => $this->nspData('session_ids')
  115. ]);
  116. $this->sender->successUId('new message', '消息桶送', ['aaa' => 'bbb'], [
  117. 'id' => $this->session('session_id'),
  118. 'type' => $this->session('auth'),
  119. ]);
  120. // foreach ($clientIds as $client_id) {
  121. // $this->sender->successSocket('new message', ['aaa' => 'bbb']);
  122. // }
  123. $this->socket->on('test-child', function ($data, $callback) {
  124. echo "子集消息来了";
  125. $this->session('text:child', 'aaa');
  126. $this->sender->successSocket($callback, '连接成功', [
  127. 'msg' => '子事件夜之星成功了'
  128. ]);
  129. });
  130. });
  131. // socket 连接初始化,socket.io-client 连接后的第一步
  132. $this->register('connection', function ($data, $callback) {
  133. // 初始化连接
  134. $auth = $data['auth'] ?? '';
  135. if (!in_array($auth, $this->auth)) {
  136. throw new ShoproException('身份错误');
  137. }
  138. // 存储当前 auth 驱动
  139. $this->session('auth', $auth);
  140. // 加入对应身份组
  141. $this->socket->join($this->getRoomName('auth', ['auth' => $auth]));
  142. // 加入在线连接组
  143. $this->socket->join('online');
  144. // 检测并自动登录
  145. $result = $this->chatService->authLogin($data);
  146. // 注册各自身份的事件
  147. $this->authEvent($auth);
  148. // 连接成功,发送给自己
  149. $this->sender->authSuccess($callback);
  150. });
  151. // auth 身份登录,管理员或者用户
  152. $this->register('login', function ($data, $callback) {
  153. // 登录,和系统中的用户或者管理员绑定
  154. $result = $this->chatService->authLogin($data);
  155. if ($result) {
  156. // 登录成功
  157. $this->sender->authSuccess($callback);
  158. return true;
  159. }
  160. // 登录失败
  161. throw new ShoproException('登录失败');
  162. });
  163. /**
  164. * 断开连接
  165. */
  166. $this->register('disconnect', function ($data, $callback) {
  167. $customer_service_id = $this->session('customer_service_id');
  168. $session_id = $this->session('session_id');
  169. $identify = $this->session('identify') ?: '';
  170. // $auth = $this->session('auth');
  171. // $authUser = $this->session('auth_user');
  172. // 断开连接,解绑 bind 的身份
  173. $this->chatService->disconnectUnbindAll();
  174. // 如果登录了,并且所有客户端都下线了, 删除相关 auth 的 ids
  175. // if ($this->chatService->isLogin() && !$this->getter('socket')->isOnlineAuthBySessionId($session_id, $auth)) {
  176. // $this->nspSessionIdDel($session_id, $auth);
  177. // }
  178. // 如果是顾客
  179. if ($identify == 'customer') {
  180. // 顾客所在房间
  181. $room_id = $this->session('room_id');
  182. // 顾客断开连接
  183. if (!$this->getter('socket')->isOnLineCustomerById($session_id)) {
  184. // 当前所遇用户端都断开了
  185. $waiting_room_name = $this->getRoomName('customer_service_room_waiting', ['room_id' => $room_id]);
  186. $rooms = $this->socket->rooms;
  187. // 判断是否在排队中
  188. if (in_array($waiting_room_name, $rooms)) {
  189. // 这里顾客的所有客户端都断开了,在排队排名中移除
  190. $this->nspWaitingDel($room_id, $session_id);
  191. // 排队发生变化,通知房间中所有排队用户
  192. $this->sender->allWaitingQueue($room_id);
  193. // 离开排队中房间(将离线的用户从等待中移除)
  194. $this->socket->leave($waiting_room_name);
  195. // 通知更新排队中列表,把当前下线用户移除
  196. $this->sender->waiting();
  197. }
  198. // 通知所有客服,顾客下线
  199. $this->sender->customerOffline();
  200. }
  201. }
  202. // 如果是客服
  203. if ($identify == 'customer_service') {
  204. // 客服断开连接
  205. if (!$this->getter('socket')->isOnLineCustomerServiceById($customer_service_id)) {
  206. // 当前客服的所有客户端都下线了
  207. // 更新客服状态为离线
  208. $this->getter()->updateCustomerServiceStatus($customer_service_id, 'offline');
  209. // 通知连接的用户(在当前客服服务的房间里面的用户),客服下线了
  210. $this->sender->customerServiceOffline();
  211. // 通知当前房间的在线客服,更新当前在线客服列表
  212. $this->sender->customerServiceUpdate();
  213. }
  214. }
  215. });
  216. }
  217. /**
  218. * 注册相关身份事件
  219. *
  220. * @param string $auth
  221. * @return void
  222. */
  223. private function authEvent($auth)
  224. {
  225. // 实例化相关身份事件
  226. $class = "\\addons\\shopro\\library\\chat\\provider\\auth\\" . ucfirst($auth);
  227. $provider = new $class($this);
  228. // 注册相关身份事件
  229. $provider->on();
  230. }
  231. /**
  232. * 站内信通知
  233. *
  234. * @param object $http_connection
  235. * @param string $uri
  236. * @param array $data
  237. * @return void
  238. */
  239. public function innerWorker($httpConnection, $uri, $data)
  240. {
  241. if ($uri == '/notification') {
  242. $this->exec($httpConnection, function () use ($data) {
  243. $receiver = $data['receiver'] ?? [];
  244. $sendData = $data['data'] ?? [];
  245. $receiver_type = $receiver['type'] ?? 'user';
  246. $receiverIds = $receiver['ids'] ?? '';
  247. $receiverIds = is_array($receiverIds) ? $receiverIds : explode(',', $receiverIds);
  248. // 循环给接收者发送消息
  249. foreach ($receiverIds as $id) {
  250. // 获取接收人的 session_id
  251. $session_id = $this->getter()->getSessionIdByAuth($id, $receiver_type);
  252. if ($session_id) {
  253. $this->sender->successUId('notification', '收到通知', $sendData, [
  254. 'id' => $session_id, 'type' => $receiver_type
  255. ]);
  256. }
  257. }
  258. });
  259. }
  260. // 这句话必须有,否则会提示超时
  261. $httpConnection->send('ok');
  262. }
  263. }