|
@@ -1,9 +1,12 @@
|
|
|
<?php
|
|
<?php
|
|
|
// +----------------------------------------------------------------------
|
|
// +----------------------------------------------------------------------
|
|
|
-// | Chat 消息处理类
|
|
|
|
|
|
|
+// | Chat 消息处理类(参照熙思研模式:在 WS handler 内直接保存聊天记录)
|
|
|
// +----------------------------------------------------------------------
|
|
// +----------------------------------------------------------------------
|
|
|
namespace app\services\workerman\chat;
|
|
namespace app\services\workerman\chat;
|
|
|
|
|
|
|
|
|
|
+use app\model\api\ChatRecord;
|
|
|
|
|
+use app\model\api\ChatUserRelation;
|
|
|
|
|
+use app\model\api\User;
|
|
|
use app\services\workerman\Response;
|
|
use app\services\workerman\Response;
|
|
|
use Workerman\Connection\TcpConnection;
|
|
use Workerman\Connection\TcpConnection;
|
|
|
|
|
|
|
@@ -26,9 +29,6 @@ class ChatHandle
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TODO: 根据你的系统实现客服token验证
|
|
// TODO: 根据你的系统实现客服token验证
|
|
|
- // 示例:$kefuInfo = your_kefu_auth_service::parseToken($token);
|
|
|
|
|
-
|
|
|
|
|
- // 临时示例
|
|
|
|
|
$kefuInfo = [
|
|
$kefuInfo = [
|
|
|
'uid' => $res['data']['uid'] ?? 1,
|
|
'uid' => $res['data']['uid'] ?? 1,
|
|
|
'name' => $res['data']['name'] ?? '客服',
|
|
'name' => $res['data']['name'] ?? '客服',
|
|
@@ -50,70 +50,134 @@ class ChatHandle
|
|
|
return $response->close(['msg' => '授权失败!']);
|
|
return $response->close(['msg' => '授权失败!']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // TODO: 根据你的系统实现用户token验证
|
|
|
|
|
- // 示例:$authInfo = your_user_auth_service::parseToken($token);
|
|
|
|
|
-
|
|
|
|
|
- // 临时示例
|
|
|
|
|
- $userInfo = [
|
|
|
|
|
- 'uid' => $res['data']['uid'] ?? 1,
|
|
|
|
|
- 'nickname' => $res['data']['nickname'] ?? '用户',
|
|
|
|
|
- 'avatar' => $res['data']['avatar'] ?? '',
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ // 通过 token 查询用户
|
|
|
|
|
+ $userInfo = User::where('token', $token)->find();
|
|
|
|
|
+ if (!$userInfo) {
|
|
|
|
|
+ return $response->close(['msg' => '用户不存在']);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $connection->user = (object)$userInfo;
|
|
|
|
|
-
|
|
|
|
|
- // 从 URL 参数获取聊天对象 UID(在 socket.php onWebSocketConnect 中设置)
|
|
|
|
|
|
|
+ $connection->user = (object)$userInfo->toArray();
|
|
|
$connection->chatToUid = $connection->to_uid ?? 0;
|
|
$connection->chatToUid = $connection->to_uid ?? 0;
|
|
|
-
|
|
|
|
|
$this->service->setUser($connection);
|
|
$this->service->setUser($connection);
|
|
|
|
|
|
|
|
return $response->success([
|
|
return $response->success([
|
|
|
'msg' => '登录成功',
|
|
'msg' => '登录成功',
|
|
|
- 'to_uid' => $connection->chatToUid, // 返回当前聊天对象
|
|
|
|
|
|
|
+ 'to_uid' => $connection->chatToUid,
|
|
|
]);
|
|
]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 发送消息
|
|
|
|
|
|
|
+ * 用户切换到与某人聊天(参照熙思研 to_chat)
|
|
|
|
|
+ * 前端打开某用户聊天窗口时调用,用于标记消息已读、重置未读数
|
|
|
|
|
+ */
|
|
|
|
|
+ public function to_chat(TcpConnection &$connection, array $res, Response $response)
|
|
|
|
|
+ {
|
|
|
|
|
+ $to_uid = $res['data']['id'] ?? $res['data']['to_uid'] ?? 0;
|
|
|
|
|
+ $connection->chatToUid = $to_uid;
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($connection->user) && $connection->chatToUid) {
|
|
|
|
|
+ $uid = $connection->user->uid;
|
|
|
|
|
+ // 标记对方发给我的消息为已读
|
|
|
|
|
+ ChatRecord::markAsRead($uid, $connection->chatToUid);
|
|
|
|
|
+ $response->send('mssage_num', [
|
|
|
|
|
+ 'uid' => $connection->chatToUid,
|
|
|
|
|
+ 'num' => 0,
|
|
|
|
|
+ 'recored' => (object)[],
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发送消息(参照熙思研模式:在 WS handler 内直接保存聊天记录到数据库)
|
|
|
*/
|
|
*/
|
|
|
public function chat(TcpConnection &$connection, array $res, Response $response)
|
|
public function chat(TcpConnection &$connection, array $res, Response $response)
|
|
|
{
|
|
{
|
|
|
- // 检查用户是否已登录
|
|
|
|
|
if (!isset($connection->user) || !$connection->user) {
|
|
if (!isset($connection->user) || !$connection->user) {
|
|
|
return $response->send('err_tip', ['msg' => '请先登录']);
|
|
return $response->send('err_tip', ['msg' => '请先登录']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 优先从消息数据获取 to_uid,其次从 URL 参数获取(聊天对象UID)
|
|
|
|
|
$to_uid = $res['data']['to_uid'] ?? $connection->chatToUid ?? 0;
|
|
$to_uid = $res['data']['to_uid'] ?? $connection->chatToUid ?? 0;
|
|
|
|
|
+ $msn_type = $res['data']['type'] ?? ChatRecord::TYPE_TEXT;
|
|
|
$msn = $res['data']['msn'] ?? '';
|
|
$msn = $res['data']['msn'] ?? '';
|
|
|
- $msn_type = $res['data']['type'] ?? 1; // 1=文字, 2=语音, 3=图片
|
|
|
|
|
|
|
+ $form_type = $res['form_type'] ?? $connection->form_type ?? ChatRecord::FROM_PC;
|
|
|
|
|
+ $uid = $connection->user->uid ?? 0;
|
|
|
|
|
|
|
|
if (!$to_uid) {
|
|
if (!$to_uid) {
|
|
|
return $response->send('err_tip', ['msg' => '用户不存在']);
|
|
return $response->send('err_tip', ['msg' => '用户不存在']);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if ($to_uid == ($connection->user->uid ?? 0)) {
|
|
|
|
|
|
|
+ if ($to_uid == $uid) {
|
|
|
return $response->send('err_tip', ['msg' => '不能和自己聊天']);
|
|
return $response->send('err_tip', ['msg' => '不能和自己聊天']);
|
|
|
}
|
|
}
|
|
|
|
|
+ if (!in_array($msn_type, [ChatRecord::TYPE_TEXT, ChatRecord::TYPE_VOICE, ChatRecord::TYPE_IMAGE])) {
|
|
|
|
|
+ return $response->send('err_tip', ['msg' => '格式错误']);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $uid = $connection->user->uid ?? 0;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 清洗消息内容
|
|
|
|
|
+ $msn = trim(strip_tags(str_replace(["\n", "\t", "\r", " "], '', htmlspecialchars_decode($msn))));
|
|
|
|
|
+
|
|
|
|
|
+ // 判断对方是否在线且正在与当前用户聊天
|
|
|
|
|
+ $connections = $this->service->user();
|
|
|
|
|
+ $online = isset($connections[$to_uid]) && isset($connections[$to_uid]->chatToUid) && $connections[$to_uid]->chatToUid == $uid;
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 关键:在 WS handler 内直接保存聊天记录 ==========
|
|
|
|
|
+ $record = ChatRecord::saveRecord($uid, $to_uid, $msn, $msn_type, $form_type);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果对方在线且正在聊天,直接标记已读
|
|
|
|
|
+ if ($online) {
|
|
|
|
|
+ ChatRecord::where('id', $record->id)->update(['is_read' => ChatRecord::READ]);
|
|
|
|
|
+ $record->is_read = ChatRecord::READ;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新会话关系
|
|
|
|
|
+ $relation = ChatUserRelation::getOrCreate($uid, $to_uid);
|
|
|
|
|
+ if ($relation) {
|
|
|
|
|
+ $relation->update_time = time();
|
|
|
|
|
+ $relation->save();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取发送者用户信息
|
|
|
|
|
+ $user = User::find($uid);
|
|
|
|
|
+ $nickname = $user->nickname ?? $connection->user->nickname ?? '用户';
|
|
|
|
|
+ $avatar = $user->avatar ?? $connection->user->avatar ?? '';
|
|
|
|
|
+
|
|
|
|
|
+ // 未读消息计数
|
|
|
|
|
+ $unreadCount = ChatRecord::getUnreadCount($to_uid, $uid);
|
|
|
|
|
+
|
|
|
|
|
+ // 构造返回数据
|
|
|
$data = [
|
|
$data = [
|
|
|
- 'from_uid' => $uid,
|
|
|
|
|
- 'to_uid' => $to_uid,
|
|
|
|
|
- 'msn' => $msn,
|
|
|
|
|
- 'type' => $msn_type,
|
|
|
|
|
- 'add_time' => time(),
|
|
|
|
|
- 'nickname' => $connection->user->nickname ?? '用户',
|
|
|
|
|
- 'avatar' => $connection->user->avatar ?? '',
|
|
|
|
|
|
|
+ 'record_id' => $record->id,
|
|
|
|
|
+ 'user_id' => $uid,
|
|
|
|
|
+ 'to_user_id' => $to_uid,
|
|
|
|
|
+ 'content' => $msn,
|
|
|
|
|
+ 'type' => $msn_type,
|
|
|
|
|
+ 'type_text' => ChatRecord::getTypeText($msn_type),
|
|
|
|
|
+ 'form_type' => $form_type,
|
|
|
|
|
+ 'is_read' => $online ? ChatRecord::READ : ChatRecord::UNREAD,
|
|
|
|
|
+ 'create_time' => $record->create_time,
|
|
|
|
|
+ 'nickname' => $nickname,
|
|
|
|
|
+ 'avatar' => $avatar,
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
// 给自己回复
|
|
// 给自己回复
|
|
|
$response->send('chat', $data);
|
|
$response->send('chat', $data);
|
|
|
|
|
|
|
|
- // 发送给目标用户
|
|
|
|
|
- if (isset($this->service->user()[$to_uid])) {
|
|
|
|
|
- $response->connection($this->service->user()[$to_uid])->send('reply', $data);
|
|
|
|
|
|
|
+ // 发送给对方
|
|
|
|
|
+ if ($online) {
|
|
|
|
|
+ // 对方在线且正在聊天:直接推送消息
|
|
|
|
|
+ $replyData = $data;
|
|
|
|
|
+ $replyData['is_read'] = ChatRecord::READ;
|
|
|
|
|
+ $response->connection($this->service->user()[$to_uid])->send('reply', $replyData);
|
|
|
|
|
+ } elseif (isset($connections[$to_uid])) {
|
|
|
|
|
+ // 对方在线但没在聊天:推送未读消息提醒
|
|
|
|
|
+ $response->connection($this->service->user()[$to_uid])->send('mssage_num', [
|
|
|
|
|
+ 'uid' => $uid,
|
|
|
|
|
+ 'num' => $unreadCount,
|
|
|
|
|
+ 'recored' => [
|
|
|
|
|
+ 'nickname' => $nickname,
|
|
|
|
|
+ 'avatar' => $avatar,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]);
|
|
|
}
|
|
}
|
|
|
|
|
+ // 对方不在线:什么都不做,等对方上线后通过 HTTP API 拉取
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|