diff --git a/server/app/api/controller/ChatController.php b/server/app/api/controller/ChatController.php new file mode 100644 index 0000000..5e9d435 --- /dev/null +++ b/server/app/api/controller/ChatController.php @@ -0,0 +1,238 @@ +request->param('id/d', 0); + + if (!$techId) { + return json([ + 'code' => 400, + 'msg' => '技师ID不能为空' + ]); + } + + try { + // 查询技师信息 + $tech = Coach::find($techId); + + if (!$tech) { + return json([ + 'code' => 404, + 'msg' => '技师不存在' + ]); + } + + // 格式化返回数据 + $result = [ + 'id' => $tech['id'], + 'name' => $tech['name'], + 'avatar' => $tech['work_photo'], + 'service_count' => $tech['service_count'], + 'rating' => $tech['rating'], + 'skills' => $tech['skills'], + 'description' => $tech['description'], + 'status' => $tech['status'], + 'create_time' => $tech['create_time'] + ]; + + return json([ + 'code' => 200, + 'msg' => '成功', + 'data' => $result + ]); + } catch (\Exception $e) { + return json([ + 'code' => 500, + 'msg' => '获取技师信息失败: ' . $e->getMessage() + ]); + } + } + + // 获取会话ID + public function conversation_id() + { + $techId = $this->request->param('tech_id/d', 0); + $userId = $this->userId; + + if (!$techId||!$userId) { + return json(['code' => 400, 'msg' => '用户ID和技师ID不能为空']); + } + + try { + // 查询会话 + $conversation = Db::name('chat_conversation') + ->where('user_id', $userId) + ->where('tech_id', $techId) + ->find(); + + if ($conversation) { + return json(['code' => 200, 'data' => ['conversation_id' => $conversation['id']]]); + } + + // 创建新会话 + $conversationId = Db::name('chat_conversation') + ->insertGetId([ + 'user_id' => $userId, + 'tech_id' => $techId, + 'unread_count' => 0, + 'update_time' => date('Y-m-d H:i:s') + ]); + + return json(['code' => 200, 'data' => ['conversation_id' => $conversationId]]); + } catch (\Exception $e) { + return json(['code' => 500, 'msg' => '获取会话ID失败: ' . $e->getMessage()]); + } + } + + + /** + * 获取聊天历史记录 + */ + public function history() + { + $params = $this->request->param(); + $conversationId = $params['conversation_id'] ?? 0; + $page = $params['page'] ?? 1; + $pageSize = $params['page_size'] ?? 20; + + $where = [ + 'conversation_id' => $conversationId + ]; + + $query = ChatMessage::where($where) + ->order('id', 'asc'); + + $list = $query->page($page, $pageSize) + ->select(); + + $total = $query->count(); + + foreach ($list as &$item) { + if ($item['sender_type']==1){//发送者类型是用户的话 则获取用户信息 + $item['user'] = User::find($item['sender_id']); + }else{ + $item['user'] = Coach::find($item['sender_id']); + } + + } + + return json([ + 'code' => 200, + 'data' => [ + 'list' => $list, + 'total' => $total, + 'has_more' => $total > $page * $pageSize + ] + ]); + } + + /** + * 发送消息 + */ + public function send() + { + $params = $this->request->param(); + + $techId = $params['tech_id'] ?? 0; + $orderId = $params['order_id'] ?? 0; + $content = $params['content'] ?? ''; + + if (empty($content)) { + return json(['code' => 400, 'msg' => '消息内容不能为空']); + } + // 生成会话ID + $conversation = ChatConversation::where(['tech_id'=>$techId,'user_id'=>$this->userId])->find(); + if (empty($conversation)) { + $cccc = new ChatConversation; + $conversationId = $cccc->insertGetId(['tech_id'=>$techId,'user_id'=>$this->userId]); + }else{ + $conversationId = $conversation['id']; + } + + // 创建消息 + $message = new ChatMessage(); + $message->conversation_id = $conversationId; + $message->sender_id = $this->userId; + $message->sender_type = ChatMessage::SENDER_USER; // 用户 + $message->receiver_id = $techId; + $message->receiver_type = ChatMessage::SENDER_TECH; // 技师 + $message->content = $content; + $message->message_type = ChatMessage::TYPE_TEXT; // 文本 + $message->read_status = ChatMessage::UNREAD; // 未读 + $message->order_id = $orderId; + $message->save(); + + // TODO: 这里应该通过WebSocket推送给技师 + + return json(['code' => 200, 'msg' => '发送成功', 'data' => $message]); + } + + /** + * 标记消息为已读 + */ + public function markAsRead() + { + $params = $this->request->param(); + + $conversationId = $params['conversation_id'] ?? ''; + $userId = $this->request->uid; + + if (empty($conversationId)) { + return json(['code' => 400, 'msg' => '会话ID不能为空']); + } + + // 标记消息为已读 + Db::name('chat_message') + ->where('conversation_id', $conversationId) + ->where('receiver_id', $userId) + ->where('read_status', ChatMessage::UNREAD) + ->update(['read_status' => ChatMessage::READ]); + + return json(['code' => 200, 'msg' => '已标记为已读']); + } + + /** + * 获取未读消息数量 + */ + public function unreadCount() + { + $unreadCount = ChatMessage::where([ + 'conversation_id' => $this->request->uid, + 'receiver_type' => ChatMessage::SENDER_USER, // 用户 + 'read_status' => ChatMessage::UNREAD + ])->count(); + + return json(['code' => 200, 'data' => ['count' => $unreadCount]]); + } + + /** + * 获取最后一条消息 + */ + public function lastMessage() + { + + // 生成会话ID + $conversationId = $this->request->param('conversation_id/d', 0); + + $message = ChatMessage::where([ + 'conversation_id' => $conversationId, + ]) + ->order('id', 'desc') + ->find(); + + return json(['code' => 200, 'data' => $message]); + } +} \ No newline at end of file diff --git a/server/app/coachapi/controller/ChatController.php b/server/app/coachapi/controller/ChatController.php new file mode 100644 index 0000000..a59ac96 --- /dev/null +++ b/server/app/coachapi/controller/ChatController.php @@ -0,0 +1,312 @@ +request->param('conversation_id/d', 0); + + if (!$conversationId) { + return json(['code' => 400, 'msg' => '会话ID不能为空']); + } + + try { + // 查询会话信息 + $conversation = ChatConversation::find($conversationId); + + if (!$conversation) { + return json(['code' => 404, 'msg' => '会话不存在']); + } + + // 查询用户信息 + $userInfo = User::field('id, nickname, avatar')->find($conversation['user_id']); + + // 查询技师信息 + $techInfo = Coach::field('id, name as nickname, work_photo as avatar')->find($conversation['tech_id']); + + // 查询最后一条消息 + $lastMessage = []; + if ($conversation['last_msg_id']) { + $lastMessage = ChatMessage::field('content, create_time')->find($conversation['last_msg_id']); + } + + return json([ + 'code' => 200, + 'data' => [ + 'id' => $conversation['id'], + 'user_info' => $userInfo ?: null, + 'tech_info' => $techInfo ?: null, + 'last_message' => $lastMessage ?: null, + 'unread_count' => $conversation['unread_count'], + 'update_time' => $conversation['update_time'] + ] + ]); + } catch (\Exception $e) { + return json(['code' => 500, 'msg' => '获取会话信息失败: ' . $e->getMessage()]); + } + } + + /** + * 获取聊天历史 + */ + public function history() + { + $conversationId = $this->request->param('conversation_id/d', 0); + $page = $this->request->param('page/d', 1); + $pageSize = $this->request->param('page_size/d', 20); + + if (!$conversationId) { + return json(['code' => 400, 'msg' => '会话ID不能为空']); + } + + try { + // 计算偏移量 + $offset = ($page - 1) * $pageSize; + + // 查询消息列表 + $messages = Db::name('chat_message') + ->where('conversation_id', $conversationId) + ->order('id', 'asc') + ->limit($offset, 999999) + ->select() + ->toArray(); + + // 查询总数 + $total = Db::name('chat_message') + ->where('conversation_id', $conversationId) + ->count(); + + return json([ + 'code' => 200, + 'data' => [ + 'list' => $messages, + 'total' => $total, + 'has_more' => ($offset + count($messages)) < $total + ] + ]); + } catch (\Exception $e) { + return json(['code' => 500, 'msg' => '获取聊天历史失败: ' . $e->getMessage()]); + } + } + + /** + * 标记消息为已读 + */ + public function mark_as_read() + { + $conversationId = $this->request->param('conversation_id/d', 0); + $userId = $this->request->param('user_id/d', 0); + + if (!$conversationId || !$userId) { + return json(['code' => 400, 'msg' => '参数不完整']); + } + + try { + // 开始事务 + Db::startTrans(); + + // 1. 标记消息为已读 + Db::name('chat_message') + ->where('conversation_id', $conversationId) + ->where('receiver_id', $userId) + ->where('read_status', 0) + ->update([ + 'read_status' => 1, + 'update_time' => date('Y-m-d H:i:s') + ]); + + // 2. 重置会话未读数 + Db::name('chat_conversation') + ->where('id', $conversationId) + ->update([ + 'unread_count' => 0, + 'update_time' => date('Y-m-d H:i:s') + ]); + + // 提交事务 + Db::commit(); + + return json(['code' => 200, 'msg' => '标记成功']); + } catch (\Exception $e) { + // 回滚事务 + Db::rollback(); + return json(['code' => 500, 'msg' => '标记失败: ' . $e->getMessage()]); + } + } + + /** + * 获取会话列表 + * @return \think\Response + */ + public function conversations(Request $request) + { + try { + // 获取当前技师ID (从token或session中获取) + $techId = $this->getCurrentTechId(); + + // 获取请求参数 + $page = $request->param('page/d', 1); + $pageSize = $request->param('page_size/d', 10); + + // 查询会话列表 + $conversations = ChatConversation::where('tech_id', $techId) + ->with(['user', 'lastMessage']) + ->order('update_time', 'desc') + ->page($page, $pageSize) + ->select(); + + // 格式化返回数据 + $data = []; + foreach ($conversations as $convo) { + $data[] = [ + 'id' => $convo->id, + 'user_id' => $convo->user_id, + 'user_name' => $convo->user->nickname ?? '', + 'user_avatar' => $convo->user->avatar ?? '', + 'last_message' => $convo->lastMessage->content ?? '', + 'last_message_time' => $convo->lastMessage->create_time ?? '', + 'unread_count' => $convo->unread_count, + 'update_time' => $convo->update_time + ]; + } + + return json([ + 'code' => 200, + 'msg' => '成功', + 'data' => $data + ]); + } catch (\Exception $e) { + return json([ + 'code' => 500, + 'msg' => '获取会话列表失败: ' . $e->getMessage() + ]); + } + } + + /** + * 获取当前技师ID + * @return int + * @throws \Exception + */ + private function getCurrentTechId() + { + + return $this->coachInfo['coach_id']; + } + + + + /** + * 发送消息 + */ + public function send() + { + $params = $this->request->param(); + + $techId = $params['tech_id'] ?? 0; + $orderId = $params['order_id'] ?? 0; + $content = $params['content'] ?? ''; + + if (empty($content)) { + return json(['code' => 400, 'msg' => '消息内容不能为空']); + } + // 生成会话ID + $conversation = ChatConversation::where(['tech_id'=>$techId,'user_id'=>$this->userId])->find(); + if (empty($conversation)) { + $cccc = new ChatConversation; + $conversationId = $cccc->insertGetId(['tech_id'=>$techId,'user_id'=>$this->userId]); + }else{ + $conversationId = $conversation['id']; + } + + // 创建消息 + $message = new ChatMessage(); + $message->conversation_id = $conversationId; + $message->sender_id = $this->userId; + $message->sender_type = ChatMessage::SENDER_USER; // 用户 + $message->receiver_id = $techId; + $message->receiver_type = ChatMessage::SENDER_TECH; // 技师 + $message->content = $content; + $message->message_type = ChatMessage::TYPE_TEXT; // 文本 + $message->read_status = ChatMessage::UNREAD; // 未读 + $message->order_id = $orderId; + $message->save(); + + // TODO: 这里应该通过WebSocket推送给技师 + + return json(['code' => 200, 'msg' => '发送成功', 'data' => $message]); + } + + /** + * 标记消息为已读 + */ + public function markAsRead() + { + $params = $this->request->param(); + + $conversationId = $params['conversation_id'] ?? ''; + $userId = $this->request->uid; + + if (empty($conversationId)) { + return json(['code' => 400, 'msg' => '会话ID不能为空']); + } + + // 标记消息为已读 + Db::name('chat_message') + ->where('conversation_id', $conversationId) + ->where('receiver_id', $userId) + ->where('read_status', ChatMessage::UNREAD) + ->update(['read_status' => ChatMessage::READ]); + + return json(['code' => 200, 'msg' => '已标记为已读']); + } + + /** + * 获取未读消息数量 + */ + public function unreadCount() + { + $unreadCount = ChatMessage::where([ + 'conversation_id' => $this->request->uid, + 'receiver_type' => ChatMessage::SENDER_USER, // 用户 + 'read_status' => ChatMessage::UNREAD + ])->count(); + + return json(['code' => 200, 'data' => ['count' => $unreadCount]]); + } + + /** + * 获取最后一条消息 + */ + public function lastMessage() + { + $params = $this->request->param(); + + $techId = $params['tech_id'] ?? 0; + $orderId = $params['order_id'] ?? 0; + + // 生成会话ID + $conversationId = min($this->request->uid, $techId) . '_' . max($this->request->uid, $techId); + + $message = ChatMessage::where([ + 'conversation_id' => $conversationId, + 'order_id' => $orderId + ]) + ->order('id', 'desc') + ->find(); + + return json(['code' => 200, 'data' => $message]); + } +} \ No newline at end of file diff --git a/server/app/common/model/chat/ChatConversation.php b/server/app/common/model/chat/ChatConversation.php new file mode 100644 index 0000000..61e5028 --- /dev/null +++ b/server/app/common/model/chat/ChatConversation.php @@ -0,0 +1,31 @@ +belongsTo('app\common\model\user\User', 'user_id'); + } + + // 关联技师 + public function tech() + { + return $this->belongsTo('app\common\model\tech\Tech', 'tech_id'); + } + + // 关联最后一条消息 + public function lastMessage() + { + return $this->belongsTo('app\common\model\chat\ChatMessage', 'last_msg_id'); + } +} \ No newline at end of file diff --git a/server/app/common/model/chat/ChatMessage.php b/server/app/common/model/chat/ChatMessage.php new file mode 100644 index 0000000..f284081 --- /dev/null +++ b/server/app/common/model/chat/ChatMessage.php @@ -0,0 +1,89 @@ + '文本', + self::TYPE_IMAGE => '图片', + self::TYPE_VOICE => '语音', + ]; + return $status[$data['message_type']] ?? '未知'; + } + + /** + * 获取发送者类型文本 + * @param $value + * @param $data + * @return string + */ + public function getSenderTypeTextAttr($value, $data) + { + $status = [ + self::SENDER_USER => '用户', + self::SENDER_TECH => '技师', + ]; + return $status[$data['sender_type']] ?? '未知'; + } + + /** + * 获取读取状态文本 + * @param $value + * @param $data + * @return string + */ + public function getReadStatusTextAttr($value, $data) + { + $status = [ + self::UNREAD => '未读', + self::READ => '已读', + ]; + return $status[$data['read_status']] ?? '未知'; + } + + /** + * 关联会话 + */ + public function conversation() + { + return $this->belongsTo('app\common\model\chat\ChatConversation', 'conversation_id'); + } + + + /** + * 关联订单 + */ + public function order() + { + return $this->belongsTo('app\common\model\order\Order', 'order_id'); + } +} \ No newline at end of file diff --git a/server/app/socket/ChatServer.php b/server/app/socket/ChatServer.php new file mode 100644 index 0000000..f1b15a4 --- /dev/null +++ b/server/app/socket/ChatServer.php @@ -0,0 +1,387 @@ +getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); +}); + +// 创建 WebSocket 服务器 +$server = new Server("0.0.0.0", 9501); + +// 初始化数据库连接 +$db = createDbConnection(); + +// 创建连接表 +$table = new Table(1024); +$table->column('fd', Table::TYPE_INT); +$table->column('uid', Table::TYPE_INT); +$table->column('type', Table::TYPE_INT); // 1-用户 2-技师 +$table->create(); + +$server->table = $table; +$server->db = $db; // 将数据库连接附加到服务器对象 + +// 连接处理 +$server->on('open', function (Server $server, $request) { + $params = getQueryParams($request->server['query_string']); + + // 获取用户类型 + $userType = $params['type'] ?? 0; + + // 用户验证逻辑 + if (!$user = verifyToken($server->db, $params['token'] ?? '', $userType)) { + $server->close($request->fd); + return; + } + + // 确保类型参数存在 + if (!in_array($userType, [1, 2])) { + error_log("无效的用户类型: {$userType}"); + $server->close($request->fd); + return; + } + + // 存储连接信息 + $info = [ + 'fd' => $request->fd, + 'uid' => $user['id'], + 'type' => $userType + ]; + + $server->table->set($request->fd, $info); + + echo "客户端 {$request->fd} 已连接 (UID: {$user['id']}, 类型: {$userType})\n"; +}); + +// 消息处理 +$server->on('message', function (Server $server, $frame) { + $data = json_decode($frame->data, true); + + // 获取发送者信息 + $senderInfo = $server->table->get($frame->fd); + if (!$senderInfo) { + error_log("无法获取发送者信息: FD={$frame->fd}"); + return; + } + + switch ($data['action']) { + case 'send': + // 确保所有必要字段都存在 + if (!isset($data['conversation_id'], $data['content'])) { + error_log("消息缺少必要字段: " . json_encode($data)); + return; + } + + // 根据会话ID获取会话信息 + $conversation = getConversationById($server->db, $data['conversation_id']); + if (!$conversation) { + error_log("会话不存在: conversation_id={$data['conversation_id']}"); + return; + } + + // 确定接收者信息 + if ($senderInfo['type'] == 1) { // 发送者是用户 + $receiverId = $conversation['tech_id']; + $receiverType = 2; // 技师 + } else { // 发送者是技师 + $receiverId = $conversation['user_id']; + $receiverType = 1; // 用户 + } + + // 1. 存储到数据库 + $messageData = [ + 'sender_id' => $senderInfo['uid'], + 'sender_type' => $senderInfo['type'], + 'receiver_id' => $receiverId, + 'receiver_type' => $receiverType, + 'content' => $data['content'], + 'order_id' => $data['order_id'] ?? 0, + 'conversation_id' => $data['conversation_id'] + ]; + + $messageId = saveMessage($server->db, $messageData); + + // 2. 构建消息体 + $message = [ + 'action' => 'new', + 'data' => [ + 'id' => $messageId, + 'content' => $data['content'], + 'sender_type' => $senderInfo['type'], + 'create_time' => date('Y-m-d H:i:s'), + 'conversation_id' => $data['conversation_id'], + ] + ]; + + // 3. 找到接收者发送消息 + sendToUser($server, $receiverId, $receiverType, $message); + + // 4. 也发给自己(保证消息同步) + $server->push($frame->fd, json_encode($message)); + break; + + case 'read': + // 确保所有必要字段都存在 + if (!isset($data['conversation_id'], $data['user_id'])) { + error_log("标记已读缺少必要字段: " . json_encode($data)); + return; + } + + // 标记消息为已读 + markMessagesAsRead($server->db, $data['conversation_id'], $data['user_id']); + break; + } +}); + +// 关闭连接 +$server->on('close', function ($server, $fd) { + $server->table->del($fd); + echo "客户端 {$fd} 已断开连接\n"; +}); + +/** + * 解析查询字符串 + */ +function getQueryParams($queryString) { + $params = []; + parse_str($queryString, $params); + + // 过滤特殊字符 + foreach ($params as $key => $value) { + $params[$key] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); + } + + return $params; +} + +/** + * 创建数据库连接 + */ +function createDbConnection() { + $host = '127.0.0.1'; + $dbname = 'anmo'; + $username = 'anmo'; + $password = 'fmyrGXBYijbmSMbi'; + + try { + $db = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + return $db; + } catch (PDOException $e) { + die("数据库连接失败: " . $e->getMessage()); + } +} + +/** + * 验证 token + */ +function verifyToken(PDO $db, $token, $userType) { + try { + if (empty($token)) { + throw new Exception("Token不能为空"); + } + + // 根据用户类型选择不同的验证逻辑 + switch ($userType) { + case 1: // 普通用户 + $stmt = $db->prepare("SELECT * FROM ls_user_session WHERE token = :token"); + $stmt->bindParam(':token', $token); + $stmt->execute(); + $session = $stmt->fetch(); + + if (!$session) { + error_log("用户会话不存在: token={$token}"); + return false; + } + + // 检查会话是否过期 + if ($session['expire_time'] < time()) { + error_log("用户会话已过期: token={$token}"); + return false; + } + + $stmt = $db->prepare("SELECT * FROM ls_user WHERE id = :user_id"); + $stmt->bindParam(':user_id', $session['user_id']); + $stmt->execute(); + return $stmt->fetch(); + + case 2: // 技师 + $stmt = $db->prepare("SELECT * FROM ls_coach_user_session WHERE token = :token"); + $stmt->bindParam(':token', $token); + $stmt->execute(); + $session = $stmt->fetch(); + + if (!$session) { + error_log("技师会话不存在: token={$token}"); + return false; + } + + // 检查会话是否过期 + if ($session['expire_time'] < time()) { + error_log("技师会话已过期: token={$token}"); + return false; + } + + $stmt = $db->prepare(" + SELECT c.* + FROM ls_coach c + WHERE c.coach_user_id = :coach_user_id + "); + $stmt->bindParam(':coach_user_id', $session['coach_user_id']); + $stmt->execute(); + return $stmt->fetch(); + + default: + throw new Exception("无效的用户类型: {$userType}"); + } + } catch (Exception $e) { + error_log("Token验证错误: " . $e->getMessage()); + return false; + } +} + +/** + * 根据ID获取会话信息 + */ +function getConversationById(PDO $db, $conversationId) { + try { + $stmt = $db->prepare(" + SELECT * + FROM ls_chat_conversation + WHERE id = :conversation_id + "); + $stmt->bindParam(':conversation_id', $conversationId); + $stmt->execute(); + $conversation = $stmt->fetch(); + + if (!$conversation) { + error_log("会话不存在: conversation_id={$conversationId}"); + return false; + } + + return $conversation; + } catch (Exception $e) { + error_log("获取会话信息失败: " . $e->getMessage()); + return false; + } +} + +/** + * 保存消息到数据库 + */ +function saveMessage(PDO $db, $data) { + try { + // 确保所有必要字段都存在 + if (!isset($data['sender_id'], $data['sender_type'], $data['receiver_id'], $data['receiver_type'], $data['conversation_id'])) { + throw new Exception("缺少必要的消息字段"); + } + + $stmt = $db->prepare(" + INSERT INTO ls_chat_message + (conversation_id, sender_id, sender_type, receiver_id, receiver_type, content, message_type, read_status, order_id) + VALUES (:conversation_id, :sender_id, :sender_type, :receiver_id, :receiver_type, :content, :message_type, :read_status, :order_id) + "); + + $messageType = $data['message_type'] ?? 1; // 默认为文本消息 + $readStatus = 0; // 默认为未读 + $orderId = $data['order_id'] ?? 0; + + $stmt->bindParam(':conversation_id', $data['conversation_id']); + $stmt->bindParam(':sender_id', $data['sender_id']); + $stmt->bindParam(':sender_type', $data['sender_type']); + $stmt->bindParam(':receiver_id', $data['receiver_id']); + $stmt->bindParam(':receiver_type', $data['receiver_type']); + $stmt->bindParam(':content', $data['content']); + $stmt->bindParam(':message_type', $messageType); + $stmt->bindParam(':read_status', $readStatus); + $stmt->bindParam(':order_id', $orderId); + + $stmt->execute(); + + return $db->lastInsertId(); + } catch (Exception $e) { + error_log("保存消息失败: " . $e->getMessage()); + return 0; + } +} + +/** + * 标记消息为已读 + */ +function markMessagesAsRead(PDO $db, $conversationId, $userId) { + try { + // 确保参数有效 + if (empty($conversationId) || empty($userId)) { + throw new Exception("无效的会话ID或用户ID"); + } + + // 开始事务 + $db->beginTransaction(); + + // 1. 标记消息为已读 + $stmt = $db->prepare(" + UPDATE ls_chat_message + SET read_status = 1 + WHERE conversation_id = :conversation_id + AND receiver_id = :receiver_id + "); + + $stmt->bindParam(':conversation_id', $conversationId); + $stmt->bindParam(':receiver_id', $userId); + $stmt->execute(); + + // 2. 重置会话未读数 + $stmt = $db->prepare(" + UPDATE ls_chat_conversation + SET unread_count = 0 + WHERE id = :conversation_id + "); + $stmt->bindParam(':conversation_id', $conversationId); + $stmt->execute(); + + // 提交事务 + $db->commit(); + + return true; + } catch (Exception $e) { + // 回滚事务 + $db->rollBack(); + error_log("标记消息已读失败: " . $e->getMessage()); + return false; + } +} + +/** + * 向用户发送消息 + */ +function sendToUser($server, $uid, $type, $message) { + foreach ($server->table as $row) { + if ($row['uid'] == $uid && $row['type'] == $type) { + try { + $server->push($row['fd'], json_encode($message)); + } catch (Exception $e) { + error_log("发送消息失败: " . $e->getMessage()); + } + } + } +} + +// 启动服务器 +echo "启动 WebSocket 服务器在 ws://0.0.0.0:9501\n"; +$server->start(); \ No newline at end of file