ShoproChat.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <?php
  2. namespace addons\shopro\console;
  3. use think\console\Input;
  4. use think\console\Output;
  5. use think\console\input\Argument;
  6. use think\console\input\Option;
  7. use think\exception\HttpResponseException;
  8. use Workerman\Worker;
  9. use Workerman\Timer;
  10. use PHPSocketIO\SocketIO;
  11. use Workerman\Protocols\Http\Request;
  12. use Workerman\Connection\TcpConnection;
  13. use addons\shopro\library\chat\Chat;
  14. use addons\shopro\library\chat\Getter;
  15. use addons\shopro\library\chat\Sender;
  16. use addons\shopro\library\chat\traits\Helper;
  17. use think\Db;
  18. class ShoproChat extends Command
  19. {
  20. use Helper;
  21. protected $input = null;
  22. protected $output = null;
  23. /**
  24. * 帮助命令配置
  25. */
  26. protected function configure()
  27. {
  28. $this->setName('shopro:chat')
  29. ->addArgument('action', Argument::OPTIONAL, "action start|stop|restart|status", 'start')
  30. ->addArgument('type', Argument::OPTIONAL, "d -d")
  31. ->addOption('debug', null, Option::VALUE_OPTIONAL, '开启调试模式', false)
  32. ->setHelp('此命令是用来启动 Shopro商城 的客服服务端进程')
  33. ->setDescription('Shopro商城 客服');
  34. }
  35. /**
  36. * 执行帮助命令
  37. */
  38. protected function execute(Input $input, Output $output)
  39. {
  40. $this->input = $input;
  41. $this->output = $output;
  42. global $argv;
  43. $action = $input->getArgument('action');
  44. $type = $input->getArgument('type') ? '-d' : '';
  45. $debug = $input->hasOption('debug');
  46. if (strpos(strtolower(PHP_OS), 'win') === false) {
  47. // windows 不需要设置参数
  48. $argv = [];
  49. $argv[0] = 'think shopro:chat';
  50. $argv[1] = $action;
  51. $argv[2] = $type ? '-d' : '';
  52. }
  53. $this->start($input, $output, $debug);
  54. }
  55. private function start($input, $output, $debug)
  56. {
  57. $chatSystem = $this->getConfig('system');
  58. $ssl = $chatSystem['ssl'] ?? 'none';
  59. $ssl_cert = $chatSystem['ssl_cert'] ?? '';
  60. $ssl_key = $chatSystem['ssl_key'] ?? '';
  61. $worker_num = $chatSystem['worker_num'] ?? 1;
  62. $port = $chatSystem['port'] ?? '';
  63. $port = $port ? intval($port) : 2121;
  64. $inside_host = $chatSystem['inside_host'] ?? '';
  65. $inside_host = '0.0.0.0'; // 这里默认都只绑定 0.0.0.0
  66. $inside_port = $chatSystem['inside_port'] ?? '';
  67. $inside_port = $inside_port ? intval($inside_port) : 9191;
  68. // 创建socket.io服务端
  69. $context = [
  70. // 'pingInterval' => '10', // 参数不可用
  71. // 'pingTimeout' => '50' // 参数不对,不可用
  72. ];
  73. if ($ssl == 'cert') {
  74. // 证书模式
  75. $context['ssl'] = [
  76. 'local_cert' => $ssl_cert,
  77. 'local_pk' => $ssl_key,
  78. 'verify_peer' => false
  79. ];
  80. }
  81. $io = new SocketIO($port, $context);
  82. $io->worker->name = 'ShoproChatWorker';
  83. // $io->worker->count = $worker_num; // 启动 worker 的进程数量,经测试 linux 上不支持设置多个进程, 再启动 web-msg-sender 时候 会导致多次启动同一个端口,端口被占用的情况
  84. $io->debug = $debug; // 自定义 debug
  85. // 定义命名空间
  86. $nsp = $io->of('/chat');
  87. $io->on('workerStart', function () use ($io, $nsp, $inside_host, $inside_port) {
  88. $inner_http_worker = new Worker('http://' . $inside_host . ':' . $inside_port);
  89. $inner_http_worker->onMessage = function (TcpConnection $httpConnection, Request $request) use ($io, $nsp) {
  90. // 请求地址
  91. $uri = $request->uri();
  92. // 请求参数
  93. $data = $request->post();
  94. $chat = new Chat($io, $nsp);
  95. $chat->innerWorker($httpConnection, $uri, $data);
  96. };
  97. $inner_http_worker->listen();
  98. // 添加排队等待定时器 【30 秒 通知一次等待中的用户,有等待中用户被接入时也会主动通知等待中用户】
  99. $getter = new Getter(null, $io, $nsp);
  100. $sender = new Sender(null, $io, $nsp, $getter);
  101. Timer::add(30, function () use ($getter, $sender) {
  102. // 定时通知所有房间中排队用户排名变化
  103. $sender->allWaitingQueue();
  104. });
  105. Timer::add(15, function () use ($getter) {
  106. // 更新客服忙碌度
  107. $getter->updateCustomerServiceBusyPercent();
  108. });
  109. });
  110. // 当有客户端连接时打印一行文字
  111. $nsp->on('connection', function($socket) use ($io) {
  112. $nsp = $io->of('/chat');
  113. // 连接时候只走一次,后续发消息,这个方法就不走了
  114. // 绑定客服连接事件
  115. try {
  116. $chat = new Chat($io, $nsp, $socket);
  117. $chat->on();
  118. } catch (HttpResponseException $e) {
  119. $data = $e->getResponse()->getData();
  120. $message = $data ? ($data['msg'] ?? '') : $e->getMessage();
  121. echo $message;
  122. } catch (\Exception $e) {
  123. echo $e->getMessage();
  124. }
  125. });
  126. // 定义第二个命名空间
  127. // $nsp = $io->of('/server');
  128. // $nsp->on('connection', function($socket) use ($io) {
  129. // // $chat = new Chat($io, $socket);
  130. // // $chat->on();
  131. // echo "new connection server\n";
  132. // });
  133. // 断开 mysql 连接,防止 2006 MySQL server has gone away 错误
  134. Db::close();
  135. // 日志文件
  136. if (!is_dir(RUNTIME_PATH . 'log/chat')) {
  137. @mkdir(RUNTIME_PATH . 'log/chat', 0755, true);
  138. }
  139. Worker::$logFile = RUNTIME_PATH . 'log/chat/shopro_chat.log';
  140. Worker::$stdoutFile = RUNTIME_PATH . 'log/chat/std_out.log'; // 如果部署的时候部署错误(比如未删除php禁用函数),会产生大量日志,先关掉
  141. Worker::runAll();
  142. }
  143. }