WebSocket.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <?php
  2. /**
  3. * FastIm v1.0.0
  4. * FastAdmin企业IM客服系统
  5. * https://www.fastadmin.net/store/fastim.html
  6. *
  7. * Copyright 2021 FastAdmin:thinkphp
  8. *
  9. * FastAdmin企业IM客服系统不是开源产品,所有文字、图片、样式、风格等版权归企业IM客服系统作者所有,如有复制、仿冒、抄袭、盗用,FastAdmin和企业IM客服系统作者将追究法律责任
  10. *
  11. * Released on: November 8, 2021
  12. */
  13. namespace addons\fastim\library\swoole;
  14. use think\Db;
  15. use Swoole\Server\Helper;
  16. use Swoole\WebSocket\Frame;
  17. use Swoole\WebSocket\Server as WebSocketServer;
  18. use Exception;
  19. use Swoole\Timer;
  20. /**
  21. * Swoole WebSocket Server 命令行服务类
  22. * 此文件的修改不支持热更新,请于更新后重启swoole-websocket服务
  23. */
  24. class WebSocket
  25. {
  26. protected $monitor;
  27. protected $lastMtime;
  28. protected $config;
  29. /**
  30. * Swoole对象
  31. * @var object
  32. */
  33. protected $swoole;
  34. /**
  35. * Socket的类型
  36. * @var int
  37. */
  38. protected $sockType = SWOOLE_SOCK_TCP;
  39. /**
  40. * 运行模式
  41. * @var int
  42. */
  43. protected $mode = SWOOLE_PROCESS;
  44. /**
  45. * swooleCommon 类实例
  46. */
  47. protected $swooleCommon;
  48. /**
  49. * 支持的响应事件
  50. * @var array
  51. */
  52. protected $event = [
  53. 'Start',
  54. 'Shutdown',
  55. 'WorkerStart',
  56. 'WorkerStop',
  57. 'WorkerExit',
  58. 'Close',
  59. 'Task',
  60. 'Finish',
  61. 'PipeMessage',
  62. 'WorkerError',
  63. 'ManagerStart',
  64. 'ManagerStop',
  65. 'Open',
  66. 'Message',
  67. 'HandShake',
  68. 'Request'
  69. ];
  70. function __construct()
  71. {
  72. $this->config = get_addon_config('fastim');
  73. if ($this->config['wss_switch']) {
  74. if (file_exists($this->config['ssl_cert_file']) && file_exists($this->config['ssl_key_file'])) {
  75. $this->sockType = SWOOLE_SOCK_TCP | SWOOLE_SSL;
  76. } else {
  77. throw new Exception('SSL certificate file does not exist!');
  78. }
  79. }
  80. $this->swoole = new WebSocketServer('0.0.0.0', $this->config['websocket_port'], $this->mode, $this->sockType);
  81. }
  82. /**
  83. * Worker 进程启动
  84. * @param $server
  85. * @param $worker_id
  86. */
  87. public function onWorkerStart($server, $worker_id)
  88. {
  89. $this->lastMtime = time();
  90. if (0 == $worker_id && $this->monitor) {
  91. $this->monitor($server);
  92. }
  93. // print_r(get_included_files());// 查看不支持热更新的文件列表
  94. // 保持mysql链接可用性
  95. $server->tick(27000, function () {
  96. Db::execute("SELECT 1");
  97. });
  98. }
  99. /**
  100. * 链接握手成功
  101. * @param $server
  102. * @param $frame
  103. */
  104. public function onOpen($ws, $request)
  105. {
  106. $ws->push($request->fd, json_encode([
  107. 'event' => 'open'
  108. ]));
  109. }
  110. /**
  111. * 收到数据帧
  112. * @param $server
  113. * @param $frame
  114. */
  115. public function onMessage($server, $frame)
  116. {
  117. $data = json_decode($frame->data, true);
  118. // 安全检查过滤
  119. array_walk_recursive($data, ['addons\fastim\library\Common', 'checkVariable']);
  120. if (!is_array($data) || !isset($data['c']) || !isset($data['a'])) {
  121. $server->push($frame->fd, json_encode([
  122. 'event' => 'show_msg',
  123. 'data' => '错误的请求!',
  124. 'close' => true
  125. ]));
  126. $server->close($frame->fd);
  127. return;
  128. }
  129. // 载入文件类似:根目录/addons/fastim/library/controller/index.php
  130. $filename = __DIR__ . '/../controller/' . $data['c'] . '.php';
  131. if (file_exists($filename)) {
  132. require_once $filename;
  133. // 检查要访问的类是否存在
  134. if (!class_exists($data['c'], false)) {
  135. $server->push($frame->fd, json_encode([
  136. 'event' => 'show_msg',
  137. 'data' => '访问的控制器不存在!',
  138. 'close' => true
  139. ]));
  140. $server->close($frame->fd);
  141. return;
  142. }
  143. } else {
  144. $server->push($frame->fd, json_encode([
  145. 'event' => 'show_msg',
  146. 'data' => '错误的请求!',
  147. 'close' => true
  148. ]));
  149. $server->close($frame->fd);
  150. return;
  151. }
  152. $o = new $data['c']([$server, $frame, $this->swooleCommon]); // 新建对象
  153. if (!method_exists($o, $data['a'])) {
  154. $server->push($frame->fd, json_encode([
  155. 'event' => 'show_msg',
  156. 'data' => '访问的方法不存在!'
  157. ]));
  158. return;
  159. }
  160. if ($o->authCheck($data['a'])) {
  161. $data['data'] = $data['data'] ?? [];
  162. call_user_func_array([$o, $data['a']], [$data['data']]); //调用对象$o($c)里的方法$a
  163. }
  164. }
  165. /**
  166. * 链接关闭
  167. */
  168. public function onClose($server, $fd, $reactorId)
  169. {
  170. // 解除fd绑定并修改用户状态
  171. $uid = $this->swooleCommon->getUidByFd($fd);
  172. if (!$this->swooleCommon->unbindUid($fd, $uid)) {
  173. /**
  174. * 用户断开1分钟后才设置为离线状态
  175. * Timer::after 不同于sleep,不会阻塞
  176. */
  177. Timer::after(60000, function () use ($uid) {
  178. if (!$this->swooleCommon->getFdByUid($uid)) {
  179. Db::name('fastim_user')->where('id', $uid)->update([
  180. 'status' => 0
  181. ]);
  182. $this->swooleCommon->radioStatusMessage($uid, 0);
  183. }
  184. });
  185. }
  186. }
  187. public function option(array $option)
  188. {
  189. if (!empty($option)) {
  190. $this->swoole->set($this->checkOptions($option));
  191. }
  192. // 注册回调
  193. foreach ($this->event as $event) {
  194. if (method_exists($this, 'on' . $event)) {
  195. $this->swoole->on($event, [$this, 'on' . $event]);
  196. }
  197. }
  198. // 实例化swooleCommon类
  199. $this->swooleCommon = new \addons\fastim\library\swoole\Common($option['max_connections'], $this->swoole);
  200. }
  201. protected function checkOptions(array $options)
  202. {
  203. if (class_exists(Helper::class)) {
  204. $constOptions = Helper::GLOBAL_OPTIONS + Helper::SERVER_OPTIONS + Helper::PORT_OPTIONS + Helper::HELPER_OPTIONS;
  205. foreach ($options as $k => $v) {
  206. if (!array_key_exists(strtolower($k), $constOptions)) {
  207. unset($options[$k]);
  208. }
  209. }
  210. }
  211. return $options;
  212. }
  213. public function setMonitor($interval = 2, $path = [])
  214. {
  215. $this->monitor['interval'] = $interval;
  216. $this->monitor['path'] = (array)$path;
  217. }
  218. /**
  219. * 文件监控
  220. *
  221. * @param $server
  222. */
  223. public function monitor($server)
  224. {
  225. if ($this->monitor['path']) {
  226. $server->tick($this->monitor['interval'], function () use ($server) {
  227. foreach ($this->monitor['path'] as $path) {
  228. $dir = new \RecursiveDirectoryIterator($path);
  229. $iterator = new \RecursiveIteratorIterator($dir);
  230. foreach ($iterator as $file) {
  231. if (pathinfo($file, PATHINFO_EXTENSION) != 'php') {
  232. continue;
  233. }
  234. if ($this->lastMtime < $file->getMTime()) {
  235. $this->lastMtime = $file->getMTime();
  236. echo '[update]' . $file . " reload...\n";
  237. $server->reload();
  238. return;
  239. }
  240. }
  241. }
  242. });
  243. }
  244. }
  245. /**
  246. * 魔术方法 有不存在的操作的时候执行
  247. * @access public
  248. * @param string $method 方法名
  249. * @param array $args 参数
  250. * @return mixed
  251. */
  252. public function __call($method, $args)
  253. {
  254. call_user_func_array([$this->swoole, $method], $args);
  255. }
  256. }