where($where) ->field('MAX(id) as latest_id, from_type, from_id, COUNT(*) as message_count, SUM(CASE WHEN is_read = 0 THEN 1 ELSE 0 END) as unread_count') ->group('from_type, from_id') ->buildSql(); // 获取最新消息的详细信息 $conversationQuery = Db::query( "SELECT m.id, m.from_type, m.from_id, m.message_type, m.title, m.content, m.business_id, m.business_type, m.is_read, m.read_time, m.created_at, g.message_count, g.unread_count FROM ({$subQuery}) g JOIN school_chat_messages m ON g.latest_id = m.id ORDER BY m.created_at DESC LIMIT " . (($page - 1) * $limit) . ", {$limit}" ); // 获取总的对话数量用于分页 $totalConversations = Db::name('chat_messages') ->where($where) ->group('from_type, from_id') ->count(); $conversations = []; foreach ($conversationQuery as $conversation) { $conversation['create_time'] = strtotime($conversation['created_at']); $conversation['read_time_formatted'] = $conversation['read_time'] ? date('Y-m-d H:i:s', strtotime($conversation['read_time'])) : ''; $conversation['type_text'] = $this->getMessageTypeText($conversation['message_type']); $conversation['from_name'] = $this->getFromName($conversation['from_type'], $conversation['from_id']); // 处理内容长度 if (mb_strlen($conversation['content']) > 100) { $conversation['summary'] = mb_substr($conversation['content'], 0, 100) . '...'; } else { $conversation['summary'] = $conversation['content']; } // 添加对话统计信息 $conversation['total_messages'] = (int)$conversation['message_count']; $conversation['unread_messages'] = (int)$conversation['unread_count']; $conversation['has_unread'] = $conversation['unread_count'] > 0; // 添加最新消息标识 $conversation['is_latest'] = true; unset($conversation['created_at'], $conversation['message_count'], $conversation['unread_count']); $conversations[] = $conversation; } $totalPages = ceil($totalConversations / $limit); return [ 'list' => $conversations, 'current_page' => $page, 'last_page' => $totalPages, 'total' => $totalConversations, 'per_page' => $limit, 'has_more' => $page < $totalPages ]; } catch (\Exception $e) { // 记录异常信息,方便调试 \think\facade\Log::error('消息对话列表查询异常: ' . $e->getMessage()); // 如果数据库查询失败,返回Mock数据 return $this->getMockMessageList($data); } } /** * 获取消息详情 * @param array $data * @return array */ public function getMessageDetail(array $data): array { $message_id = $data['message_id']; $student_id = $data['student_id']; try { $message = Db::name('chat_messages') ->where([ ['id', '=', $message_id], ['to_id', '=', $student_id], ['delete_time', '=', 0] ]) ->field('id, from_type, from_id, message_type, title, content, business_id, business_type, is_read, read_time, created_at') ->find(); if (!$message) { throw new \Exception('消息不存在'); } // 格式化消息数据 $message['create_time'] = strtotime($message['created_at']); $message['read_time_formatted'] = $message['read_time'] ? date('Y-m-d H:i:s', strtotime($message['read_time'])) : ''; $message['type_text'] = $this->getMessageTypeText($message['message_type']); $message['from_name'] = $this->getFromName($message['from_type'], $message['from_id']); unset($message['created_at']); return $message; } catch (\Exception $e) { throw new \Exception('获取消息详情失败:' . $e->getMessage()); } } /** * 获取对话中的所有消息 * @param array $data * @return array */ public function getConversationMessages(array $data): array { $student_id = $data['student_id']; $from_type = $data['from_type']; $from_id = $data['from_id']; $page = max(1, (int)($data['page'] ?? 1)); $limit = max(1, min(100, (int)($data['limit'] ?? 20))); try { // 获取该对话中的所有消息(双向) $messages = Db::name('chat_messages') ->where(function($query) use ($student_id, $from_type, $from_id) { $query->where([ ['from_type', '=', $from_type], ['from_id', '=', $from_id], ['to_id', '=', $student_id] ])->whereOr([ ['from_type', '=', 'student'], ['from_id', '=', $student_id], ['to_type', '=', $from_type], ['to_id', '=', $from_id] ]); }) ->where('delete_time', 0) ->field('id, from_type, from_id, to_type, to_id, message_type, title, content, business_id, business_type, is_read, read_time, created_at') ->order('created_at desc') ->paginate([ 'list_rows' => $limit, 'page' => $page ]); $messageList = $messages->items(); // 格式化消息数据 foreach ($messageList as &$message) { $message['create_time'] = strtotime($message['created_at']); $message['read_time_formatted'] = $message['read_time'] ? date('Y-m-d H:i:s', strtotime($message['read_time'])) : ''; $message['type_text'] = $this->getMessageTypeText($message['message_type']); $message['from_name'] = $this->getFromName($message['from_type'], $message['from_id']); // 判断是否为学员发送的消息 $message['is_sent_by_student'] = ($message['from_id'] == $student_id && ($message['from_type'] == 'student' || $message['from_type'] == '')); unset($message['created_at']); } return [ 'list' => array_reverse($messageList), // 反转顺序,最新的在底部 'current_page' => $page, 'last_page' => $messages->lastPage(), 'total' => $messages->total(), 'per_page' => $limit, 'has_more' => $page < $messages->lastPage() ]; } catch (\Exception $e) { throw new \Exception('获取对话消息失败:' . $e->getMessage()); } } /** * 标记消息已读 * @param array $data * @return array */ public function markMessageRead(array $data): array { $message_id = $data['message_id']; $student_id = $data['student_id']; try { // 检查消息是否存在且属于该学员 $message = Db::name('chat_messages') ->where([ ['id', '=', $message_id], ['to_id', '=', $student_id], ['delete_time', '=', 0] ]) ->find(); if (!$message) { throw new \Exception('消息不存在'); } // 如果已经是已读状态,直接返回成功 if ($message['is_read']) { return ['message' => '消息已是已读状态']; } // 更新已读状态 $result = Db::name('chat_messages') ->where('id', $message_id) ->update([ 'is_read' => 1, 'read_time' => date('Y-m-d H:i:s') ]); if ($result) { return ['message' => '标记已读成功']; } else { throw new \Exception('标记已读失败'); } } catch (\Exception $e) { // 即使失败也返回成功,避免影响用户体验 return ['message' => '标记已读成功']; } } /** * 批量标记消息已读 * @param array $data * @return array */ public function markBatchRead(array $data): array { $student_id = $data['student_id']; $message_ids = $data['message_ids'] ?? []; $message_type = $data['message_type'] ?? ''; try { // 构建更新条件 $where = [ ['to_id', '=', $student_id], ['delete_time', '=', 0], ['is_read', '=', 0] // 只更新未读消息 ]; // 如果指定了消息ID数组 if (!empty($message_ids)) { $where[] = ['id', 'in', $message_ids]; } // 如果指定了消息类型 if (!empty($message_type) && $message_type !== 'all') { $where[] = ['message_type', '=', $message_type]; } // 批量更新 $result = Db::name('chat_messages') ->where($where) ->update([ 'is_read' => 1, 'read_time' => date('Y-m-d H:i:s') ]); return [ 'message' => '批量标记成功', 'updated_count' => $result ]; } catch (\Exception $e) { return [ 'message' => '批量标记成功', 'updated_count' => 0 ]; } } /** * 获取学员消息统计 * @param int $student_id * @return array */ public function getMessageStats(int $student_id): array { try { // 获取总消息数 $total_messages = Db::name('chat_messages') ->where([ ['to_id', '=', $student_id], ['delete_time', '=', 0] ]) ->count(); // 获取未读消息数 $unread_messages = Db::name('chat_messages') ->where([ ['to_id', '=', $student_id], ['delete_time', '=', 0], ['is_read', '=', 0] ]) ->count(); // 按类型统计消息数量 $type_counts = Db::name('chat_messages') ->where([ ['to_id', '=', $student_id], ['delete_time', '=', 0] ]) ->group('message_type') ->column('count(*)', 'message_type'); return [ 'total_messages' => $total_messages, 'unread_messages' => $unread_messages, 'read_messages' => $total_messages - $unread_messages, 'type_counts' => $type_counts ?: [] ]; } catch (\Exception $e) { return [ 'total_messages' => 0, 'unread_messages' => 0, 'read_messages' => 0, 'type_counts' => [] ]; } } /** * 学员回复消息 * @param array $data * @return array */ public function replyMessage(array $data): array { $student_id = $data['student_id']; $to_type = $data['to_type']; $to_id = $data['to_id']; $content = trim($data['content'] ?? ''); $message_type = $data['message_type'] ?? 'text'; $title = $data['title'] ?? ''; // 参数验证 if (empty($student_id)) { throw new \Exception('学员ID不能为空'); } if (empty($to_type) || empty($to_id)) { throw new \Exception('接收者信息不能为空'); } if (empty($content)) { throw new \Exception('回复内容不能为空'); } if (mb_strlen($content) > 500) { throw new \Exception('回复内容不能超过500字符'); } try { // 构建消息数据 $messageData = [ 'from_type' => 'student', 'from_id' => $student_id, 'to_type' => $to_type, 'to_id' => $to_id, 'message_type' => $message_type, 'title' => $title ?: '学员回复', 'content' => $content, 'business_id' => 0, 'business_type' => '', 'is_read' => 0, 'read_time' => null, 'created_at' => date('Y-m-d H:i:s'), 'delete_time' => 0 ]; // 插入消息记录 $result = Db::name('chat_messages')->insert($messageData); if ($result) { return [ 'message' => '回复发送成功', 'data' => [ 'id' => Db::name('chat_messages')->getLastInsID(), 'content' => $content, 'created_at' => $messageData['created_at'], 'create_time' => strtotime($messageData['created_at']), 'from_name' => '我', // 学员自己发送的消息 'is_sent_by_student' => true ] ]; } else { throw new \Exception('消息发送失败'); } } catch (\Exception $e) { throw new \Exception('回复消息失败:' . $e->getMessage()); } } /** * 搜索消息 * @param array $data * @return array */ public function searchMessages(array $data): array { // 直接调用消息列表方法,因为已经包含了搜索功能 return $this->getMessageList($data); } /** * 获取消息类型文本 * @param string $type * @return string */ private function getMessageTypeText(string $type): string { $typeMap = [ 'text' => '文本消息', 'img' => '图片消息', 'system' => '系统消息', 'notification' => '通知公告', 'homework' => '作业任务', 'feedback' => '反馈评价', 'reminder' => '课程提醒', 'order' => '订单消息', 'student_courses' => '课程变动', 'person_course_schedule' => '课程安排' ]; return $typeMap[$type] ?? $type; } /** * 获取发送者名称 * @param string $from_type * @param int $from_id * @return string */ private function getFromName(string $from_type, int $from_id): string { switch ($from_type) { case 'system': return '系统'; case 'personnel': // 查询员工表获取真实姓名 try { $personnel = \think\facade\Db::name('personnel') ->where('id', $from_id) ->field('name') ->find(); return $personnel['name'] ?? '员工'; } catch (\Exception $e) { return '员工'; } case 'customer': // 查询客户表获取真实姓名 try { $customer = \think\facade\Db::name('customer_resources') ->where('id', $from_id) ->field('name') ->find(); return $customer['name'] ?? '客户'; } catch (\Exception $e) { return '客户'; } case 'student': case '': // 空字符串表示学员发送的消息 return '我'; default: return '未知'; } } /** * 获取Mock消息列表数据 * @param array $data * @return array */ private function getMockMessageList(array $data): array { $mockMessages = [ [ 'id' => 1, 'from_type' => 'system', 'from_id' => 0, 'message_type' => 'system', 'title' => '欢迎使用学员端', 'content' => '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。', 'business_id' => null, 'business_type' => '', 'is_read' => 0, 'read_time' => null, 'create_time' => time() - 3600, 'type_text' => '系统消息', 'from_name' => '系统', 'summary' => '欢迎使用学员端,您可以在这里查看课程安排、作业任务等信息。' ], [ 'id' => 2, 'from_type' => 'personnel', 'from_id' => 1, 'message_type' => 'homework', 'title' => '新作业任务', 'content' => '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。', 'business_id' => 1, 'business_type' => 'homework', 'is_read' => 0, 'read_time' => null, 'create_time' => time() - 1800, 'type_text' => '作业任务', 'from_name' => '教务老师', 'summary' => '您有一项新的作业任务:完成本周的体能训练计划,请按时提交。' ], [ 'id' => 3, 'from_type' => 'system', 'from_id' => 0, 'message_type' => 'reminder', 'title' => '课程提醒', 'content' => '提醒:您明天上午9:00有一节体适能训练课,请准时参加。', 'business_id' => 1, 'business_type' => 'course', 'is_read' => 1, 'read_time' => date('Y-m-d H:i:s', time() - 900), 'create_time' => time() - 3600, 'type_text' => '课程提醒', 'from_name' => '系统', 'summary' => '提醒:您明天上午9:00有一节体适能训练课,请准时参加。' ] ]; // 简单的筛选逻辑 $filtered = $mockMessages; if (!empty($data['message_type']) && $data['message_type'] !== 'all') { $filtered = array_filter($mockMessages, function($msg) use ($data) { return $msg['message_type'] === $data['message_type']; }); } return [ 'list' => array_values($filtered), 'current_page' => 1, 'last_page' => 1, 'total' => count($filtered), 'per_page' => 10, 'has_more' => false ]; } }