SocketServer.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Helpers\Jwt;
  4. use App\Models\DepositModel;
  5. use App\Models\MessageModel;
  6. use App\Services\Api\DepositService;
  7. use App\Services\Api\MemberService;
  8. use App\Services\Api\OrderService;
  9. use App\Services\Common\MessageService;
  10. use App\Services\ConfigService;
  11. use App\Services\RedisService;
  12. use Illuminate\Console\Command;
  13. use const http\Client\Curl\SSL_VERSION_SSLv3;
  14. use const http\Client\Curl\SSL_VERSION_TLSv1_2;
  15. class SocketServer extends Command
  16. {
  17. public $ws;
  18. /**
  19. * The name and signature of the console command.
  20. *
  21. * @var string
  22. */
  23. protected $signature = 'swoole:chat {op?}';
  24. /**
  25. * The console command description.
  26. *
  27. * @var string
  28. */
  29. protected $description = 'Chat server run';
  30. protected $logger = null;
  31. /**
  32. * Create a new command instance.
  33. *
  34. * @return void
  35. */
  36. public function __construct()
  37. {
  38. parent::__construct();
  39. }
  40. /**
  41. * Execute the console command.
  42. *
  43. * @return mixed
  44. */
  45. public function handle()
  46. {
  47. $op = $this->argument('op');
  48. $op = $op ? $op : 'start';
  49. if ($op == 'start') {
  50. echo "swoole chat service start ...\n";
  51. $this->start();
  52. } else if ($op == 'stop') {
  53. echo "swoole chat service stop ...\n";
  54. $this->stop();
  55. }
  56. }
  57. /**
  58. * 运行
  59. */
  60. public function start()
  61. {
  62. try {
  63. //创建websocket服务器对象,监听0.0.0.0:7104端口
  64. // HTTPS 通过nginx 配置证书即可
  65. $this->ws = new \Swoole\WebSocket\Server("0.0.0.0", env('SOCKET_PORT', '8660'));
  66. //监听WebSocket连接打开事件
  67. $this->ws->on('open', [$this, 'open']);
  68. //监听WebSocket消息事件
  69. $this->ws->on('message', [$this, 'message']);
  70. //监听WebSocket主动推送消息事件
  71. $this->ws->on('request', [$this, 'request']);
  72. //监听WebSocket连接关闭事件
  73. $this->ws->on('close', [$this, 'close']);
  74. $this->ws->start();
  75. } catch (\Exception $exception) {
  76. $date = date('Y-m-d H:i:s');
  77. RedisService::set("caches:sockets:error", $exception->getMessage(), 600);
  78. $this->info("【{$date}】Socket:运行异常=》" . $exception->getMessage());
  79. }
  80. }
  81. /**
  82. * 建立连接
  83. * @param $ws
  84. * @param $request
  85. */
  86. public function open($ws, $request)
  87. {
  88. $date = date('Y-m-d H:i:s');
  89. $logFile = '/storage/logs/swoole-task-'.date('Y-m-d', time() - 86400).'.log';
  90. if(file_exists(base_path().$logFile)){
  91. unlink(base_path().$logFile);
  92. }
  93. $fdData = RedisService::get("chats:bind:chat_1");
  94. $customFd = $fdData && isset($fdData['fd'])? $fdData['fd'] : 0;
  95. $checkFd = RedisService::get("chats:frames:" . $customFd);
  96. RedisService::clear('chats:checkCustom:from_'.$request->fd);
  97. $this->ws->push($request->fd, json_encode(['success' => 'true', 'op' => 'conn', 'message' => '连接成功','custom_fd'=>$checkFd && $customFd? $customFd:0, 'fd' => $request->fd], 256));
  98. $this->info("【{$date}】Socket:客户端【{$request->fd}】连接成功");
  99. }
  100. /**
  101. * 接收消息
  102. * @param $ws
  103. * @param $frame
  104. */
  105. public function message($ws, $frame)
  106. {
  107. $date = date('Y-m-d H:i:s');
  108. RedisService::set("chats:frames:" . $frame->fd, json_decode($frame->data, true), 86400);
  109. $fdData = RedisService::get("chats:bind:chat_1");
  110. $customFd = $fdData && isset($fdData['fd'])? $fdData['fd'] : 0;
  111. if ($frame->data == 'ping') {
  112. $this->ws->push($frame->fd, 'pong');
  113. if($customFd != $frame->fd){
  114. $this->sendMsg($frame->fd, ['success' => true,'op'=>'custom', 'message' => $customFd?'客服上线':'客服离线', 'scene'=>'check', 'data' => ['online'=>$customFd,'to_fd'=>$customFd], 't' => time()]);
  115. }
  116. // 推送客服保证金信息
  117. if($customFd == $frame->fd){
  118. $messages = MessageService::make()->getUnreadList(0);
  119. $count = isset($messages['count'])? $messages['count'] : 0;
  120. if($count>0){
  121. $this->sendMsg($frame->fd, ['success' => true,'op'=>'notice', 'message' => '消息通知', 'scene'=>'deposit', 'data' => $messages, 't' => time()]);
  122. }
  123. }
  124. $this->info("【{$date}】Socket:客户端【{$frame->fd}】心跳包",false);
  125. return false;
  126. }
  127. // 推送客服保证金信息
  128. else if($customFd && RedisService::get("chats:frames:" . $customFd)){
  129. $messages = MessageService::make()->getUnreadList(0);
  130. $count = isset($messages['count'])? $messages['count'] : 0;
  131. if($count>0){
  132. $this->sendMsg($customFd, ['success' => true,'op'=>'notice', 'message' => '消息通知', 'scene'=>'deposit', 'data' => $messages, 't' => time()]);
  133. }
  134. }
  135. // 消息处理
  136. $frameId = $frame->fd;
  137. $data = $frame->data ? json_decode($frame->data, true) : [];
  138. $fromUid = isset($data['from_uid']) ? intval($data['from_uid']) : 0;
  139. $isCustom = isset($data['is_custom']) ? intval($data['is_custom']) : 0;
  140. $token = isset($data['token']) ? $data['token'] : '';
  141. $op = isset($data['op']) ? $data['op'] : '';
  142. $scene = isset($data['scene']) && $data['scene'] ? $data['scene'] : 'chat';
  143. $toUid = isset($data['to_uid']) ? intval($data['to_uid']) : 0;
  144. $jwt = new Jwt('jwt_jd_app');
  145. $userId = $jwt->verifyToken($token);
  146. if (!$isCustom && $userId != $fromUid) {
  147. $this->info("【{$scene} {$date}】Socket:请先登录再连接【{$frameId}-{$fromUid}】");
  148. $this->sendMsg($frameId, ['success' => false,'op'=>'error', 'message' => '请先登录', 'scene'=>$scene, 'data' => $data, 't' => time()]);
  149. return false;
  150. }
  151. if ($op != 'login' && ($toUid<=0 || $fromUid == $toUid)) {
  152. $this->info("【{$scene} {$date}】Socket:参数错误【{$fromUid}-{$toUid}】");
  153. $this->sendMsg($frameId, ['success' => false,'op'=>'error', 'message' => '参数错误,请先选择回复用户~', 'scene'=>$scene, 'data' => $data, 't' => time()]);
  154. return false;
  155. }
  156. // 签名验证
  157. $system = isset($data['system']) ? $data['system'] : [];
  158. $uuid = isset($system['uuid'])? $system['uuid'] : 0;
  159. $ctime = isset($system['ct'])? $system['ct'] : 0;
  160. $message = isset($data['message']) ? trim($data['message']) : '';
  161. $sign = isset($data['sign']) ? trim($data['sign']) : '';
  162. $checkSign = getSign($op.'&'.$message.'&'.$fromUid.'&'.$uuid.'&'.$ctime);
  163. if($checkSign != $sign){
  164. $this->info("【{$scene} {$date}】Socket:签名失败【{$frameId}-{$fromUid}】");
  165. $this->sendMsg($frameId, ['success' => false,'op'=>'error', 'message' => '请求签名失败', 'scene'=>$scene,'sign'=>$checkSign, 'data' => $data, 't' => time()]);
  166. return false;
  167. }
  168. $apiUrl = env('APP_URL','');
  169. $chatKey = isset($data['chat_key']) ? trim($data['chat_key']) : '';
  170. $chatKey = $chatKey ? $chatKey : getChatKey($fromUid, $toUid);
  171. try {
  172. // 推送Fd处理
  173. if ($fromUid && $frameId) {
  174. RedisService::set("chats:bind:{$scene}_{$fromUid}", ['fd' => $frameId,'scene'=>$scene, 'user_id' => $fromUid, 'uuid' => $uuid, 'chat_key' => $chatKey], 3600);
  175. }
  176. switch ($op) {
  177. case 'chat': // 图文聊天
  178. $msgType = isset($data['msg_type']) ? $data['msg_type'] : 1;
  179. // 发送参数验证
  180. if ($fromUid <= 0 || empty($message)) {
  181. $this->info("【{$scene} {$date}】Chat:参数错误,from@{$fromUid}-to@{$toUid}。");
  182. $this->sendMsg($frameId, ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$data, 'message' => '参数错误']);
  183. return false;
  184. }
  185. // 敏感词过滤
  186. $message = \App\Services\Api\MessageService::make()->checkMessage($message);
  187. // 用户私聊
  188. $fromInfo = [];
  189. if($fromUid>1){
  190. $fromInfo = MemberService::make()->getInfo(['id'=> $fromUid,'status'=>1],[],false);
  191. if(empty($fromInfo)){
  192. $this->info("【{$scene} {$date}】Chat:发送用户不存在,from@{$fromUid}-to@{$toUid}。");
  193. $this->sendMsg($frameId, ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$data, 'message' => '您的账号不可用或已冻结,请联系客服']);
  194. return false;
  195. }
  196. }
  197. $toInfo = [];
  198. if($toUid>1){
  199. $toInfo = MemberService::make()->getInfo(['id'=> $toUid,'status'=>1],[],false);
  200. if(empty($toInfo)){
  201. $this->info("【{$scene} {$date}】Chat:接收用户不存在,from@{$fromUid}-to@{$toUid}。");
  202. $this->sendMsg($frameId, ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$data, 'message' => '对方账号不可用或无法接收消息']);
  203. return false;
  204. }
  205. }
  206. $fromUserName = isset($fromInfo['realname'])? $fromInfo['realname'] : ($fromUid>1? '用户'.$fromUid:'客服');
  207. $fromAvatar = isset($fromInfo['avatar']) && $fromInfo['avatar']? $fromInfo['avatar'] : get_image_url($fromUid>1?'/images/member/logo.png':'/images/member/custom.png');
  208. $toUserName = isset($toInfo['realname'])? $toInfo['realname'] : ($toUid>1? '用户'.$toUid:'客服');
  209. $toAvatar = isset($toInfo['avatar']) && $toInfo['avatar']? $toInfo['avatar'] : get_image_url($toUid>1?'/images/member/logo.png':'/images/member/custom.png');
  210. $msgData = [
  211. 'from_uid' => $fromUid,
  212. 'to_uid' => $toUid,
  213. 'type' => 1,
  214. 'msg_type' => $msgType,
  215. 'title' => '聊天消息',
  216. 'description' => $msgType == 2 ? '[图片]' : mb_substr($message, 0, 20),
  217. 'content' => $msgType==2? get_image_path($message) : $message,
  218. 'chat_key' => $chatKey,
  219. 'create_time' => time(),
  220. 'update_time' => time(),
  221. 'is_read' => 2,
  222. 'status' => 1
  223. ];
  224. // 敏感消息
  225. if(empty($message)){
  226. $msgData['from_user_name'] = $fromUserName;
  227. $msgData['from_user_avatar'] = get_image_url($fromAvatar, $apiUrl);
  228. $msgData['to_user_name'] = $toUserName;
  229. $msgData['to_user_avatar'] = get_image_url($toAvatar, $apiUrl);
  230. $msgData['time_text'] = dateFormat($msgData['create_time']);
  231. $msgData['content'] = '<span style="color: red;">敏感内容</span>';
  232. $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
  233. return false;
  234. }
  235. if (!$id = MessageModel::insertGetId($msgData)) {
  236. $data = ['success' => false,'op'=>'push','scene'=>$scene,'data'=>$msgData, 'message' => '消息发送失败'];
  237. $this->sendMsg($frameId, $data);
  238. return false;
  239. }
  240. // 推送消息给对方
  241. $msgData['from_user_name'] = $fromUserName;
  242. $msgData['from_user_avatar'] = get_image_url($fromAvatar, $apiUrl);
  243. $msgData['to_user_name'] = $toUserName;
  244. $msgData['to_user_avatar'] = get_image_url($toAvatar, $apiUrl);
  245. $msgData['time_text'] = dateFormat($msgData['create_time']);
  246. if($msgData['msg_type'] == 1 || $msgData['msg_type'] == 3){
  247. $msgData['content'] = format_message($msgData['content']);
  248. }else if($msgData['msg_type'] == 2){
  249. $msgData['content'] = $msgData['content']? get_image_url($msgData['content'],$apiUrl) : '';
  250. }else if($msgData['msg_type'] == 4){
  251. $msgData['content'] = $msgData['content']? json_decode($msgData['content'], true) : [];
  252. }
  253. // 接收方缓存
  254. RedisService::clear("caches:messages:unread_count_{$toUid}");
  255. // 返回自身消息
  256. $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
  257. // 推送给对方消息
  258. $pushCustom = ConfigService::make()->getConfigByCode('push_custom_message',1);
  259. $toBindData = RedisService::get("chats:bind:{$scene}_{$toUid}");
  260. $toFd = isset($toBindData['fd']) ? $toBindData['fd'] : 0;
  261. if ($toBindData && $toFd) {
  262. $this->sendMsg($toFd, ['success' => true, 'op' => 'push' ,'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '推送消息成功:' . $toFd]);
  263. $this->info("【{$date}】Chat:客户端【{$frameId}-{$fromUid}】推送消息给【{$toFd}-{$toUid}。");
  264. }
  265. // 如果开启离线推送,或一直推送,根据关键词推送客服消息
  266. if((($pushCustom == 1 && !$toFd)||($pushCustom==2) && ($msgType == 1) )&& $fromUid >1){
  267. // 搜索内容
  268. $replyContent = ConfigService::make()->getContentByKw($message);
  269. $sendData = $msgData = [
  270. 'from_uid' => 1,
  271. 'to_uid' => $fromUid,
  272. 'type' => 1,
  273. 'msg_type' => 3,
  274. 'title' => '客服消息',
  275. 'description' => mb_substr($message, 0, 20),
  276. 'content' => $replyContent,
  277. 'chat_key' => $chatKey,
  278. 'create_time' => time(),
  279. 'update_time' => time(),
  280. 'is_read' => 1,
  281. 'status' => 1
  282. ];
  283. $sendData['from_user_name'] = $toUserName;
  284. $sendData['from_user_avatar'] = $toAvatar;
  285. $sendData['to_user_name'] = $fromUserName;
  286. $sendData['to_user_avatar'] = $fromAvatar;
  287. $sendData['content'] = format_message($replyContent);
  288. $sendData['time_text'] = dateFormat($msgData['create_time']);
  289. if($replyContent){
  290. if (MessageModel::insertGetId($msgData)) {
  291. $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene, 'data' => $sendData, 'message' => '回复成功:' . $frameId]);
  292. }
  293. }else{
  294. $replyContent = ConfigService::make()->getConfigByCode('custom_offline_reply','');
  295. if($replyContent){
  296. $sendData['content'] = format_message($replyContent);
  297. $this->sendMsg($frameId, ['success' => true, 'op' => 'push', 'scene'=> $scene, 'data' => $sendData, 'message' => '回复成功:' . $frameId]);
  298. }
  299. }
  300. }
  301. break;
  302. case 'refund_apply': // 保证金退款申请消息通知
  303. $orderId = isset($data['order_id'])? $data['order_id'] : 0;
  304. // 发送参数验证
  305. if ($fromUid <= 0 || empty($message) || empty($orderId)) {
  306. $this->info("【{$scene} {$date}】Message:参数错误,from@{$fromUid}-to@{$toUid}。");
  307. $this->sendMsg($frameId, ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$data, 'message' => '参数错误']);
  308. return false;
  309. }
  310. // 订单信息
  311. $orderInfo = DepositService::make()->getInfo($orderId);
  312. $orderNo = isset($orderInfo['refund_no'])? $orderInfo['refund_no'] : '';
  313. $orderMoney = isset($orderInfo['refund_money'])? $orderInfo['refund_money'] : 0;
  314. if(empty($orderInfo) || $orderMoney<=0){
  315. $this->info("【{$scene} {$date}】Message:订单信息不存在,from@{$fromUid}-to@{$toUid}-{$orderId}。");
  316. $this->sendMsg($frameId, ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$data, 'message' => '订单信息不存在']);
  317. return false;
  318. }
  319. $msgData = [
  320. 'from_uid' => $fromUid,
  321. 'to_uid' => $toUid,
  322. 'type' => 4,
  323. 'msg_type' => 4,
  324. 'title' => '保证金退款申请消息',
  325. 'description' => $message,
  326. 'order_no' => $orderNo,
  327. 'content' => json_encode([
  328. 'title'=> $message.'<span class="ele-text-primary">查看订单</span>',
  329. 'order_no'=> $orderNo,
  330. 'money'=> $orderMoney,
  331. 'date'=> date('Y-m-d H:i:s'),
  332. 'user_id'=> $fromUid,
  333. 'type'=> 'deposit',
  334. 'remark'=> '退保申请',
  335. ],256),
  336. 'chat_key' => $chatKey,
  337. 'create_time' => time(),
  338. 'update_time' => time(),
  339. 'is_read' => 2,
  340. 'status' => 1
  341. ];
  342. if (!$id = MessageModel::insertGetId($msgData)) {
  343. $data = ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$msgData, 'message' => '消息发送失败'];
  344. $this->sendMsg($frameId, $data);
  345. return false;
  346. }
  347. // 推送消息给对方
  348. $msgData['time_text'] = dateFormat($msgData['create_time']);
  349. $msgData['content'] = $msgData['content']? json_decode($msgData['content'], true) : [];
  350. // 返回自身消息
  351. $this->sendMsg($frameId, ['success' => true, 'op' => 'notice', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
  352. // 推送给对方消息
  353. $toBindData = RedisService::get("chats:bind:{$scene}_{$toUid}");
  354. $toFd = isset($toBindData['fd']) ? $toBindData['fd'] : 0;
  355. if ($toBindData && $toFd) {
  356. $this->sendMsg($toFd, ['success' => true, 'op' => 'notice' ,'scene'=> $scene,'custom_fd'=>$customFd, 'data' => ['count'=>1,'list'=>[$msgData]], 'message' => '推送消息成功:' . $toFd]);
  357. $this->info("【{$date}】Message:客户端【{$frameId}-{$fromUid}】推送消息给【{$toFd}-{$toUid}。");
  358. }
  359. break;
  360. case 'notice': // 其他消息通知
  361. $type = isset($data['type']) ? $data['type'] : 5;
  362. $order = isset($data['order']) ? $data['order'] : [];
  363. // 发送参数验证
  364. if ($fromUid <= 0 || empty($message) || empty($order)) {
  365. $this->info("【{$scene} {$date}】Message:参数错误,from@{$fromUid}-to@{$toUid}。");
  366. $this->sendMsg($frameId, ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$data, 'message' => '参数错误']);
  367. return false;
  368. }
  369. $msgData = [
  370. 'from_uid' => $fromUid,
  371. 'to_uid' => $toUid,
  372. 'type' => $type,
  373. 'msg_type' => 4,
  374. 'title' => $message,
  375. 'order_no' => isset($order['order_no'])?$order['order_no'] : '',
  376. 'description' => isset($order['title'])? $order['title'] : '有新的订单消息',
  377. 'content' => json_encode($order,256),
  378. 'chat_key' => $chatKey,
  379. 'create_time' => time(),
  380. 'update_time' => time(),
  381. 'is_read' => 2,
  382. 'status' => 1
  383. ];
  384. if (!$id = MessageModel::insertGetId($msgData)) {
  385. $data = ['success' => false,'op'=>'notice','scene'=>$scene,'data'=>$msgData, 'message' => '消息发送失败'];
  386. $this->sendMsg($frameId, $data);
  387. return false;
  388. }
  389. // 推送消息给对方
  390. $msgData['time_text'] = dateFormat($msgData['create_time']);
  391. $msgData['content'] = $msgData['content']? json_decode($msgData['content'], true) : [];
  392. // 返回自身消息
  393. $this->sendMsg($frameId, ['success' => true, 'op' => 'notice', 'scene'=> $scene,'custom_fd'=>$customFd, 'data' => $msgData, 'message' => '发送成功:' . $frameId]);
  394. // 推送给对方消息
  395. $toBindData = RedisService::get("chats:bind:{$scene}_{$toUid}");
  396. $toFd = isset($toBindData['fd']) ? $toBindData['fd'] : 0;
  397. echo $toFd;
  398. if ($toBindData && $toFd) {
  399. $this->sendMsg($toFd, ['success' => true, 'op' => 'notice' ,'scene'=> $scene,'custom_fd'=>$customFd, 'data' => ['count'=>1,'list'=>[$msgData]], 'message' => '推送消息成功:' . $toFd]);
  400. $this->info("【{$date}】Message:客户端【{$frameId}-{$fromUid}】推送消息给【{$toFd}-{$toUid}。");
  401. }
  402. break;
  403. case 'login': // 登录
  404. if($toUid<=0){
  405. $toUid = 1;
  406. $data['to_uid'] = $toUid;
  407. $data['is_custom'] = true;
  408. $data['to_user_name'] = '客服';
  409. }
  410. // 未读消息
  411. $unreadCount = 0;
  412. if($fromUid==1){
  413. $unreadCount = intval(\App\Services\Api\MessageService::make()->getUnreadCount(1));
  414. }
  415. $fdData = RedisService::get("chats:bind:chat_".$toUid);
  416. $toFd = $fdData && isset($fdData['fd'])? $fdData['fd'] : 0;
  417. $checkFd = RedisService::get("chats:frames:" . $toFd);
  418. $online = $checkFd && $toFd? 1 : 0;
  419. $data['to_fd'] = $online? $toFd : 0;
  420. $data['chat_key'] = getChatKey($fromUid,$toUid);
  421. $this->info("【{$scene} {$date}】Socket:登录成功【{$frameId}-{$fromUid}-{$op}】。");
  422. $this->sendMsg($frameId, ['success' => true,'op'=> $op, 'scene'=>$scene,'custom_fd'=>$customFd,'unread'=>$unreadCount, 'message' => '登录成功', 'data' => $data, 't' => time()]);
  423. break;
  424. default:
  425. $this->sendMsg($frameId, ['success' => false, 'message' => 'ok', 'scene'=>$scene,'custom_fd'=>$customFd, 'data' => $data, 't' => time()]);
  426. break;
  427. }
  428. $this->info("【{$scene} {$date}】Chat:客户端【{$frameId}】消息处理成功");
  429. } catch (\Exception $exception) {
  430. RedisService::set("caches:sockets:error_{$frameId}", ['error' => $exception->getMessage(),'trace'=>$exception->getTrace(), 'date' => $date], 7200);
  431. $this->info("【{$scene} {$date}】Chat:客户端【{$frameId}】消息处理错误 " . $exception->getMessage());
  432. }
  433. }
  434. /**
  435. * 签名验证
  436. * @param $data
  437. * @return bool
  438. */
  439. public function checkSign($data)
  440. {
  441. $checkSign = isset($data['sign']) ? $data['sign'] : '';
  442. $sign = getSign($data);
  443. if ($sign != $checkSign) {
  444. return false;
  445. }
  446. return true;
  447. }
  448. /**
  449. * 推送消息
  450. * @param $fd
  451. * @param $op
  452. * @param $data
  453. */
  454. public function sendMsg($fd, $data)
  455. {
  456. $date = date('Y-m-d H:i:s');
  457. try {
  458. if (!RedisService::exists("chats:frames:" . $fd)) {
  459. $this->info("【{$date}】Chat:客户端【{$fd}】推送用户已经掉线 ");
  460. return false;
  461. }
  462. $this->ws->push($fd, json_encode($data, 256));
  463. } catch (\Exception $exception) {
  464. $this->info("【{$date}】Chat:客户端【{$fd}】消息处理错误 " . $exception->getMessage());
  465. }
  466. }
  467. /**
  468. * 接收请求
  469. * @param $request
  470. * @param $response
  471. */
  472. public function request($request, $response)
  473. {
  474. }
  475. /**
  476. * 关闭连接
  477. * @param $ws
  478. * @param $fd
  479. */
  480. public function close($ws, $fd = '')
  481. {
  482. $date = date('Y-m-d H:i:s');
  483. RedisService::clear("chats:frames:" . $fd);
  484. $this->info("【{$date}】Chat:客户端【{$fd}】连接关闭");
  485. RedisService::clear('chats:checkCustom:from_'.$fd);
  486. $this->ws->close($fd);
  487. // 清理历史消息
  488. if(!RedisService::get("caches:messages:clear")){
  489. $chatLogCount = ConfigService::make()->getConfigByCode('chat_log_count',300);
  490. $expireTime = ConfigService::make()->getConfigByCode('chat_log_expire',30);
  491. $expireTime = $expireTime>0 && $expireTime <= 365? $expireTime : 30;
  492. $expireTime = $expireTime * 24 * 3600;
  493. if(MessageModel::count('id')>$chatLogCount){
  494. MessageModel::where('create_time','<', time() - $expireTime)->delete();
  495. }
  496. }
  497. }
  498. /**
  499. * 停止运行
  500. */
  501. public function stop()
  502. {
  503. // 直接杀
  504. $stoSh = base_path().'/crontab/socketStop.sh';
  505. if(file_exists($stoSh) && function_exists('exec')){
  506. exec("{$stoSh}");
  507. }
  508. echo "$stoSh\n";
  509. echo "chat stop success...\n";
  510. if ($this->ws) {
  511. $date = date('Y-m-d H:i:s');
  512. $this->info("【{$date}】Chat:停止运行服务");
  513. $this->ws->close();
  514. }
  515. }
  516. /**
  517. * 消息
  518. * @param string $data
  519. */
  520. public function info($data,$verbosity=true)
  521. {
  522. \logger()->channel('chat')->info($data);
  523. if(env('SWOOLE_LOG', true) && $verbosity){
  524. parent::info($data);
  525. }
  526. }
  527. }