ChatHandle.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | Chat 消息处理类(参照熙思研模式:在 WS handler 内直接保存聊天记录)
  4. // +----------------------------------------------------------------------
  5. namespace app\services\workerman\chat;
  6. use app\model\api\ChatRecord;
  7. use app\model\api\ChatUserRelation;
  8. use app\model\api\User;
  9. use app\services\workerman\Response;
  10. use Workerman\Connection\TcpConnection;
  11. class ChatHandle
  12. {
  13. protected $service;
  14. public function __construct(ChatService &$service)
  15. {
  16. $this->service = &$service;
  17. }
  18. /**
  19. * 客服登录
  20. */
  21. public function kefu_login(TcpConnection &$connection, array $res, Response $response)
  22. {
  23. if (!isset($res['data']) || !$token = $res['data']) {
  24. return $response->close(['msg' => '授权失败!']);
  25. }
  26. // TODO: 根据你的系统实现客服token验证
  27. $kefuInfo = [
  28. 'uid' => $res['data']['uid'] ?? 1,
  29. 'name' => $res['data']['name'] ?? '客服',
  30. ];
  31. $connection->kefuUser = (object)$kefuInfo;
  32. $connection->user = (object)['uid' => $kefuInfo['uid'], 'nickname' => $kefuInfo['name'] ?? '客服'];
  33. $this->service->setKefuUser($connection);
  34. return $response->success();
  35. }
  36. /**
  37. * 用户登录
  38. */
  39. public function login(TcpConnection &$connection, array $res, Response $response)
  40. {
  41. if (!isset($res['data']) || !$token = $res['data']) {
  42. return $response->close(['msg' => '授权失败!']);
  43. }
  44. // 通过 token 查询用户
  45. $userInfo = User::where('token', $token)->find();
  46. if (!$userInfo) {
  47. return $response->close(['msg' => '用户不存在']);
  48. }
  49. $connection->user = (object)$userInfo->toArray();
  50. $connection->chatToUid = $connection->to_uid ?? 0;
  51. $this->service->setUser($connection);
  52. return $response->success([
  53. 'msg' => '登录成功',
  54. 'to_uid' => $connection->chatToUid,
  55. ]);
  56. }
  57. /**
  58. * 用户切换到与某人聊天(参照熙思研 to_chat)
  59. * 前端打开某用户聊天窗口时调用,用于标记消息已读、重置未读数
  60. */
  61. public function to_chat(TcpConnection &$connection, array $res, Response $response)
  62. {
  63. $to_uid = $res['data']['id'] ?? $res['data']['to_uid'] ?? 0;
  64. $connection->chatToUid = $to_uid;
  65. if (isset($connection->user) && $connection->chatToUid) {
  66. $uid = $connection->user->uid;
  67. // 标记对方发给我的消息为已读
  68. ChatRecord::markAsRead($uid, $connection->chatToUid);
  69. $response->send('mssage_num', [
  70. 'uid' => $connection->chatToUid,
  71. 'num' => 0,
  72. 'recored' => (object)[],
  73. ]);
  74. }
  75. }
  76. /**
  77. * 发送消息(参照熙思研模式:在 WS handler 内直接保存聊天记录到数据库)
  78. */
  79. public function chat(TcpConnection &$connection, array $res, Response $response)
  80. {
  81. if (!isset($connection->user) || !$connection->user) {
  82. return $response->send('err_tip', ['msg' => '请先登录']);
  83. }
  84. $to_uid = $res['data']['to_uid'] ?? $connection->chatToUid ?? 0;
  85. $msn_type = $res['data']['type'] ?? ChatRecord::TYPE_TEXT;
  86. $msn = $res['data']['msn'] ?? '';
  87. $form_type = $res['form_type'] ?? $connection->form_type ?? ChatRecord::FROM_PC;
  88. $uid = $connection->user->uid ?? 0;
  89. if (!$to_uid) {
  90. return $response->send('err_tip', ['msg' => '用户不存在']);
  91. }
  92. if ($to_uid == $uid) {
  93. return $response->send('err_tip', ['msg' => '不能和自己聊天']);
  94. }
  95. if (!in_array($msn_type, [ChatRecord::TYPE_TEXT, ChatRecord::TYPE_VOICE, ChatRecord::TYPE_IMAGE])) {
  96. return $response->send('err_tip', ['msg' => '格式错误']);
  97. }
  98. // 清洗消息内容
  99. $msn = trim(strip_tags(str_replace(["\n", "\t", "\r", "&nbsp;"], '', htmlspecialchars_decode($msn))));
  100. // 判断对方是否在线且正在与当前用户聊天
  101. $connections = $this->service->user();
  102. $online = isset($connections[$to_uid]) && isset($connections[$to_uid]->chatToUid) && $connections[$to_uid]->chatToUid == $uid;
  103. // ========== 关键:在 WS handler 内直接保存聊天记录 ==========
  104. $record = ChatRecord::saveRecord($uid, $to_uid, $msn, $msn_type, $form_type);
  105. // 如果对方在线且正在聊天,直接标记已读
  106. if ($online) {
  107. ChatRecord::where('id', $record->id)->update(['is_read' => ChatRecord::READ]);
  108. $record->is_read = ChatRecord::READ;
  109. }
  110. // 更新会话关系
  111. $relation = ChatUserRelation::getOrCreate($uid, $to_uid);
  112. if ($relation) {
  113. $relation->update_time = time();
  114. $relation->save();
  115. }
  116. // 获取发送者用户信息
  117. $user = User::find($uid);
  118. $nickname = $user->nickname ?? $connection->user->nickname ?? '用户';
  119. $avatar = $user->avatar ?? $connection->user->avatar ?? '';
  120. // 未读消息计数
  121. $unreadCount = ChatRecord::getUnreadCount($to_uid, $uid);
  122. // 构造返回数据
  123. $data = [
  124. 'record_id' => $record->id,
  125. 'user_id' => $uid,
  126. 'to_user_id' => $to_uid,
  127. 'content' => $msn,
  128. 'type' => $msn_type,
  129. 'type_text' => ChatRecord::getTypeText($msn_type),
  130. 'form_type' => $form_type,
  131. 'is_read' => $online ? ChatRecord::READ : ChatRecord::UNREAD,
  132. 'create_time' => $record->create_time,
  133. 'nickname' => $nickname,
  134. 'avatar' => $avatar,
  135. ];
  136. // 给自己回复
  137. $response->send('chat', $data);
  138. // 发送给对方
  139. if ($online) {
  140. // 对方在线且正在聊天:直接推送消息
  141. $replyData = $data;
  142. $replyData['is_read'] = ChatRecord::READ;
  143. $response->connection($this->service->user()[$to_uid])->send('reply', $replyData);
  144. } elseif (isset($connections[$to_uid])) {
  145. // 对方在线但没在聊天:推送未读消息提醒
  146. $response->connection($this->service->user()[$to_uid])->send('mssage_num', [
  147. 'uid' => $uid,
  148. 'num' => $unreadCount,
  149. 'recored' => [
  150. 'nickname' => $nickname,
  151. 'avatar' => $avatar,
  152. ],
  153. ]);
  154. }
  155. // 对方不在线:什么都不做,等对方上线后通过 HTTP API 拉取
  156. }
  157. }